PHP

индекс
206,76

MACRO — гибкий PHP шаблонизатор, с человеческим «лицом»

Раз уж сегодня на хабре день РНР шаблонизаторов, то не могу не рассказать о MACRO — наиболее гибком шаблонизаторе с читаемыми шаблонами, среди известных мне.

Причина его создания проста — используемый нами в то время WACT, становился все более монструозным. Подробнее о причинах и первоначальной идее можно прочитать у нас на форуме.

Основные «фишки»


Гибкость


Внутри можно легко использовать обычные php-вставки, а сам шаблонизатор содержит очень небольшой набор правил своего использования. Это дает нам очень гибкий инструмент, с поддержкой, как pull, так и push доступа к данным.

Высокий реюз шаблонов


Мощные средства для компоновки шаблонов: обворачивание(wrap), включение(include), переиспользование(apply) в рамках одного и того же шаблона. Примеры шаблонной магии можно посмотреть в вики проекта. Благодаря использованию MACRO, нам удалось полностью избавиться от дублирования в шаблонах.

Скорость


Код первоначального шаблона сначала переводится в готовый к запуску php-скрипт, причем шаблон компилируется целиком, поэтому композиция (разбиение шаблона на части, обворачивание и т.д. ) почти никак не влияет на скорость исполнения шаблона, так как он собирается в одно целое(класс) при компиляции, а область видимости(контекст) организуется с помощью методов скомпилированного класса. То есть большинство «тяжелых» операций либо выполняются на стадии компиляции, либо используют встроенные в РНР средства, что позволило добиться хороших «скоростных» показателей.

Расширяемость


Легкость добавления своих тегов и своих фильтров. А также возможность создавать целые пакеты со своими тегами или фильтрами.

Модифицируемость


Гибкость настраивания и легкость допиливания достигается за счет низкого связанности компонентов и адекватного ООП.

Читаемость


Шаблоны, несмотря на всю гибкость, нормально читаются непрограммистами, после их неглубокого зомбирования.

Немного дегтя


MACRO по сути является syntactic sugar для нативного РНР. И если вы не платите верстальщику зарплату уже три месяца, то он вполне может загубить весь сайт, имея доступ только к шаблонам.

Show me the code!


Пример шаблона:
{{insert into="content_zone" file="page.phtml"}}
<img src={$#photo.largeFileUrl} />
<dl>
<dt>Автор:</dt><dd>{$#photo.member.name}</dd>
<dt>Категория:</dt><dd>{$#photo.category.title}</dd>
<dt>Название:</dt><dd>{$#photo.title}</dd>
<dt>Теги:</dt>
<dd>
{{list using='{$#photo.tags}' as='$tag'}}
 <ul>
 {{list:item}}
  <li>{$tag.title|uppercase}</li>
 {{/list:item}}
 </ul>
 {{list:default}}
 Нет тегов
 {{/list:default}} 
{{/list}}
</dd>
{{insert file="photo/marks.phtml"/}}
</dl>
{{/insert}}


* This source code was highlighted with Source Code Highlighter.


Тэги


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

MACRO содержит достаточное большое количество тегов, которые позволяют выводить списки, компоновать шаблоны, разделять списки на страницы и выводить пейджеры, работать с формами и так далее.

Выражения


Выражения (output expressions) используются для вывода каких-либо значений переменных. Выражения в нашем примере, это {$tag.title|uppercase} и {$#photo.largeFileUrl}. Выражения, стоящие в шаблонах — это по сути операции echo. Точкой разделяются части пути до выводимой переменной. Выражение {$tag.title} эквивалентно <?php if(isset($tag['title'])) echo $tag['title']; ?>.

Подробно выражения описаны в разделе "Выражения".

Фильтры


Фильтры используются для модификации/форматирования значений, выводимых в выражениях. Выражения с фильтрами в нашем примере, это {$tag.title|uppercase}. Фильтр uppercase — переводит значения в верхний регистр. По-сути это алиас на php-функцию strtoupper, которая применяется для переменной, указанной в выражении.

Обычно фильтр представляет из себя враппер для какой-нибудь часто используемой функции php. Однако, ничего не мешает создавать свои уникальные фильтры, так как делать это достаточно просто. Например, несколько дней назад мне довелось добавить фильтр склоняющий существительные в зависимости от числа (1 человек, 2 человека и далее).

Немного о скорости


Разрабатывать шаблонизатор с прицелом на высокую скорость не имеет смысла без набора тестов. Если кратко, то на верстке, близкой к «боевой», MACRO медленнее чистого РНР в 1,75 раза, но быстрее ближайшего «человеческого» шаблонизатора (smarty) на треть.

Подробные результаты тестов можно посмотреть(и скачать) на соответствующей странице.

_________
Текст подготовлен в ХабраРедакторе
+22
23 ноября 2008, 18:45
51

комментарии (96)

+5
ra26info #
Smartyклон?! :) «Show me the code!» похоже на смарти
+3
ra26info #
в целом интересно, хотя давно не использую такого рода шаблонизаторы, именно по тому, что «медленнее чистого РНР».
–1
korchasa #
К сожалению pure PHP шаблоны плохочитаемы.
–2
ionicman #
Абсолютно нормально читаемы. или {} лучше чем <? ?> ?? Это смешно ;)
+1
Bal #
> или {} лучше чем <? ?>

{$val} содержит меньше синтаксического мусора, чем <? echo $val; ?>, а уж тем более, чем у <? echo $this->val; ?>

Когда таких параметров становятся десятки, в одном случае мы видим аккуратный HTML с вставками, в другом — тонну закорючек :)



Мне, как бы, сейчас приходится и на Smarty шаблоны использовать, и на PHP, благо, фреймворку моему пофиг, какой вид юзать, использование унифицировано. Так вот, Smarty — на голову компактнее и нагляднее. И PHP используется только когда или уж совсем скорость критична, или когда логика очень сложная и 3/4 шаблона — из кода состоит :D



А так — надо будет сабж прикрутить, оценить. Синтаксис там чуть более громоздкий, чем у Smarty, но терпимый. М.б. некоторые компоненты с большим числом вложений, на него переведу, если эффективен окажется.
+3
ionicman #
Да? а <?=$somthing ?>

попробуйте — работает ;)
–2
Bal #
Век живи, век учись, блин :D Спасибо, ловите плюс :)
+3
BisnoyZakat #
Насколько я знаю использование этого метода не рекомендуется.
0
psylosss #
Совершенно верно.
0
Goodkat #
кем не рекомендуется?
я про этот метод прочёл в документации к Zend Framework
0
Fally #
framework.zend.com/manual/ru/coding-standard.coding-style.html
Раздел: «B.4. Стиль кодирования. B.4.1. Обрамление PHP-кода»
Цитата:
" PHP-код должен всегда обрамлятся полными PHP-тегами:
<?php

?>
Короткие теги не допустимы. "
0
Goodkat #
эм…
вот пример использвоания:
framework.zend.com/manual/de/zend.form.quickstart.html#zend.form.quickstart.render

в самом низу, лучше поиском "<?="

собссно, из этого примера я и узнал о короткой версии тега

странно, что в русской версии они пишут о недопустимости, а в немецкой приводят в качестве примера
0
roller #
слишком много русских быдлокодеров развелось
0
Fally #
А посмотрите эту же страницу на английском языке (ему я доверяю больше, чем другим, ибо оф. документация написана на нём) и на том же русском, и обратите внимание, что в том месте где в немецком варианте короткие теги, в анлийском и русском вариантах короткие теги не используются…

Offtop… за что минусовали не понял… ссылку же дал на оф. документацию…
0
Goodkat #
на русском:
===== begin cut =====
… и скрипт вида для отображения формы:

<h2>Please login:</h2>
<?= $this->form ?>

Как вы наверное заметили, код контроллера не является полным
===== end cut =====

на английском:
===== begin cut =====
And a view script for displaying the form:

<h2>Please login:</h2>
<?= $this->form ?>

As you'll note from the controller code, there's more work to do
===== end cut =====
+1
ionicman #
Если уж делать шаблонизатор не нативный — то, по моему мнению, лучше делать это на том, что для этого предназначено — на XSLT, ан егородить велосипед. А если хочется чегото простого — то тогда юзать натив PHP
0
Bal #
У XSLT синтаксис ещё страшнее :) А так — да, никто не мешает и его использовать.
–1
AxisPod #
pure PHP себя отлично показывает и быстро и наглядно (все зависит от того как оформлять), хорошо подходит альтернативный синтаксис структур управления

и также для других команд, да и расширяемость лучше, главное не путать бизнес логику и отображение.
–1
roller #
он не альтернативный, он — устаревший
0
tenshi #
pastebin.com/f6f42f82b
0
Bal #
[input name=«xxx» value="[?=htmlspecialchars($xxx);?]" /]
+1
tenshi #
а с переводами строк что делать? а с кавычками? =)
впрочем, проблема не только в этом, а ещё и в том, что шаблон получается невалидным xml => его труднее воспринимать, а подсветка синтаксиса сходит с ума…
0
Bal #
>а с переводами строк что делать? а с кавычками? =)

?

php -r 'echo htmlspecialchars("qqq\"qqq\nqqq");'
qqq"qqq
qqq


>а ещё и в том, что шаблон получается невалидным xml => его труднее воспринимать

Хм. Как валидность xml влияет на восприятие? :) А в браузере уже всё валидно будет.

> а подсветка синтаксиса сходит с ума…

Редакторы надо нормальные использовать :)
+1
tenshi #
! ну и поедет у тебя вся вёрстка, ибо в аттрибутах кавычки и переводы тоже должны экранироваться, а не только угловые скобочки да амперсандики.

угловые скобки рябят в лазах.

и какой же редактор понимает инструкции препроцессору внутри аттрибутов?
0
Bal #
>ну и поедет у тебя вся вёрстка, ибо в аттрибутах кавычки и переводы тоже должны экранироваться, а не только угловые скобочки да амперсандики.

Мы, наверное, друг друга недопонимаем.

>и какой же редактор понимает инструкции препроцессору внутри аттрибутов?

Требуется уточнение:
— Откуда вдруг взялся препроцессор, что ты под ним понимаешь?
— Что ты понимаешь под «пониманием редактором»? Тут возможно двоякое поведение. Редакторы, типа mcedit в этом случае тупо подсвечивают строку как строку. Редакторы в массе своей, от vim до kdevelop подсвечивают в этом случае в строке php-синтаксис. Что для тебя более правильно? :)
0
tenshi #
pastebin.com/f3e2d5100

www.w3.org/TR/REC-xml/#sec-pi

правильное — подсвечивать ошибки, в частности: угловые скобки внутри атрибута — грубейшая ошибка.
0
Bal #
Давай мух отдельно, а котлеты — отдельно.

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

Если используешь в качестве шаблона PHP — наверное, ты знаешь, что делаешь. И это, как раз, не тот случай, за который я ратую :) Но даже в этом случае проблема не встаёт. Шаблоны — на то и шаблоны, чтобы использовать их декомпозитивно. И уж на такую фигню, как угловую скобку внутри параметра, можно забить. Всё равно проверять на валидность можно только конечный результат, а не шаблон.
0
korchasa #
Нет, не клон. Именно из-за того, что WACT стал похходить на smarty и пришлось от него отказаться.
НЛО прилетело и опубликовало эту надпись здесь
+5
AstralMan #
Лучший шаблонизатор это PHP =)
0
korchasa #
MACRO потому так и называется, что является сахаром для нативного РНР ;)
+2
Sulako #
перед такими заявлениями определяйте критерий лучшести :)
+1
phpdude #
а каково время генерации страниц?
+2
phpdude #
+ вопрос. поддежрживаются ли вложенные ключи, например

{$users[$get.userid].username} ??
+1
Ueasley #
И есть ли там какие-нибудь итераторы по массивам? (php-вставки — зло)
+1
phpdude #
почему зло? объясните пожалуйста :)
+1
Ueasley #
Если работаете только вы — вполне нормально.

Но верстальщики почему-то начинают бояться шаблона, когда видят в нем php-код, и лезут с кучей вопросов перед тем, как начать что-то делать (это не мой опыт, мне рассказывали).
0
phpdude #
я бы тоже постеснялся начать верстать. а вы в шаблоны пхп код не кладите и будет счастье.

кстати, итератор то там есть явно {foreach я думаю называется, если нет, то шблонизатор — УГ.
0
Ueasley #
Так а я о чем? PHP-вставки в шаблон — зло.

Или вы не об этом спрашивали?

Мой первый вопрос был дополнением к вашему, если что :-)
0
phpdude #
ах… я подумал про нативный — УГ ИМХО:(
0
korchasa #
По опыту — не начинают, если им объяснить, что на php-вставки просто не надо обращать внимания. Расстановка этих вставок — дело программиста.
0
korchasa #
Просто у нас это только pull-данные:

<?php $articles = ArticleDao::findForFront(); ?>

0
caezar #
а разве это не работа контроллера?
+1
korchasa #
Существуют понятия «активный шаблон» и pull-данные. Очень удобная штука во многих случаях, например, облако тэгов, которое отображается на каждой странице.
+1
zerkms #
работа контроллера — получить модели, получить отображения. и рассказать последним о первых (академически).
0
Bal #
>верстальщики почему-то начинают бояться шаблона, когда видят в нем php-код, и лезут с кучей вопросов перед тем, как начать что-то делать (это не мой опыт, мне рассказывали)

Подтверждаю этот опыт личной практикой :)
0
korchasa #
Да, конечно, тэг лист
0
korchasa #
Нет, ибо чаще всего это (как и в вашем примере) не уровень представления.
–1
phpdude #
да, смарти со своим широким синтаксисом испортил понятие «шаблонизатор» как таковое ((

хотя вот давайте порассуждаем.
xslt — шаблонизатор? с этим не поспоришь, но там же можно делать условные выборки

0
korchasa #
А можно ссылку на определение «шаблонизатора»? ;)

xslt — это вполне шаблонизатор. Молоток не перестает быть молотком, если к нему приделать сбоку пистолет.
0
phpdude #
простите, у меня отправился камент и отстрипился пример))

вот пример

[xsl:variable name=«userid» select="//get/userid"/]
[xsl:value-of select="//users[id=$userid]/@name" /]

по идее должно сработать, уверен на 99%. вы дальше думаете, что это не уровень представления??)))

я согласен что это не уровень представления, но шаблонизатор — это str_replace был в 98ом году, у нас 21 век и потребности увы другие, надо гибче решения. иначе вы тупо плодите код, который может даже не понадобиться, например ну и определите вы это, профоричите половину переменных, преобразуете, а в новом дизайне они не будут использоваться, лишняя нагрузка на бизнес логику, груда кода, и просто простите, помойка.
0
korchasa #
Если уж так захотелось:
<?php $selected_user = $users[$get->userId()] ?>
{$selected_user.name}
Я использую macro уже больше года, и не помню, чтобы у меня возникли проблемы с динамическими вложенными ключами. Приведенный код вообще страшен, т.к. если в get не будет userId, или в $users не будет такого элемента, то плохо.
0
phpdude #
будет или нет это уже дело бизнес логики ))

проще уж тогда полностью)

0
phpdude #
простите камент ушел не по моей воле (

[?=$users[$get->userId()]['name'] ?]

тогда уж так проще)

а насчет будет ли там юзер — дело бизнес логики)

я же не говорю про юзеров, я от балды и взял, например категория в списке категорий в поисковой форме. она то уж точно будет, если пришел пост и форм сработала.
0
korchasa #
Как я написал выше — это не такая частая потребность, чтобы переписывать парсер.
0
phpdude #
а говорите ООП……… зачем же переписывать то, если ООП хорошее

«Гибкость настраивания и легкость допиливания достигается за счет низкого связанности компонентов и адекватного ООП.»

это так, критика ради критики, дело конечно ваше :)
0
korchasa #
ну не надо к словам придираться. Не переписывать, а дописывать. Один класс — lmbMacroExpression
–1
phpdude #
ну ладн :)) я же говорю критика ради критики :)
+2
phpdude #
можно посмотреть как вы выполняете тесты шаблонизаторов? хочу попробовать свой прогнать, сравнить с вашим) мб ваш буду юзать)
0
korchasa #
https://svn.limb-project.com/limb/misc/template_engines_bench/
0
korchasa #
Все зависит от машинки и немного от сложности шаблона. На моей домашней машине:
korchasa@korchasa:/www/localhost/korchasa-bench$ cat /proc/cpuinfo
processor: 0
vendor_id: AuthenticAMD
cpu family: 6
model: 8
model name: Unknow CPU Type
stepping: 1
cpu MHz: 1350.114
cache size: 256 KB
с включенным APC, в забандленной версии — 150-160 rps.
+1
Ueasley #
Не знаю, привык к Smarty.
+3
phpdude #
интересно чо за пидрила всех отминусовал? гавнодрищ, выйди из тени, морду будем бить.
0
phpdude #
чудеса. НЛО чтоли %)
+1
III #
Кто-то не любит PHP?=)
–6
tenshi #
интересно чо за пидрила всех заплюсовал? гавнодрищ, выйди из тени, морду будем бить.
+1
egorinsk #
phpdude, Вы перелогиниться забыли (http://habrahabr.ru/blogs/php/45311/#comment_1144725)
0
phpdude #
в смысле? у меня один аккаунт если вы про это.
–4
phpdude #
любитель жанго подошел, сичас будет холивар!!! жанго рулитааааа ааа жанго жангоааа бу==ээээ
+1
selitskas #
Хм. Интересно, думаете, стоит менять в и так тормознутом проекте smarty (который тормозит кроме всего прочего вывод страницы до 1.5 сек) на MACRO? Или только потеря времени?
0
korchasa #
Smarty ощутимо сильнее тормозит при увеличении вложенности шаблонов, из-за раздельной компиляции, и копировании контекста. Ничего этого у нас нет, потому если проводить тесты на «боевых» шаблонах с вложенностью, хотя бы, в 3-4 уровня, то smarty будет угрюмо плестись в хвосте.
0
studentpm #
Честно говоря не вижу смысла в использовании шаблонизаторов. Читаемость шаблонов не аргумент (некоторые и среиалайз строки читают на ура).
Увеличение производительности — тоже не будет при использовании того же eAccelerator.

По мне так лучше семантическая(наглядная) верстка и php вставки(нативный шаблон).
0
korchasa #
Читаемость строк это как раз — главный аргумент. Правки в нативном РНР тупо дольше делать. Всякие хэлперы (например, Zend'овские) улучшают читаемость, но это все равно «кашица».
0
studentpm #
А чем <?=$myVar;?> отличается по читабельности {$myVar}… скобочками?
0
korchasa #
{$myVar} это не <?=$myVar;?>, а <?=htmlspecialchars($myVar);?>. Но это простой пример. Чем сложнее, тем хуже читать pure PHP:
{$#book→getAuthor().full_name}

0
korchasa #
Извиняюсь, сообщение порезалось:

{$item.title}

<?=(isset($item['title'])? htmlspecialchars($item['title']): '';?>

А дальше все хуже и хуже…
0
studentpm #
Вот хоть убей, но обработкой должен заниматься контроллер а не представление.
Опять же имхо.
0
korchasa #
Обработкой чего?
0
studentpm #
Данных.
0
korchasa #
На вкус и цвет. Нам это проще делать в шаблоне, благодаря самому macro, чем раздувать контроллеры.

Лучше пусть верстальщик напишет {$text|nl2br}, чем он полезет в контроллер.
+2
zerkms #
должен? с какой стати?
откуда контроллер будет знать, что для конкретно этого отображения для sanitize будет нужно htmlspecialchars (html, браузер), а для другого — консоль/ncurses не нужно?
0
korchasa #
Ой зря ты это начал… ;)
0
zerkms #
хм… а мне кажется, что не особо-то и попрёшь против этого высказывания… хотя — посмотрим :-)
0
korchasa #
Я? Нет, конечно. Во-первых я всеми двумя руками за минимизацию контроллеров. А во-вторых мы их в CLI не используем.

Обычно, если мне нужно и cli и html, то я пишу reporter'ы. Хотя идея с контроллерами интересная.

Сейчас как раз пишу генератор моделей/контроллеров/etc. Там то либо command, либо controller. И, в принципе, мне нужно поддерживать оба интерфейса(cli, html). Может и получится объединить их. Пока только не очень понимаю, как абстрагироваться от того, как приходят параметры. Можно конечно все свести к lmbHttpRequest, но как-то это не красиво.
+1
zerkms #
да какая разница, CLI был как пример. если показалось слишком отдалено от реальности, то: обычный хтмл для браузера и rss. контроллер один, модель одна, отображения и санитайзы разные.
0
Bal #
Обработкой данных должен заниматься контроллер. Но он не должен заниматься шаблонизацией.

Если в зависимости от условия у тебя то жирным, то курсивом текст выделяться должен, то совать strong или em в контроллер — это несравнимо больший грех, чем if'ы в шаблон :)
0
phpdude #
количеством символов, в {$myVar} их на 4 меньше. отсюда — лаконичность.

такие примеры не интересные, давайте я напишу на смарти примерчик, а вы на пуре.

{$smarty.get.category|default:«выберите категорию»|upper|escape}

а на пхп? :)
0
studentpm #
Ну если на прямую то вот:
<?=(empty($_GET['category'])? 'выберите категорию': strtoupper(htmlspecialchars($_GET['category'])));?>

А при грамотном MVC это будет так:
<?=$view->get('category', 'выберите категорию')?>

зы: пример странный, через get передавать русский текст…
зы2: strtoupper вообще говоря лишняя функция, это проще и лучше делать через css
0
phpdude #
Ну если на прямую то вот:
[?=(empty($_GET['category'])? 'выберите категорию': strtoupper(htmlspecialchars($_GET['category'])));?]

А при грамотном MVC это будет так:
[?=$view->get('category', 'выберите категорию')?]

первый пример ну лад, допустим да, но согласитесь на смарти это проще выглядит в разы?

второе… при грамотном мвц, шаблон должен уметь вызывать методы контроллера? или я чего то не понял?
+1
TiGR #
Напишите пример php кода, аналогичный тому, что представлен в статье.
+1
ayavryk #
>лучше семантическая(наглядная) верстка и php вставки
Наглядная HTML-верстка разбавленная php-вставками теряет наглядность.
Поддержка и модернизация такой окрошки становится очень проблематичной.

Впрочем, шаблонизаторы типа предложенного положение не сильно меняют.
+1
TiGR #
Я правильно понял — MACRO не является самостоятельным продуктом? И употребить его вне Limb3 будет проблематично, если вообще возможно?
0
korchasa #
У него ровно две зависимости — пакеты CORE и FS. Если делать меньше, то это уже будет дублирование кода.
+3
oowl #
Что-то жутко знакомое, противное, казалось бы, уже отжившее свой век встретило меня на странице результатов тестов…
«Снежинки», — задумчиво пробормотал я и закрыл страничку.
0
korchasa #
Согласен, но так как на остальной части сайта они было, то пришлось делать и здесь. Обещаю, что завтра они исчезнут навсегда.
+1
BonySoft #
Познакомился с MACRO летом этого года; применил его в двух «боевых» проектах, впечатления в-основном положительные: к синтаксису привыкаешь быстро, теги и фильтры достаточно обширны и покрывают значительную часть потребностей (к тому же никто не мешает добавить свои), парсер услужливо подсказывает, где ошибки (незакрытые теги, неверные параметры, некорректная вложенность и прочие), высокая скорость (компилятор на выходе отдает уже native-PHP, отсюда и :)

Из минусов (имхо): скомпилированные шаблоны валятся в одну кучу, а имена файлов хешируются, что затрудняет работу с ними (к примеру, если надо удалить какой-то конкретный, чтобы он перекомпилировался при обращении); кастомные теги/фильтры со сложной macro-логикой (например, form-тег select с optiongroup) своими силами создавать тяжко, т.к. не описан соответствующий SDK :/ правда компенсируется это весьма оперативным и благожелательным коммьюнити 8)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.