Как я перешел со Smarty на Twig

:

Раньше я был ярым поклонником Smarty. У Smarty достаточно много достоинств, он распространен, с ним просто, он привычен и так далее. Но так вышло, что для одного из проектов Smarty оказался слишком уж тяжелым и слегка тормозным. Я не говорю, что Smarty плох или что он негодный, нет. Просто в некоторых условиях его производительность оказалась недостаточной, и надо было искать альтернативу. Альтернатива нашлась и я очень рад, что мне выпала возможность работать с Twig.

Smarty был второй версии, в комментариях верно замечают, что третья версия имеет отличия и часто значительные.

В первую очередь я попытался подстроить Twig под синтаксис Smarty, так как тот мне был привычен, понятен и приятен. Сделать это можно, пускай и не в полной мере. Вот так:

$loader = new Twig_Loader_Filesystem('/templates');
$twig = new Twig_Environment($loader,array('charset'=>' utf -8','cache'=>'/templ_c','auto_reload'=>true));			
$escaper = new Twig_Extension_Escaper(true);
$twig->addExtension($escaper);

$lexer = new Twig_Lexer($twig, array(
  'tag_comment'  => array('{*', '*}'),
  'tag_block'    => array('{', '}'),
  'tag_variable' => array('{$', '}'),
));
$twig->setLexer($lexer);

При инициализации объявляем в массиве tag_comment, tag_block и tag_valiable. Теперь можно как в Smarty выводить переменные. При переходе достаточно удобно, но сейчас я понимаю, что лучше было бы этого не делать. Синтаксис становится неоднородным.

К некоторым вещам все же пришлось привыкать и некоторое время заглядывать в документацию, чтобы ничего не напутать. Например обход массивов

{for category in category_tree}
{$category.name}
{endfor}

Вместо
{foreach from=$category_tree item=category}
{$category.name}
{/foreach}

Обратите внимание, что в Twig не смотря на переопределение тэга переменной знак доллара перед именем переменной не ставится. Если бы не переход со Smarty — было бы удобнее.

Блоки закрываются не {/if} а {endif}. Тоже по началу путаешься.

Или, например, блоки которые не надо обрабатывать шаблонизатором.
{raw} {endraw} вместо {literal}{/literal}.

Все это мелкие подстройки и связанная с переходом адаптация. Скорее всего это даже усложнено тем, что синтаксис похож.

К чему я привык работая со Smarty?

Во-первых это структура шаблонов. Шаблоны дробились на шапку, основное тело и подвал. Также боковые блоки и другие блоки тоже иногда выделялись. Дальше все это дело подключалось в основном шаблоне в порядке следования. Теперь я понимаю, что это совсем не круто. Наследование – вот это круто! Страницы сайта зачастую построены по одному принципу, либо есть несколько типов страниц со своей организацией. Шапка/подвал везде одинаковая зачастую, а вот левое/правое меню может скакать то влево, то вправо, либо на части страниц его может не быть вовсе. В Smarty я бы стал городить условия и подключать нужный шаблон, в нем подключать еще один шаблон и так далее. В Twig все иначе. Здесь, как и в ООП шаблоны наследуются друг от друга, можно переопределять части шаблонов. Это просто здорово. Движение фактически в другую сторону – не от главного шаблона к дочерним, а отображая конечный шаблон он собирается из родителей их родителей и так до корня.
Строим основной шаблон с шапкой/подвалом, можно даже боковыми блоками, а потом наследуем от него другие шаблоны, более низкого уровня. В них запросто переопределяем боковой блок. Например, можно от основного шаблона унаследовать шаблон с левым меню и шаблон с правым меню. Что там выше, кто от кого наследуется — дело только тех, кто выше. А потом в зависимости оттого, что нам требуется – наследовать конечный шаблон либо от шаблона с правым меню, либо от шаблона с левым меню. А потом можем передумать и унаследовать шаблон не от шаблона левого меню, а от шаблона правого меню. Такая простота, прозрачность и гибкость меня очень радует. Даже кардинальные изменения даются легко. Немного кода.

Основной родительский шаблон.

main.tpl

<!doctype html>
<head>
 <title>{block html_title}Заголовок по умолчанию{endblock}</title>
</head>
<html>
<div class=”header”>Содержимое шапки</div>
<div class=”body”>
	{block content}
		Здесь ничего нет. {*Если мы не переопределим содержимое блока, то страница все равно не будет пустой.*}
	{endblock}
</div>
<div class=”footer”></div>
</html>

Теперь напишем страницу просто без меню.
left_menu.tpl
{extends "main.tpl "}
{block html_title}Главная страница{endblock} {*Переопределяем заголовок страницы*}
{block content}
 	Тут содержимое главной страницы. Это содержимое заменит определенный в родительском шаблоне текст.
{endblock}

А теперь шаблон меню с левой стороны.
left_menu.tpl
{extends "main.tpl "}
{block content}
<div class=”left_menu”>
{*Выводим список каких-то категорий*}
{for category in category_tree}
<a href=”?{$category.id}”>{$category.name}</a>
     {endfor}
</div>
<div class=”content”>
 {block page_content} {* Обратите внимание, блок не может называеться content, так как такой блок уже есть. *}
 	Здесь ничего нет. 
 {endblock}
</div>
{endblock}

И конечную страницу
{extends "left_menu.tpl "}
{block html_title}Конечная страница{endblock}
{endblock}
{block page_content}
 Содержимое страницы с меню. На этой странице переопределен заголовок страницы, заданный в самом первом, основном шаблоне.
{endblock}

В итоге конечные шаблоны не загромождаются подключениями, условиями и всем остальным. Просто говорим от чего наследуем шаблон, а дальше только по сути.

Работая со Smarty, я привык к тому, что надо передавать все переменные в шаблон и уже там их выводить, вся обработка была в коде. Часто даже форматирование приходилось делать до шаблонизатора, в итоге выходила мешанина. В Twig можно вообще управлять логикой исполнения из шаблона, без каких либо телодвижений. Если в выводе будет не название поля, а название метода, то в этом месте будет выведено возвращаемое методом значение. Т.е. если нам надо вывести какое-то подсчитываемое значение, то его не нужно подсчитывать заранее, это будет произведено непосредственно при выводе.

// Класс пользователя
class User
{
	public function free_space()
	{
		// Что-то вычисляем, проверяем наличие свободного места.
		Return $result;
	}
}

В шаблоне вызываем:
{$user.free_space} 

И все. Не надо об этом заранее думать – все отработает и отработает быстро. Неприятным моментом было то, что если поле класса приватное, то выводить twig его не станет, даже если в классе описан геттер.

И третье это написание собственных расширений. Часто бывают нужны какие-то функции, не реализованные в шаблонизаторе. Расширяется twig крайне просто. Например нам нужно типографить вывод. Выборочно.

class My_Twig_Extension extends Twig_Extension
{
  
// Этот метод должен быть объявлен обязательно.	
  public function getFilters()
  {
    return array(    
    	'typograph => new Twig_Filter_Method($this, 'typograph')    
    );	
  }

  public function typograph($text)
  {
  	return $typograph->parse($text); // тут собственно типограф работает с передаваемым ему текстом.
  }
	
}

И подключаем его на этапе инициализации.
$twig->addExtension(new My_Twig_Extension());

Теперь шаблоне для тех переменных которые нам надо обработать – просто дописываем модификатор – {$page.content|typograph} и все, значение будет обработано. Не нужно сначала перебирать все значения до передачи их шаблонизатору и обрабатывать. Шаблонизатор все равно будет обходить массив для выхода — так это делается один раз, а не два. Расширение можно написать одно и потом дополнять его нужными функциями.

После Twig-a в сторону Smarty смотреть не очень хочется. Как то уж слишком легко и логично все получается вместе с ним.