MACRO - стотысячная попытка сделать новый PHP шаблонизатор

pachanga

Новичок
MACRO - стотысячная попытка сделать новый PHP шаблонизатор

Всем привет! Что может быть лучше того, чтобы пофлеймить в оффтопе в выходные дни на счет еще одной(возможно тщетной) попыткой сделать новый шаблонизатор ;)

Хотел бы начать с того, что, на мой взгляд, лучшим шаблонизатором для PHP является...сам PHP ;) Для кого-то это может быть спорным утверждением, но никто же не будет отвергать тот факт, что изначально PHP именно и задумывался с этой целью: перемешивание разметки с программной логикой. И с этой задачей PHP справляется просто замечательно, так почему же этим не воспользоваться и лишь сделать этот процесс более удобным?

Как обычно говорится в такие моменты, meet MACRO! Основная цель MACRO - облегчить процесс использования PHP вперемешку с какой-либо разметкой при помощи макросов. Идея с макросами концептуально взята из C/C++, однако в MACRO намного расширена. В конечном итоге макросы, как и в C/C++ преобразуются в PHP код. Процесс компиляции шаблона, естественно, не сверх быстрый, поэтому MACRO поддерживает кеширования результат компиляции.

В принципе, в качестве шаблонов для MACRO можно использовать обычный PHP код, например:

Код:
<?php echo $foo;?>
Но, согласитесь, вот это более читабельно:

Код:
{$foo}
В этом плане MACRO похож на Smarty, но, наверное, здесь сходства и кончаются.

Или, например, следующий PHP код, который можно спокойно использовать в MACRO:

Код:
<?php foreach($items as $item) {?>
<b><?php echo $item;?></b>
<?php } ?>
...можно переписать так, используя макрос {{list}}:

Код:
{{list using="$items" as="$item"}}
{{list:item}}
<b>{$item}</b>
{{/list:item}}
{{/list}}
Внимательный читатель возмутится - "как же так, ведь PHP версия короче!", и именно для этого случая будет прав. Но, a) никто и не мешает использовать PHP напрямую в MACRO b) на самом деле, {{list}} макрос умеет делать намного больше(кстати, в PHP версии вместо <?php echo $item;?> можно использовать просто {$item}).

Покажу более продвинутый пример использования {{list}}:

Код:
{{list using="$items" as="$item" counter="$count"}}
Items:
{{list:item}}
{$count})<b>{$item}</b>
{{/list:item}}
done!
{{list:empty}}
Nothing
{{/list:empty}}
{{/list}}
PHP версия бы выглядела как-то так(примерно это MACRO и генерирует для этого случая):

Код:
<?php 
$count=0;
foreach($items as $item) {
$count++;
if($count == 1) {
?>
Items
<?php } ?>
<php echo $count;?>)<b><?php echo $item;?></b>
<?php } ?>
<?php if(!$count){ ?>
Nothing
<?php } else { ?>
done!
<?php } ?>
Опять же, этот код можно так и использовать, но, думаю, на этот раз преимущество макроса {{list}} более очевидно. И опять же, {{list}} умеет делать еще некоторые довольно полезные вещи, о которых, если будет интересно, я могу рассказать.

В общем, если вы все еще читаете, то наверное, уже стало понятно что {{foo}} - макрос, а {$foo} - так называемое output expression в терминах MACRO. Написать свой простейший макрос довольно просто, например, создав файл hello.tag.php(в определенной директории) с таким содержанием:

Код:
<?php
/**
 * @tag hello
 */
class HelloWorldMacro extends lmbMacroTag
{
  function generateContents($code)
  {
    $code->writePHP('echo "Hello world!";');
  }
}
Теперь, этот макрос, который автоматически "подцепится", можно использовать в шаблоне так:

Код:
{{hello}}
..и это выведет "Hello world!"

В поставке с MACRO, кроме {{list}} идет еще ряд макросов, например, {{include}} и {{wrap}}. Хороший пример всегда лучше кучи нудных речей, поэтому сразу к примерам и перейдем:

page.phtml
Код:
{{wrap with="layout.phtml" into="content"}}
Some contents...
{{/wrap}}
layout.phtml
Код:
{{include file="header.phtml"/}}

{{slot id="content"}}

{{include file="footer.phtml"/}}
Когда происходит рендеринг page.phtml, он "обворачивается" в шаблон layout.phtml и вставляется в слот content. Шаблон layout.phtml же в свою очередь подключает шаблоны header.phtml и footer.phtml(которые для экономии места я здесь не привожу).

В принципе, {{include}} макрос можно заменить обычной <?php include(..); ?> вставкой, однако MACRO делает это более оптимально, включая содержимое подключаемого шаблона на этапе компиляции(с оговоркой, если подключаемый файл задается константой, когда же используется динамическое подключение типа {{include file="$some_file"}}, то подключение происходит в runtime).

Если {{include}} имеет довольно простой PHP эквивалент, то {{wrap}} работает намного сложнее и PHP версия будет выглядеть не столь тривиально. Однако и {{wrap}} в случае статического обертывания(т.е когда используется {{wrap with="some_tpl.phtml ..}}) компилируется оптимально в один единственный шаблон. В случае же динамического обворачивания, конечный результат не столь оптимален, но более гибок. Покажу пример:

Код:
$macro = new lmbMacroTemplate("page.phtml");
$macro->set("layout", $layout);
page.phtml
Код:
{{wrap with="$this->layout" into="content"}}
Hello!
{{/wrap}}
В этом примере, мы можем при помощи переменной $layout контролировать процесс обворачивания в конкретный шаблон. Кстати, здесь же можно увидеть, что все переменные, которые передаются в MACRO из вне, доступны глобально через $this. {{include}} и {{wrap}} умеют делать еще некоторые полезные вещи(например, неограниченная глубина обворачиваний, передача параметров и проч), о которых, опять же, я расскажу, если будет интерес.

Еще несколько слов про output expressions, они поддерживают фильтры, т.е:

Код:
{{$foo|trim|uppercase}}
В конечном итоге это скомпилируется в PHP код вида:

Код:
<?php echo uppercase(trim($foo));?>
Фильтры, как и макросы, писать также довольно просто, например:

Код:
class UpperFilter extends lmbMacroFilter
{
  function getValue()
  {
    return 'strtoupper(' . $this->base->getValue() . ')';
  }
}
Кроме этого output expressions поддерживают metnods chaining:

Код:
{{$foo.bar.zoo}}
Что в итоге превратится примерно в следующее:

Код:
<?php echo $foo->get('bar')->get('zoo');?>
Мелочь, но довольно удобно. Output expressions умеют еще много чего, но...не все же карты раскрывать сразу ;)

Из основных возможностей MACRO, пожалуй, все. Собственно, зачем я затеял этот тред: 1) хотелось бы просто услышать мнения людей 2) чисто корыстные цели заинтересовать еще кого-то и, возможно, найти единомышленников

MACRO доступен в Limb3 репозитории и является кандидатом на включение в стандарную поставку пакетов, как только все задуманное будет сделано. А задуманного осталось довольно много: {{pager}} макрос, набор удобных {{form}}, {{input}}, {{textarea}} и проч. макросов.

P.S. Если кто-то интересуется Limb3, возможно спросит, " а как же WACT?". В двух словах, я разочаровался в нем, хотя он пока и является шаблонизатором Limb3 по-умолчанию. Скажем так, MACRO является продолжателем идей WACT, однако в более простой и логичной оболочке. Кстати, в Limb3 уже в данный момент можно использовать одновременно и WACT и MACRO, однако однажды в будущем хотелось бы полностью переключиться на MACRO.
 

Фанат

oncle terrible
Команда форума
имхо, внесение в шаблонизатор конструктора неочевидной логики, делает его очередной пропиертарной разработкой, удобной только самим разработчикам, а шаблоны - несовместимыми между собой.

но с точки зрения пиара текст очень грамотно составлен.
 

pachanga

Новичок
Автор оригинала: *****
имхо, внесение в шаблонизатор конструктора неочевидной логики, делает его очередной пропиертарной разработкой, удобной только самим разработчикам, а шаблоны - несовместимыми между собой.
На счет проприетарной разработки не совсем понял...в том плане, что шаблоны MACRO несовместимы с другими шаблонизаторами, например Smarty? Или нечто другое?
 

Фанат

oncle terrible
Команда форума
между собой. разработанные разными группами программистов.

раскрываю мысль подробнее
хоть в данном тексте и рекламируется набор готовых апплетов, но сам-то продукт - это конструктор макросов. с помощью которого любой желающий напишет, к примеру, макрос foreach. правильно?

так и напишет. в результате его шаблоны
а) будут понятны только ему самому (узнать о наличии list:empty можно только из документации)
б) будут несовместимы друг с другом, поскольку макросы-то разные!
да, можно таскать вместе с шаблонами и определялки. Но кроме скрипта шаблон читают еще иногда и люди. Которые, привыкнув к list:empty не поймут foreach/ok/

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

pachanga

Новичок
Автор оригинала: *****
между собой. разработанные разными группами программистов.

раскрываю мысль подробнее
хоть в данном тексте и рекламируется набор готовых апплетов, но сам-то продукт - это конструктор макросов. с помощью которого любой желающий напишет, к примеру, макрос foreach. правильно?
Да, это можно сделать, но как бы с MACRO будет идти набор уже готовых макросов, которые, по идее, разработчики не должны изобретать(это проблема документации). А вообще, это же как проблема с конфликтом функций - нужны свои, делай собственный префикс, например, {{my:list}}, {{my:include}} и проч.
 

atv

Новичок
А можно сделать так, что бы в одном файле лежал набор именованных шаблонов, а потом, в зависимости от входных данных, передаваемых в шаблон, отрабатывал бы тот или иной именованный шаблон?

А ещё, что бы можно было в другом файле подключить этот общий файл и переопределить некоторые именованные шаблоны?
 

Фанат

oncle terrible
Команда форума
ну, как я уже говорил, это пропиертарный, удобный и понятный только своим разработчикам шаблонизатор.

вообще, типичный программист физически неспособен представить пользователем своего продукта кого-либо кроме себя.

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

pachanga

Новичок
Автор оригинала: atv
А можно сделать так, что бы в одном файле лежал набор именованных шаблонов, а потом, в зависимости от входных данных, передаваемых в шаблон, отрабатывал бы тот или иной именованный шаблон?
Ты имеешь в виду что-то типа:

templates.phtml
Код:
{{template id="foo"}}
...
{{/template}}
{{template id="bar"}}
...
{{/template}}
{{template id="hey"}}
...
{{/template}}
page.phtml
Код:
{{pick from="templates.phtml" template="$this->tpl"/}}
?

Если да, то пока такого функционала нет, но это сделать довольно просто(выше приведены воображаемые макросы)

Просто то же самое можно сделать, используя {{include}} макрос с динамическим аттрибутом file, только шаблоны будут лежать не в одном файле, а по раздельности.

-~{}~ 13.10.07 23:47:

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

-~{}~ 13.10.07 23:50:

Автор оригинала: tf
имхо бред (к аргументации относится)
Если по поводу конфликта макросов, то, например, никогда не хотелось сделать собственную реализацию strpos для PHP? Надеюсь нет, т.к явно был бы конфликт, т.е надо делать как минимум my_strpos, с другой стороны, зачем это делать, если уже все сделано за тебя?
 

atv

Новичок
Ты имеешь в виду что-то типа:
Нет я имею ввиду:
Код:
<xsl:template match="chapter">
  <fo:block>
    <xsl:apply-templates/>
  </fo:block>
</xsl:template>
Если во входных данных есть "chapter", то шаблон отработает, если нет, то нет.

А ещё этот шаблон можно переопределить. Очень удобно.

А ещё можно написать несколько шаблонов для "chapter":
Код:
<xsl:template match="chapter[@class = 'foo']">
  <fo:block>
    <xsl:apply-templates/>
  </fo:block>
</xsl:template>

<xsl:template match="chapter[@class = 'bar']">
  <fo:inline>
    <xsl:apply-templates/>
  </fo:inline>
</xsl:template>
И в коде устанавливать необходимое значение свойства "class" для елемента "chapter", в результате логика остаётся в коде, а дизайнеру нужно только подготовить шаблоны.
 

pachanga

Новичок
Автор оригинала: atv
Код:
<xsl:template match="chapter">
  <fo:block>
    <xsl:apply-templates/>
  </fo:block>
</xsl:template>
Если во входных данных есть "chapter", то шаблон отработает, если нет, то нет.
Ну вот так можно сделать:

Код:
<?php if(isset($data['chapter']) { ?>
{{include file="chapter.phtml"}}
<?php } ?>
А еще скоро будет макрос isset(скорее всего такое будет название), который, по сути, будет syntactic sugar для вышеприведенной конструкции:

Код:
{{isset var="{$data.chapter}"}}
{{include file="chapter.phtml"}}
{{/isset}}
 

Krishna

Продался Java
Не вникая в суть очередной гениальной концепции хочу сказать, что для начала неприятна разметка в духе смарти.
ИМХО, в шаблонизаторе нужно использовать XML, хотя бы потому что он знаком многим и к тому же лучше будет восприниматься редакторами.
 

pachanga

Новичок
ИМХО, в шаблонизаторе нужно использовать XML, хотя бы потому что он знаком многим и к тому же лучше будет восприниматься редакторами.
Это было сделано умышленно:

a) как раз для того чтобы можно было использовать бесконфликтно XML подобной разметкой в таких случаях:

Код:
...
<input value="{{some_macro ...}}"/>
...
b) на мой взгляд, XML подобный синтаксис путает, когда используется вперемешку с шаблонизатором, необходим уникальный узнаваемый синтаксис, например, если бы было вот так:

Код:
<book>
<include file="book.xml"/>
</book>
то было бы неочевидно, где идет XML разметка, а где отрабатывает шаблонизатор, другое дело:

Код:
<book>
{{include file="book.xml/}}
</book>
 

HraKK

Мудак
Команда форума
pachanga
Я всегда подхожу к таким задачам с одной стороны - а зачем?

Зачем на ваше ИМХО этот новый вариант шаблонизатора? Чем он будет лучше того же Quicky?
 

HraKK

Мудак
Команда форума
cDLEON
Ой йо? Все можно назвать велосипедом. Гугл - вилосипед. Виновс - вилосипед. ПХП - вилосипед. И ниче, ездиют.
 

dark-demon

d(^-^)b
pachanga, думаю скоро ты изобретёшь xslt :)

Код:
	<t:template match="contact[@type='email']">
		<t:variable name="email" select="translate(.,'/','@')" />
		<a class="contact" href="mailto:{$email}">
			<t:value-of select="$email" />
		</a>
	</t:template>
преобразует такой код:
Код:
<contact type="email">abc/gmail.com</contact>
где бы он ни постречался в такой:
Код:
<a class="contact" href="mailto:[email protected]">[email protected]</a>
 
Сверху