Magento для PHP MVC разработчиков – Макеты, Блоки и Шаблоны (ч.3/11)
Продолжая обзор Magento мы перейдем к рассмотрению Макетов (Layouts) и Блоков (Blocks).
В отличие от многих популярных PHP MVC систем, контроллер Magento не передает объект данных в View (Вид) либо не задает параметры для объекта view. Зато компонента View модели непосредственно ссылается на системные модели, чтобы получить информацию, необходимую для отображения.
Одним из последствий этого дизайнерского решения является то, что компонента View была разделена на Blocks (блоки) и Templates (шаблоны). Blocks – это PHP объекты, Templates – «сырые» PHP файлы, которые содержат смесь HTML и PHP (где PHP используется в качестве языка программирования для шаблонов). Каждый блок привязан к одному, простому файлу шаблона. Внутри phtml файла ключевое слово $this
содержит ссылку на объект блока шаблона.
Быстрый пример. Посмотрите на шаблон списка товаров по умолчанию в файле:
app/design/frontend/base/default/template/catalog/product/list.phtml
Вы увидите следующий PHP код шаблона.
<?php $_productCollection=$this->getLoadedProductCollection(); $_helper = $this->helper('catalog/output'); ?> <?php if(!$_productCollection->count()): ?> <p class="note-msg"><?php echo $this->__('There are no products matching the selection.') ?></p> <?php else: ?>
Метод getLoadedProductCollection
можно найти в шаблоне блока,
Mage_Catalog_Block_Product_List
app/code/core/Mage/Catalog/Block/Product/List.php ... public function getLoadedProductCollection(){ return $this->_getProductCollection(); } ...
Метод блока _getProductCollection
затем создает экземпляры модели и читает их данные, возвращая результат в шаблон.
Вложенные блоки
Реальная сила сочетания Блок/Шаблон (Block/Template) становится явной при использовании getChildHtml
метода. Он позволяет включать контент второстепенного Блок/Шаблон внутрь основного Блок/Шаблон.
Блоки вызывают Блоки, которые вызывают Блоки – вот так и будет создан весь HTML-макет для вашей страницы.
Посмотрите на шаблон макета страницы в одну колонку.
app/design/frontend/base/default/template/page/1column.phtml <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo $this->getLang() ?>" lang="<?php echo $this->getLang() ?>"> <head> <?php echo $this->getChildHtml('head') ?> </head> <body<?php echo $this->getBodyClass()?' class="'.$this->getBodyClass().'"':'' ?>> <?php echo $this->getChildHtml('after_body_start') ?> <div class="wrapper"> <?php echo $this->getChildHtml('global_notices') ?> <div class="page"> <?php echo $this->getChildHtml('header') ?> <div class="main-container col1-layout"> <div class="main"> <?php echo $this->getChildHtml('breadcrumbs') ?> <div class="col-main"> <?php echo $this->getChildHtml('global_messages') ?> <?php echo $this->getChildHtml('content') ?> </div> </div> </div> <?php echo $this->getChildHtml('footer') ?> <?php echo $this->getChildHtml('global_cookie_notice') ?> <?php echo $this->getChildHtml('before_body_end') ?> </div> </div> <?php echo $this->getAbsoluteFooter() ?> </body> </html>
Сам шаблон содержит совсем немного кода. Тем не менее, каждый вызов $this->getChildHtml(...)
будет подгружать и добавлять в шаблон еще один блок (Block). Эти блоки, в свою очередь, также могут использовать getChildHtml
для получения других блоков. Вот так блоками и строится весь финальный шаблон страницы.
Макет (Layout)
Итак, Блоки и Шаблоны, это все хорошо, но вам, вероятно, интересно:
- Как я объявляю Magento, какой блок я хочу использовать на этой странице?
- Как я объявляю Magento, с которого блока я должен начать рендеринг?
- Как мне указать конкретный блок в
getChildHtml(...)
? Эти строки не похожи на имя моего блока.
На эти вопросы отвечает Макет. Layout (Макет) – это набор XML строк, который будет определять, какие блоки на странице включены, а какие блоки не участвуют при рендеринге.
В прошлой статье серии мы выводили контент на экран прямо с экшен методов. На этот раз давайте создадим простой HTML шаблон для нашего модуля Hello World.
Во-первых, создадим файл в
app/design/frontend/ПАКЕТ_ТЕМ/ТЕМА/layout/local.xml
со следующим содержанием
<?xml version="1.0" encoding="UTF-8"?> <layout> <default> <reference name="root"> <block type="page/html" name="root" output="toHtml" template="simple_page.phtml" /> </reference> </default> </layout>
Затем создайте файл в
app/design/frontend/ПАКЕТ_ТЕМ/ТЕМА/template/simple_page.phtml
со следующим содержанием (мы используем спецификацию HTML5)
<!DOCTYPE html> <html lang="<?php echo $this->getLang() ?>"> <head> <title>Тест</title> <style type="text/css"> body { background-color: #f00; } </style> </head> <body> </body> </html>
Для запуска процесса создания макета (макетирование) с контроллера нам нужно добавить вызовы еще двух методов с экшен метода.
public function indexAction() { $this->loadLayout(); $this->renderLayout(); }
Очистите кэш Magento и перезагрузите страницу с контроллером Hello World (по адресу VASH-DOMEN/helloworld/index
). Теперь вы должны увидеть веб-страницу с ярко-красным фоновым цветом и HTML кодом, который содержится в шаблоне simple_page.phtml
.
Что происходит
Итак, здесь много магии вуду и загадочного колдовства. Давайте посмотрим на то, что происходит внутри.
При открытии страницы в Мадженто, система создает один большой XML-файл, содержащий в себе все настройки макета темы (все теги <block/>
, <reference/>
и <remove/>
). Когда вызывается метод контроллера loadLayout
, то Magento будет:
- Создавать этот XML-макет
- Создавать экземпляры класса Блока для каждого тега
<block/>
и<reference/>
, используя в качестве класса имя атрибутаtype
тега как путь к глобальным настройкам, и сохранять их во внутреннем массиве объектов макета_blocks
, используя имя атрибутаname
в качестве ключа к массиву. - Если тег
<block/>
содержит атрибутoutput
, его значение добавляется к внутреннему массиву_output
объектов макета.
Затем, когда вы вызовете в контроллере метод renderLayout
, Magento будет перебирать все Блоки в массиве _output
, используя значение атрибута output
в качестве метода обратного вызова. Это всегда toHtml
, и он представляет собой отправную точку для вывода этого шаблона блока.
В следующих разделах мы рассмотрим то, как создаются экземпляры Блоков, как генерируются их файлы макета, и завершим на процессе вывода.
Создание экземпляра Блока
Итак, в XML-файле макета тег <block/>
или <reference/>
имеет атрибут type
, что на самом деле является URI сгруппированного имени класса. То есть, краткой записью имени класса, который использует Блок.
<block type="page/html" ... <block type="page/template_links" ...
Располагается URI сгруппированного имени класса в файле глобальной конфигурации. Первая часть URI (перед слешем; у приведенных выше примерах page
) используется для запроса к глобальной конфигурации, чтобы найти имя класса страницы (page). Вторая часть URI (после слеша) будет добавлена к названию базового класса, чтобы создать имя класса, который Magento должен будет «экземплировать».
В качестве примера, посмотрим URI для page/html
. Сначала Magento смотрит файл глобальной конфигурации для этого типа:
app/code/core/Mage/Page/etc/config.xml
и находит
<page> <class>Mage_Page_Block</class> </page>
Это дает нам имя базового класса Mage_Page_Block
. Затем, вторая часть URI (html
) добавляется к этому имени класса, чтобы получить окончательное название класса Блока Mage_Page_Block_Html
. Это и будет классом, экземпляр которого будет создан.
Если мы создадим блок с тем же именем, как у уже существующего блока, новый экземпляр блока заменит оригинальный экземпляр. Это то, что мы сделали в нашем файле local.xml
(сверху в статье).
Использование ссылок
<reference name="" />
заденет все XML декларации, содержащиеся в уже существующем блоке с указанным именем. Все узлы <block/>
, включенные в ссылки (reference), будут подписаны в качестве дочерних блоков к родительскому блоку ссылки.
<layout version="0.1.0"> <default> <block type="page/html" name="root" output="toHtml" template="page/2columns-left.phtml"> <!-- ... дочерние блоки ... --> </block> </default> </layout>
в другом файле макета:
<layout version="0.1.0"> <default> <reference name="root"> <!-- ... другой дочерний блок ... --> <block type="page/someothertype" name="some.other.block.name" template="path/to/some/other/template" /> </reference> </default> </layout>
Несмотря на то, что корневой блок (root
) объявляется в отдельном XML-файле макета, новый блок добавляется как дочерний блок. Magento сначала создает Блок page/html
с именем root
. Тогда, когда он позже встретит ссылки с тем же именем (root), он назначит новый блок some.other.block.name
как дочерний корневому блоку.
Как генерируются Файлы Макета
Итак, в настоящее время у нас уже несколько улучшилось понимание того, что происходит с XML макетом. Но откуда этот XML-файл берется? Чтобы ответить на этот вопрос, мы должны ввести два новых понятия: Handles (зацепки) и Package Layout (пакет макетов).
Handles (зацепки)
Каждый запрос страницы в Magento будет генерировать несколько уникальных зацепок. Например, для главной страницы сайта это могут быть такие зацепки: default
, cms_index_index
, customer_logged_out
.
Зацепки содержатся в различных местах в системе Magento. Обратим внимание на две зацепки: default
и helloworld_index_index
. Зацепка default
присутствует в каждом запросе в системе Magento. Зацепка helloworld_index_index
создается путем объединения названия маршрута (helloworld), названия действия контроллера (index) и названия метода действия контроллера (index) в единственную строку. Это означает, что каждый возможный метод в контроллере имеет связанную с ним зацепку.
Пакет макетов
Вы можете представить Пакет макетов в качестве файла глобальной конфигурации. Это большой XML-файл, содержащий все возможные конфигурации макетов для конкретной системы Magento. Он создается путем объединения содержимого всех файлов XML-макетов для текущей темы. Для темы Мадженто по умолчанию это содержание всех файлов в папке:
app/design/frontend/base/default/layout/
За кулисами этого остаются секции <frontend><layout><updates/>
и <adminhtml><layout><updates/>
глобальной конфигурации, содержащие узлы со всеми названиями файлов, которые должны загружаться в текущую область. После того, как файлы, перечисленные в файле config
обработались, Magento объединяет их всех и последним добавляет универсальный XML-файл – local.xml
. Это файл, в котором вы сможете добавить свои уникальные настройки в текущей теме Magento.
Комбинация Зацепок и Пакета макетов
Если вы посмотрите на файл макета, вы увидите некоторые знакомые теги, например <block/>
и <reference/>
, но все они окружены другими тегами, например:
<default/> <catalogsearch_advanced_index/> ...
Это все теги-зацепки. Макет страницы при каждом отдельном запросе генерируется путем захвата всех секций пакета макетов, которые совпадают с зацепками в запросе.
Плюс еще один дополнительный тег, о котором вам следует знать в пакете макетов. Тег <update/>
, который позволяет включить другие теги-зацепки. К примеру:
<customer_account_index> <!-- ... --> <update handle="customer_account"/> <!-- ... --> </customer_account_index>
Эта конструкция означает, что запросы к зацепке customer_account_index
должны включать в себя <reference/>
и <blocks/>
с зацепки <customer_account/>
.
Закрепление изученного на практике
Хорошо, теперь перейдем от теории к практике. Давайте вернемся к тому, что мы делали раньше. Обладая новыми знаниями, добавляем:
<layout version="0.1.0"> <default> <block type="page/html" name="root" output="toHtml" template="simple_page.phtml" /> </default> </layout>
в файл local.xml
. Это значит, что мы переопределили тег «root» другим блоком. Размещая это в зацепке <default/>
, мы обеспечили переопределения, что будут срабатывать при каждом запросе страницы в системе. Это, вероятно, не то, что нам нужно.
Если вы откроете любую другую страницу на сайте, вы заметите, что они или пустые или имеют тот же красный фоновый цвет, как и страница модуля «hello world». Давайте изменим запись в local.xml так, чтобы изменения касались только страницы «hello world». Мы сделаем это, изменив зацепку default
на зацепку для контроллера нашего модуля (т.е., helloworld_index_index
).
<layout version="0.1.0"> <helloworld_index_index> <block type="page/html" name="root" output="toHtml" template="simple_page.phtml" /> </helloworld_index_index> </layout>
Очистите кэш в Magento и остальные страницы должны обновиться.
Сейчас это относится только к нашему index
методу. Давайте также протестуем и goodbye
метод. В контроллере меняем экшен метод goodbye
так, чтобы это выглядело следующим образом:
public function goodbyeAction() { $this->loadLayout(); $this->renderLayout(); }
Если вы загрузите следующий адрес, то вы заметите, что по-прежнему получаете макет Magento по умолчанию (default):
VASH-DOMEN/helloworld/index/goodbye
Нам нужно добавить зацепку с полным именем действия (helloworld_index_goodbye
) к файлу local.xml. Это позволит использовать тег update
для зацепки helloworld_index_index
вместо того, чтобы объявлять новый <block/>
.
<layout version="0.1.0"> <!-- ... --> <helloworld_index_goodbye> <update handle="helloworld_index_index" /> </helloworld_index_goodbye> </layout>
Загрузка следующих страниц (после очистки кэша) должна давать идентичные результаты:
VASH-DOMEN/helloworld/index/index VASH-DOMEN/helloworld/index/goodbye
Начало Вывода и getChildHtml
В стандартной конфигурации вывод начинается с Блока root
(поскольку он имеет атрибут вывода – output
). Мы переопределяем корневой шаблон собственным
template="simple_page.phtml"
Шаблоны ссылаются на корневую папку текущей или по умолчанию (default) темы. Для дефолтной темы это путь:
app/design/frontend/base/default
Для темы custom (например) это будет путь:
app/design/frontend/default/custom/template
То есть, полный путь к файлу шаблона будет таким:
app/design/frontend/default/custom/template/simple_page.phtml
Пакет тем base является конечным местом назначения для любого шаблона. Если Magento не найдет шаблон в любой другой теме, он вернется к базовой (base). Однако, как уже упоминалось, вы не должны размещать свою тему в этой папке (или корректировать файлы в ней), поскольку после обновления Magento все ваши корректировки будут затерты. Вместо этого, создавайте свой пакет тем, или же собственную тему в пакете тем default.
Добавление блоков контента
Пустая красная страница довольно скучная. Давайте наполним ее некоторым содержанием. Меняем зацепку <helloworld_index_index />
в local.xml так, как это указано ниже:
<helloworld_index_index> <block type="page/html" name="root" output="toHtml" template="simple_page.phtml"> <block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/> </block> </helloworld_index_index>
Мы добавляем новый Блок, вложенный в корневой блок root
. Это блок, который поставляется с Magento – он позволяет выводить на странице форму для регистрации клиента. Разместив блок в корневом root блоке, мы сделали его доступным для включения в нашем шаблоне simple_page.html
. Итак, это дает нам возможность вызвать блок в шаблоне simple_page.phtml, используя Блочный метод getChildHtml
. Редактируем шаблон simple_page.html следующим образом:
<body> <?php echo $this->getChildHtml('customer_form_register'); ?> </body>
Очистите кэш Magento, перезагрузите страницу, и вы увидите на странице с красным фоновым цветом форму для регистрации клиентов. В Magento есть также встроенный блок с именем top.links
. Давайте попробуем подключить к шаблону и его. Редактируем simple_page.html следующим образом:
<body> <h1>Links</h1> <?php echo $this->getChildHtml('top.links'); ?> </body>
Когда вы перезагрузите страницу, то заметите, что заголовок <h1>Links</h1>
рендерится, но ничего не рендерится для блока top.links
. Это потому, что мы не добавили его в local.xml (или в другой файл XML-макетов). Метод getChildHtml
может вызывать только те блоки, которые указаны как дочерние блоки в макете. Это позволяет Magento выводить только действительно нужные блоки, а также позволяет устанавливать различные шаблоны для блоков.
Давайте добавим блок top.links
к нашему local.xml
<helloworld_index_index> <block type="page/html" name="root" output="toHtml" template="simple_page.phtml"> <block type="page/template_links" name="top.links"/> <block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/> </block> </helloworld_index_index>
Очистите кэш и перезагрузите страницу. Теперь вы должны увидеть вывод модуля top.links
(некоторые основные ссылки системы: например, ссылку для логина на сайт, ссылку на корзину и др.).
Время применить action метод
Существует еще одна важная концепция, которую следует рассмотреть, прежде чем завершим этот урок, и это тег <action/>
. Используя тег <action/>
, мы можем вызвать публичные методы PHP классов блоков. Поэтому вместо того, чтобы менять шаблон корневого root
блока, заменив экземпляр блока на свой собственный, мы можем использовать вызов метода setTemplate
.
<layout version="0.1.0"> <helloworld_index_index> <reference name="root"> <action method="setTemplate"> <template>simple_page.phtml</template> </action> <block type="page/template_links" name="top.links"/> <block type="customer/form_register" name="customer_form_register" template="customer/form/register.phtml"/> </reference> </helloworld_index_index> </layout>
Этот код XML макета сначала установит шаблон для корневого root блока, а затем добавит два блока, которые мы используем в качестве дочерних. Как только мы очистим кэш, результат должен выглядеть так же, как и раньше. Преимущество использования <action/>
— это тот самый экземпляр блока, который был создан ранее, а все его ассоциации родители/потомки сохранятся. По этой причине это будет более надежный способ внедрения наших изменений.
Все аргументы экшен метода должны быть окружены отдельным дочерним нодом тега <action/>
. Имя этого нода (узла) не имеет значения, важен лишь порядок узлов. Мы могли бы написать нод action
из предыдущего примера следующим образом, что будет иметь такой же эффект:
<action method="setTemplate"> <some_new_template>simple_page.phtml</some_new_template> </action>
Этот пример лишь для того, чтобы проиллюстрировать, что имена аргументов узла action
являются произвольными.
Итог
В этом уроке мы рассмотрели основные принципы компоновки в Мадженто. Мы рассмотрели теги <block/>
, <reference/>
, <update/>
и <action/>
, а также зацепки обновлений макета, такие как <default/>
и <cms_index_index/>
. Они составляют большую часть конфигурации макета, который используется в Magento. Если это кажется вам немного сложным, не волнуйтесь, PHP-разработчику Мадженто редко нужно работать с макетами на таком фундаментальном уровне. Magento предоставляет ряд предварительно сконструированных макетов, которые можно модифицировать для достижения основных потребностей интернет-магазина на этом движке. А понимание того, как работает вся система макетов, может стать отличной помощью, когда вы столкнетесь с проблемами макетирования или захотите добавить новые функции к существующей системе.
Автор: Alan Storm (http://alanstorm.com/layouts_blocks_and_templates/)
Перевод на русский: SebWeo
Все статьи данной серии:
- Magento для PHP MVC разработчиков (Alan Storm) – ч.1/11
- Magento для PHP MVC разработчиков – разбор контроллера (ч.2/11)
- Magento для PHP MVC разработчиков – Макеты, Блоки и Шаблоны (ч.3/11)
- Magento для PHP MVC разработчиков – Модели и основы ORM (ч.4/11)
- Magento для PHP MVC разработчиков – Инсталлирование Ресурса (ч.5/11)
- Magento для PHP MVC разработчиков – Расширенный ORM – EAV (ч.6/11)
- Magento для PHP MVC разработчиков – Особая конфигурация системы (ч.7/11)
- Magento для PHP MVC разработчиков – Углубленная настройка системы (ч.8/11)
- Magento для PHP MVC разработчиков – Коллекции Varien Data (ч.9/11)
- Magento для PHP MVC разработчиков – Переопределение и обновляемость системы (ч.10/11)
- Magento для PHP MVC разработчиков – Конфигурация системы по умолчанию (ч.11/11)