Контент


Знакомьтесь — Kostache

Если вы читали статьи про 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 мне понравились, можно избавить свои шаблоны от многочисленных условий, проверочных переменных и т.д. Но функционала на данный момент не хватает для полноценной работы. В первую очередь жду реализацию лямбда-функций и нормальную обработку массивов.

Google Bookmarks Digg Reddit del.icio.us Ma.gnolia Technorati Slashdot Yahoo My Web News2.ru БобрДобр.ru RUmarkz Ваау! Memori.ru rucity.com МоёМесто.ru Mister Wong

Опубликовано в Kohana3.

Теги: , , .


Комментарии (20)

Будьте в курсе обсуждения, подпишитесь на RSS ленту комментариев к этой записи.

  1. Eugene RIMMER пишет:

    Что только ни придумают люди, лишь бы не переходить на Ruby on Rails

  2. php code пишет:

    спс за инфу!
    PS: модуль облегчает работу дизайнерам, но усложняет программистам))

  3. Slaver пишет:

    Yet another Smarty :)

  4. ButscH пишет:

    А чем отличается от шаблонизатора, того же смарти например?

  5. biakaveron пишет:

    Я не фанат шаблонизаторов, но насколько я понимаю, в Смарти и прочих нет разделения на класс и шаблон. Т.е. контроллер тупо передает данные в шаблон, а дальше парсится шаблон. Тут все несколько сложнее, большая часть логики вынесена в класс представления, шаблон как бы облегчается.

  6. Shaman пишет:
    Если дизайнер верстает - выучить пару магических конструкций типа 
    
    для него не проблема. 

    С теми кто не хочет учиться- есть XSLT :)

  7. Shaman пишет:

    Черт, кусок кода в предыдуем комменте злобно вырезан )

  8. alkhankhel пишет:

    А я считаю, что дизайнер должен заниматься только дизайном и приготовлением макета, а верстальщик в свою очередь должен по макету сверстать. А вот порезать вёрстку на шаблоны и добавть туда echo, if, foreach должен всё таки программист.

    Раньше обожал смарти, главным образом за его популярность, следовательно известных тегах, а также за его компилируемость, и малое время генерации страницы в сравнении с нативными шаблонами. В этом шаблонизаторе нет ни того, ни другого.
    Что мы получим с виду красивый, но трудно читаемый шаблон. И прирост к генерации страницы, за счёт подгрузки кучи дополнительный файлов и выполнения кучи регулярок. Что лучше или {{var}}, стоит ли оно того?

  9. biakaveron пишет:

    Повторюсь, мне симпатичен подход Kostache в части облегчения как контроллера, так и самого шаблона. Контроллер передает в шаблон только объекты данных (модели), а работу с ними как правило должно производить представление (если говорить о представлении как полноценном члене банды MVC). Но ведь в шаблоне это как-то некрасиво и неправильно?

    Насчет производительности не скажу. Не сомневаюсь, что стандартным kohanовским вьюшкам он уступает, возможно и смарти тоже. У него есть свои преимущества. По поводу нечитаемости даже не знаю. Насколько читабелен шаблон с кучей вставок php-кода? Синтаксис kostache достаточно прост, тут нет (пока) массы конструкций, только самое необходимое.

  10. Бубнов Славик пишет:

    У меня есть самописный шаблонизатор, который с успехом применялся на многих сайтах, которые я делал. Написал его под свои требования, оптимизировал.

    Но теперь я все чаще склоняюсь к обычному PHP-коду в HTML разметке. ИМХО, зачем усложнять себе жизнь? Выносить логику в отдельные классы, четко разграничивать сферу ответственности дизайнера и программиста.

    Я думаю, что дизайнер не должен соваться в верстку — это дело верстальщика. Задача дизайнера только отслеживать результат: чтобы было «пиксель-в-пиксель». А вот верстальщика можно и научить базовому PHP синтаксису. Там ничего сложного нет.

  11. Alexander Kupreev пишет:

    а чем Kostache лучше XSL?

  12. biakaveron пишет:

    А кто сказал, что он лучше? :)
    Не скажу, что близко знаком с XSLT, но вроде как для работы с ним надо подготовить данные соответствующим образом (трансформировать в XML). Кто будет этим заниматься — контроллер, модель? На самом деле это как бы не совсем их обязанности.

  13. Alexander Kupreev пишет:

    модель отвечает за работу с данными — значит, ей и заниматься. XML может браться либо прямо из базы (правда, майсикл тут не годится вроде), либо формироваться отдельным классом

    Я, собственно, ничего не имею сказать против, просто спросил :)

  14. biakaveron пишет:

    Модель вроде логичнее — она ближе к данным. Но ведь по идее это уже форматирование выходных данных, этим должно View заниматься. Особенно если одни и те же данные можно вывести разными способами (xml, json и т.д.)

  15. Максим Нагайченко пишет:

    ой, не знаю, чёт мне пока хорошо живется, как описано в первых строках статьи

    В результате шаблоны выглядят как сборная солянка из HTML-кода и PHP-вставок (а некоторые туда еще и JS впихивают).

  16. Мимошёл пишет:

    некоторые разработчики используют XSL шаблоны внутри проекта, спомощью спец. расширения php шаблоны и данные приводятся к html. Имхо всё это большая глупость, убивается главная фишка XSL.
    Вообще если использовать XSL шаблоны, view MVC не нужен, т.к. шаблоны компилируются на стророне клиента. А значит контроллеру просто нужно отдавать юзеру xml, в нём будет указан шаблон, в самом жаблоне может быть подгружен другой шаблон или xml… и т.д. Шаблоны кешируются на староне клиента, и человек дёргает лишь xml-ки по разным роутам.

    XSL по сути и есть V, в одной из разновидностей MVC, которую некоторые называют VMC, т.е. роутинг вызывает обращается на прямую к одному или нескольким контроллерам, и подгружаться все другие View.

    Всё в общем супер, но есть куча времени. Меня всегда затраты на производство XSL отпугивали…

  17. erema пишет:

    Случайно наткнулся на такое вот извращение: http://phphaml.sourceforge.net/
    Теоретически это же ведь прикручиваемо к кохане?

  18. biakaveron пишет:

    Теоретически все можно прикрутить к Kohana :)

  19. Zares пишет:

    Судя по многочисленным обсуждениям на форуме, этот шаблонизатор достаточно проблемный для структуры kohana.
    Но дело в другом…
    Почему тема использования шаблонизаторов так волнует вебразработчика?
    И почему тогда прогресс в этом отношении повернул на каком-то этапе в обратном направлении?
    Неужели так остро стоит вопрос облегчения труда дизайнера/верстальщика?
    Тогда всерьез нужно обратиться к чему-то, не просто новому, а более-менее передовому, прогрессивному.
    К примеру, есть js-фреймворки (TITAN, например), которые построены для работы с данными в формате JSON…
    Получаем все, что нужно с сервера и просто заселяем этим совершенно свободный от всякой логики, не-html разметки, и прочих всяких крякобразов шаблон!

  20. php-user пишет:

    Как раз ваш сайт понадобился, прохожу коробочную версию webguru, как раз будем проходить кохану, так что в закладки.



Можно включить подсветку кода: <code><pre lang="">...</pre></code>
Разрешены некоторые HTML теги

или используйте trackback.