Если вы читали статьи про 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, как раз будем проходить кохану, так что в закладки.