Если вы читали статьи про MVC, участвовали в холиварах на тему «стоит ли использовать логику в шаблонах», то наверняка эта статья вас заинтересует. Как мы знаем, изначально Kohana предлагает нам реализацию представлений (Views) в виде обычных php-файлов. В результате шаблоны выглядят как сборная солянка из HTML-кода и PHP-вставок (а некоторые туда еще и JS впихивают). Кому-то это может показаться нормальным, но все же здравый смысл подсказывает, что надо бы разделить все это разнородное добро. Собственно для решения подобных проблем и предлагается модуль Kostache.
Считается, что шаблоны могут содержать логику, если она презентационная (т.е. определяет, как отображать контент, но не генерирует его). Например, различные проверки пользователя на наличие у него прав, ветвление в зависимости от количества элементов в массиве (если есть — показать список записей, если нет — соответствующее сообщение). Однако сами шаблоны от этого чище и понятнее не становятся. Главная проблема все та же — смесь разметки (структуры страницы) и логики отображения. Модуль Kostache позволяет выделить эту самую логику в отдельный класс, а разметку оставить в шаблоне с вкраплениями вставок специального формата.
Используемая структура файлов и папок
Kostache использует два типа файлов — собственно шаблоны (templates) и классы представлений. Классы хранятся в папке classes/view и имеют расширение php, а шаблоны в templates (не views!), с расширением .mustache
. Как правило, имена файлов класса и шаблона совпадают, но это может быть исправлено специальными настройками в классе. Можно использовать поддиректории.
Практика
Начнем с простого — создадим базовый шаблон странички:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <title>{{title}}</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link rel="stylesheet" type="text/css" href="/css/style.css" /> </head> <body> <div id="header">{{header}}</div> <div id="wrapper"> <div id="sidebar"> {{sidebar}} </div> <div id="content"> {{content}} </div> </div> <div id="footer">{{footer}}</div> </body> </html> |
Внешне все понятно — раскидали структуру страницы, в двойных фигурных скобочках приведены какие-то переменные. Файл сохраняем под именем index.mustache в папке templates. Теперь надо создать класс для него. Создаем файл index.php и сохраняем в classes/index.php. Содержимое такое:
<?php defined('SYSPATH') OR die('No direct access allowed.'); class View_Index extends Kostache {} |
Класс пустой, пока ничего от себя не добавляем. Чтобы проверить его работу, в контроллере пишем echo Kostache::factory('index')
(или с использованием конструктора — new View_Index
, как удобнее). В результате увидим, что выведены вся html-разметка, а параметры в фигурных скобках пропущены. Так работает Kostache — если переменная не найдена, то вместо нее выводится пустая строка (или пустой массив, зависит от контекста).
По умолчанию используются двойные фигурные скобочки. Но вы можете переназначить открывающий и закрывающий тэги, задав свойства $o_tag
и $c_tag
класса, либо непосредственно в шаблоне использовать тэг со знаком равенства.
В классе шаблона:
public $o_tag = '<<'; public $c_tag = '>>'; |
или аналогичное в самом шаблоне (отделяем новые разделители пробелом)
{{=[[ ]]}} // выводим значение переменной по-новому [[foo]] // возвращаем все обратно [[={{ }}]] |
Начнем потихоньку заполнять шаблон. Переменная title
должна содержать заголовок страницы, поэтому давайте ее заполним.
<?php defined('SYSPATH') OR die('No direct access allowed.'); class View_Index extends Kostache { public $title = 'test Kostache page'; } |
Теперь наша страница имеет заголовок. Но прописывать в самом классе не очень интересно, лучше делать это через контроллер:
$view = Kostache::factory('index'); $view->title = 'value from controller'; echo $view; |
Вместо прямого обращения к свойству класса можно использовать метод set($key, $value)
. Также есть метод bind($key, $value)
, все аналогично стандартному View.
Методы в шаблоне
Kostache позволяет использовать имеющиеся публичные (public) методы класса для вызова из шаблона. Например, создадим метод для вывода футера:
// класс View_Index public function sidebar() { return 'function test'; } |
Теперь в шаблоне появилась строчка ‘function test‘ в блоке sidebar. Синтаксис вызова такой же, как и для переменной, т.е. {{sidebar}}
. К сожалению, передать параметры туда нельзя. Если класс имеет и свойство, и метод с одинаковыми именами, то будет возвращено значение свойства. Единственное исключение — когда значение свойства NULL (например, свойство объявлено, но не заполнено). В таком случае вернется значение из функции. Это может быть удобно для кэширования работы функции (результат работы метода sidebar()
сохраняем в свойстве $sidebar
, в результате метод будет вызван только один раз):
// класс View_Index public function sidebar() { echo 'sidebar() running!'; // увидим только один раз return $this->sidebar = 'function test'; } |
Экранирование
По умолчанию все выводимые значения экранируются. Если требуется вывести текст как есть (например, отформатированный HTML из БД), добавьте по фигурной скобке, например {{{text}}}
. Также можно использовать знак «амперсанд» — {{&text}}
. Второй вариант удобен, если вы поменяли стандартные разделители на что-то свое (в примере выше после установки квадратных скобок в качестве разделителей у меня было проигнорировано выражение [[{text}]]
, хотя [[&text]]
работало нормально).
Вложения (partials)
Предположим, у нас есть макет верхней части сайта (header), который хотелось бы прикрутить к имеющемуся шаблону. Вспоминаем о переменной {{header}}
. Но так как шаблон простенький (статичный HTML), создавать специально для него отдельный класс не хочется. Надо просто включить в тело основного шаблона все содержимое нашего подшаблона. Сперва сохраним макет в файле соответствующего формата:
// файл views/layout/header.mustache <h1>Добро пожаловать!</h1> |
Далее, надо подключить вывод содержимого шаблона. Вывод вложений отличается от обычных переменных, перед именем ставится знак «больше»:
<div id="header">{{> header}}</div> |
Не бросайтесь проверять — сразу вложение не появится. Надо его еще прописать в классе родительского шаблона (т.е. в View_Index). Для этого имеется специальное свойство $_partials
:
protected $_partials = array( 'header' => 'layout/header', ); |
Теперь работает. Обратите внимание, что в шаблоне мы использовали не путь к вложению целиком (layout/header), а его короткое название (псевдоним), а в свойстве $_partials
связали их между собой, чтобы Kostache все корректно показал. Вложения позволяют работать с переменными и методами своего родителя. Например, в шаблоне layout/header мы можем работать с переменной {{title}}
или функцией {{sidebar}}
.
К сожалению, описанный выше прием с методом
sidebar()
не сработает, т.к. значение переменной$sidebar
копируется из родительского шаблона, а не используется по ссылке. Например, дополнительный{{sidebar}}
во вложении header привет к еще одному вызову методаsidebar()
и только следующий вызов будет брать сохраненное в переменной значение. Тем не менее, этот прием может быть удобен, например при работе в цикле.
Итерации
Предположим, у нас есть список меню, который надо вывести в sidebar‘е, а хранится он в свойстве $menu
класса View_Index:
public $menu = array( array('title' => 'first', 'url' => '/first'), array('title' => 'second', 'url' => '/second'), ); |
Поскольку переменная $menu
видна во всех вложениях шаблона, то давайте сделаем вложение sidebar, в котором будем выводить пункты меню:
// файл templates/layout/sidebar.mustache <ul> {{#menu}} <li><a href="{{url}}">{{title}}</a></li> {{/menu}} {{^menu}} <li>В меню нет пунктов!</li> {{/menu}} </ul> |
Конечно, надо подкорректировать и файлы основного шаблона: в классе View_Index описать вложение sidebar в свойстве
$_partials
, а в самом шаблоне index.mustache добавить знак «больше» (т.е.{{>sidebar}}
).
Имя тэга с решеткой означает, что переменная (или результат функции) будет подвергнута перебору как обычный массив. Если же массив пустой, то выполнится секция после {{^sidebar}}
. Элементы текущей записи массива преобразовываются в обычные переменные (в примере мы видим тэги {{url}}
и {{title}}
). Если переменная уже существует, она будет заменена новой, но только на время работы цикла. Таким образом, есть некое подобие области видимости переменных.
Здесь мы разобрали только работу с ассоциативными массивами. Красиво работать с остальными (пока) не получится, об этом чуть позже.
Прагмы (pragmas)
Прагма — это такая директива, влияющая на обработку шаблона. Она может быть поставлена в любом месте шаблона (или указана в классе шаблона), и действует только для него (не распространяется даже на вложения). Синтаксис для шаблона такой:
{{%ИМЯ_ПРАГМЫ [параметр=значение]}}
Параметр и значение необязательны, для некоторых прагм их и не существует. Kostache поддерживает следующие виды:
- DOT-NOTATION. Данная прагма определяет возможность обращения к элементам массива с помощью разделителя «точка». Например, элемент
['foo']['bar']
может быть получен как foo.bar. Данная прагма может быть полезна, когда не планируется перебор дочерних элементов массива, а структура его в принципе известна (какие-нибудь анкетные данные или статистика). - IMPLICIT-ITERATOR. Позволяет обращаться к текущему элементу (неассоциативного) массива. Напомню, именно при работе с такими массивами были проблемы при выводе меню. Предположим, в свойстве
$menu
хранится массивarray('first', 'second')
. Чтобы они вывелись в списке шаблона sidebar, немного подредактируем его:// файл templates/layout/sidebar.mustache <ul> {{%IMPLICIT-ITERATOR}} {{#menu}} <li><a href="{{.}}">{{.}}</a></li> {{/menu}} {{^menu}} <li>В меню нет пунктов!</li> {{/menu}} </ul>
По умолчанию текущий элемент доступен по тэгу «точка». Прагма имеет дополнительный параметр iterator, указывающий имя тэга для итератора:
// файл templates/layout/sidebar.mustache <ul> {{%IMPLICIT-ITERATOR iterator=row}} {{#menu}} <li><a href="{{row}}">{{row}}</a></li> {{/menu}} {{^menu}} <li>В меню нет пунктов!</li> {{/menu}} </ul>
- UNESCAPED. Данная прагма работает с экранированием значений. Если данная прагма используется, то Kostache не экранирует выводимые данные по умолчанию. А вот действие третьей фигурной скобки или амперсанда & становится обратным — переменная экранируется.
Пример:
// Имеем переменную $italic = '<i>курсив</i>' {{italic}} {{{italic}}} // Шаблон №2. С прагмой, т.е. экранирование инвертировано (по умолчанию нет экранирования) // Теперь первая строчка будет с курсивом {{%UNESCAPED}} {{italic}} {{{italic}}}
Имена прагм чувствительны к регистру, при любом отклонении от стандартного написания получите ошибку (MustacheException).
Помимо вставки прагм в тело шаблона их можно указывать в классе представления. Для этого имеется массив $_pragmas
, в котором можно добавить нужные прагмы. Выглядеть это будет примерно так:
protected $_pragmas = array( Kostache::PRAGMA_UNESCAPED => TRUE, Kostache::PRAGMA_IMPLICIT_ITERATOR => array('iterator' => 'row'), ); |
Мы отключили режим экранирования по умолчанию, а также указали, что при итерации массивов элементы будут доступны под именем row
. Стоит помнить, что прагма в шаблоне имеет бОльший вес, чем прагма в классе.
Комментарии
Иногда бывает полезно оставить для себя комментарий, описывающий конкретную строчку или блок шаблона. Это можно сделать с помощью стандартных комментариев HTML, но они будут видны в исходнике страницы. Kostache позволяет писать комментарии, которые будут отброшены при рендеринге. Используйте восклицательный знак сразу после открывающих скобок:
{{! это комментарий}} {{! пишем комментарии на несколько строк }} |
Работа с массивами
Ранее мы рассмотрели работу с ассоциативными массивами, которые в рамках итерации превращаются в отдельные переменные. Например, итерация по ORM-объекту $users
позволит использовать в шаблоне такие переменные, как username, email и т.д. Удобно для ActiveRecord-объектов. Однако представим, что передан массив в виде пар 'username' => 'email'
. На данный момент Kostache не умеет работать с ключами, поэтому итерация провалится. Необходимо обработать массив, чтобы Kostache его правильно поняла. Я бы предложил такой алгоритм:
1. Данные изначально хранятся в protected
-переменной (кладутся туда контроллером через метод set()
или изначально лежат там — неважно). Ее не видно из шаблона. Пусть это будет переменная $_data
.
2. Создаем метод data()
, который будет перекладывать это все в нужный формат:
public function data() { $this->data = array(); foreach($this->_data as $username => $email) { $this->data[] = array('username' => $username, 'email' => $email); } return $this->_data; } |
3. В шаблоне обращаемся к переменной/методу data
для итерации.
Метод будет вызван только один раз, а далее (если понадобится) будет использована переменная $data
(по сути кэш работы метода data()
). Не очень приятные костыли, но что поделать…
В разработке — функции с параметрами
Я уже упоминал, что мы можем вызывать методы класса для получения данных, но никак не можем передать туда какие-либо значения. Например, для использования средств I18n в шаблоне (раньше мы могли писать echo __('Welcome')
, в Kostache ничего подобного пока нет). Но есть отдельные разработки, которые должны в ближайшем времени предоставить нам такую возможность. Вот пример, который у меня заработал с dev-веткой проекта Mustache:
В классе:
// в конструкторе объявляем наличие функции i18n() для вызова из шаблона public function __construct($template = null, $view = null, $partials = null) { $this->i18n = array($this, '_i18n'); parent::__construct($template, $view, $partials); } // это просто обертка для __() protected function _i18n($text) { return __(trim($text)); } |
Шаблон:
{{#i18n}} test {{/i18n}} |
В данной ветке методы вызываются раньше, чем переменные, поэтому назвать метод просто
i18n()
нельзя.
В итоге получим перевод слова ‘test‘. В переменной $i18n
можно также указывать статические методы, например ‘kohana::dump‘. Можно подкладывать другие значения, например {{#i18n}}{{foo}}{{/i18n}}
будет переводить значение переменной foo
(или результат работы метода foo()
). А можно совершать вложенные вызовы: {{#foo}}{{#bar}}{{var}}{{/bar}}{{/foo}}
, это удобно, например когда foo
- это свойство класса, содержащее объект с методом bar()
.
Но это так, лирическое отступление, чтобы показать, что у данного модуля все еще впереди.
Основные принципы (для закрепления)
- Переменные, которые не существуют, — игнорируются.
- Шаблон может содержать в себе другие представления, для этого используются либо вложения (partials) — они не имеют своих классов и наследуют переменные и настройки текущего класса, либо полноценные шаблоны, со своими значениями. Не забывайте объявлять вложения в свойстве
$_partials
класса. - Сперва шаблон пытается загрузить переменную из класса, и только потом ищет метод с таким именем.
- Прагмы можно указывать как в шаблоне, так и в классе. Но в первом случае они имеют бОльший вес.
- Экранирование включено по умолчанию. Если ожидаются уже отформатированные данные, которым можно доверять, отключайте экранирование для текущей переменной (с помощью тэгов) или для шаблона в целом (прагмой UNESCAPED).
—————————————-
В целом, шаблоны Kostache мне понравились, можно избавить свои шаблоны от многочисленных условий, проверочных переменных и т.д. Но функционала на данный момент не хватает для полноценной работы. В первую очередь жду реализацию лямбда-функций и нормальную обработку массивов.
Что только ни придумают люди, лишь бы не переходить на Ruby on Rails
спс за инфу!
PS: модуль облегчает работу дизайнерам, но усложняет программистам))
Yet another Smarty
А чем отличается от шаблонизатора, того же смарти например?
Я не фанат шаблонизаторов, но насколько я понимаю, в Смарти и прочих нет разделения на класс и шаблон. Т.е. контроллер тупо передает данные в шаблон, а дальше парсится шаблон. Тут все несколько сложнее, большая часть логики вынесена в класс представления, шаблон как бы облегчается.
С теми кто не хочет учиться- есть XSLT
Черт, кусок кода в предыдуем комменте злобно вырезан )
А я считаю, что дизайнер должен заниматься только дизайном и приготовлением макета, а верстальщик в свою очередь должен по макету сверстать. А вот порезать вёрстку на шаблоны и добавть туда echo, if, foreach должен всё таки программист.
Раньше обожал смарти, главным образом за его популярность, следовательно известных тегах, а также за его компилируемость, и малое время генерации страницы в сравнении с нативными шаблонами. В этом шаблонизаторе нет ни того, ни другого.
Что мы получим с виду красивый, но трудно читаемый шаблон. И прирост к генерации страницы, за счёт подгрузки кучи дополнительный файлов и выполнения кучи регулярок. Что лучше или {{var}}, стоит ли оно того?
Повторюсь, мне симпатичен подход Kostache в части облегчения как контроллера, так и самого шаблона. Контроллер передает в шаблон только объекты данных (модели), а работу с ними как правило должно производить представление (если говорить о представлении как полноценном члене банды MVC). Но ведь в шаблоне это как-то некрасиво и неправильно?
Насчет производительности не скажу. Не сомневаюсь, что стандартным kohanовским вьюшкам он уступает, возможно и смарти тоже. У него есть свои преимущества. По поводу нечитаемости даже не знаю. Насколько читабелен шаблон с кучей вставок php-кода? Синтаксис kostache достаточно прост, тут нет (пока) массы конструкций, только самое необходимое.
У меня есть самописный шаблонизатор, который с успехом применялся на многих сайтах, которые я делал. Написал его под свои требования, оптимизировал.
Но теперь я все чаще склоняюсь к обычному PHP-коду в HTML разметке. ИМХО, зачем усложнять себе жизнь? Выносить логику в отдельные классы, четко разграничивать сферу ответственности дизайнера и программиста.
Я думаю, что дизайнер не должен соваться в верстку — это дело верстальщика. Задача дизайнера только отслеживать результат: чтобы было «пиксель-в-пиксель». А вот верстальщика можно и научить базовому PHP синтаксису. Там ничего сложного нет.
а чем Kostache лучше XSL?
А кто сказал, что он лучше?
Не скажу, что близко знаком с XSLT, но вроде как для работы с ним надо подготовить данные соответствующим образом (трансформировать в XML). Кто будет этим заниматься — контроллер, модель? На самом деле это как бы не совсем их обязанности.
модель отвечает за работу с данными — значит, ей и заниматься. XML может браться либо прямо из базы (правда, майсикл тут не годится вроде), либо формироваться отдельным классом
Я, собственно, ничего не имею сказать против, просто спросил
Модель вроде логичнее — она ближе к данным. Но ведь по идее это уже форматирование выходных данных, этим должно View заниматься. Особенно если одни и те же данные можно вывести разными способами (xml, json и т.д.)
ой, не знаю, чёт мне пока хорошо живется, как описано в первых строках статьи
В результате шаблоны выглядят как сборная солянка из HTML-кода и PHP-вставок (а некоторые туда еще и JS впихивают).
некоторые разработчики используют XSL шаблоны внутри проекта, спомощью спец. расширения php шаблоны и данные приводятся к html. Имхо всё это большая глупость, убивается главная фишка XSL.
Вообще если использовать XSL шаблоны, view MVC не нужен, т.к. шаблоны компилируются на стророне клиента. А значит контроллеру просто нужно отдавать юзеру xml, в нём будет указан шаблон, в самом жаблоне может быть подгружен другой шаблон или xml… и т.д. Шаблоны кешируются на староне клиента, и человек дёргает лишь xml-ки по разным роутам.
XSL по сути и есть V, в одной из разновидностей MVC, которую некоторые называют VMC, т.е. роутинг вызывает обращается на прямую к одному или нескольким контроллерам, и подгружаться все другие View.
Всё в общем супер, но есть куча времени. Меня всегда затраты на производство XSL отпугивали…
Случайно наткнулся на такое вот извращение: http://phphaml.sourceforge.net/
Теоретически это же ведь прикручиваемо к кохане?
Теоретически все можно прикрутить к Kohana
Судя по многочисленным обсуждениям на форуме, этот шаблонизатор достаточно проблемный для структуры kohana.
Но дело в другом…
Почему тема использования шаблонизаторов так волнует вебразработчика?
И почему тогда прогресс в этом отношении повернул на каком-то этапе в обратном направлении?
Неужели так остро стоит вопрос облегчения труда дизайнера/верстальщика?
Тогда всерьез нужно обратиться к чему-то, не просто новому, а более-менее передовому, прогрессивному.
К примеру, есть js-фреймворки (TITAN, например), которые построены для работы с данными в формате JSON…
Получаем все, что нужно с сервера и просто заселяем этим совершенно свободный от всякой логики, не-html разметки, и прочих всяких крякобразов шаблон!
Как раз ваш сайт понадобился, прохожу коробочную версию webguru, как раз будем проходить кохану, так что в закладки.