{{macro}} - a zillionth attempt to make a PHP template engine that sucks less
After having used multiple template engines for the last 5 years I’m a strong believer that the best template engine is…PHP itself
We just need some tools to make the usage of raw PHP in templates more friendly and readable.
I have used the forked version of WACT template engine for about 3 years and finally come to the conclusion that its runtime part is way too complicated and non obvious at times. However I do very like the idea of compile time tree of components which generate PHP code. At the same time I just wanted to simplify WACT and make the process of adding new tags much more straight-forward.
That’s why I have hacked up the initial implementation of {{macro}}. To be short, the main idea behind {{macro}} is to use C/C++ alike(but way more sophisticated) macro tags to simplify usage of raw PHP in templates.
So, technically, {{macro}} is a simplified WACT without the run-time components part. Although {{macro}} has a components tree at compile time only, its runtime part is PHP.
{{helloworld}}
All {{macro}} tags are not hardcoded as some special cases and new tags can be added quite easily. For example {{helloworld}} tag can be written as follows:
helloworld.tag.php
<?php
/**
* @tag helloworld
*/
class HelloWorldMacro extends lmbMacroTag
{
function generateContents($code)
{
$code->writeHTML('<b>');
$code->writePHP('echo "Hello world!";');
$code->writeHTML('</b>');
}
}
Now this tag, once put into special directory scanned by {{macro}}, can be used in template:
{{helloworld}}
…which, when rendered, should output: <b>Hello World!</b>
Tags and output expressions
As you might have guessed, tags are defined with {{some_name}} syntax(hence the name {{macro}}). The choice for this non-xml-alike syntax was intentional: {{macro}} doesn’t care about XML alike markup at all and allows to have the following constructs in templates:
<a src="{{link_to ..}}"></a>
Apart from tags there are output expressions which have {$foo} alike syntax. Output expression is just a convenient way to write <?php echo .. ?> in template. For example:
{$foo}
…is compiled into:
<?php echo $foo;?>
However output expressions can do a lot more. For example, output expressions support properties chaining:
{$author.book.title}
…which will be approximately rendered into the following PHP code:
<?php echo $author->get('book')->get('title');?>
Output expressions also support filters:
{$author.name|trim|uppercase}
…here’s is the PHP code which will be rendered:
<?php echo uppercase(trim($author->get('name')));?>
Output filters
Filters are also not special cases and can be added quite easily too, have a look at the possible implementation of uppercase filter:
class UpperFilter extends lmbMacroFilter
{
function getValue()
{
return 'strtoupper(' . $this->base->getValue() . ')';
}
}
And, by the way, output expressions can be used in macro tags attributes too:
{{list from="{$author.books}"}}
...
{{/list}}
BTW, big thanks goes to Sergey “syfisher” Yudin who ported WACT alike filters and output expressions to {{macro}}!
{{list}} tag
Let’s have a look at some real world examples where {{macro}} makes usage of PHP in templates way more pleasant. Here’s some PHP code in a template which renders some list of items:
<?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 } ?>
Don’t you think it’s quite unreadable? Let’s replace it with {{macro}} equivalent using {{list}} macro tag:
{{list for="$items" as="$item" counter="$count"}}
Items:
{{list:item}}
{$count})<b>{$item}</b>
{{/list:item}}
done!
{{list:empty}}
Nothing
{{/list:empty}}
{{/list}}
{{include}} and {{wrap}} tags
If you have ever tried WACT, you might be interested to know that {{list}} tag is a port of WACT’s <list:list>. Apart from basic {{list}} macro tag there are also {{include}} and {{wrap}} tags available.
{{include}} macro works almost like WACT’s <core:include>:
{{include file="foo.phtml"/}}
In this case contents of foo.phtml will be placed into the current template at compile time.
{{include}} supports passing local variables into included templates:
{{include foo="foo" bar="$bar" wow="$this->wow"/}}
All passed attributes will be available in the included template as variables: $foo, $bar and $wow. This allows to have partials with {{macro}} quite easily.
{{include}} also supports dynamic file includes as well:
{{include file="$this->file"/}}
In this case $this->file template will be included in runtime. $this->file value can be passed into template using the following code:
...
$macro = new lmbMacroTemplate("page.phtml");
$macro->set("file", "include.phtml");
$macro->render();
Basically this is the minimal interface which {{macro}} provides. Whatever one needs to pass into template is passed via set($name, $value) interface and available in template as an attribute of the lmbMacroTemplateExecutor object which can be accessed via $this(e.g. $this->foo).
As I said above there’s also a {{wrap}} macro. It can be used as follows:
page.phtml
{{wrap with="layout.phtml" into="content"}}
Some contents...
{{/wrap}}
layout.phtml
{{slot id="content"/}}
Multiwrapping is supported as well:
page.phtml
{{wrap with="layout.phtml"}}
{{into slot="slot1"}}
Some contents...
{{/into}}
{{into slot="slot2"}}
Some contents 2...
{{/into}}
{{/wrap}}
layout.phtml
{{slot id="slot1"/}}
blah-blah
{{slot id="slot2"/}}
As in WACT, {{wrap}} is compiled statically in one template in case if static wrapping is used. However, unlike WACT’s <core:wrap> {{wrap}} supports dynamic wrapping as well:
page.phtml
{{wrap with="$this->layout" into="content"}}
Some contents...
{{/wrap}}
{{apply}} tag
I also added {{template}} and {{apply}} tags. They allow to have “inline” definitions of templates which can be dynamically “applied”:
{{template name="tpl1"}}
Hello, {$name}
{{/template}}
{{template name="tpl2"}}
Good bye, {$name}
{{/template}}
{{apply template="tpl1" name="Bob"/}}
{{apply template="tpl2" name="Bob"/}}
Currently I’m working on {{pager}} and {{form}}(with friends: {{input}}, {{textarea}}, etc) macro tags. Once they are ready I will release macro-0.1.0.
Check it out
At this moment {{macro}} is available via SVN only:
https://svn.limb-project.com/3.x/trunk/limb/macro
Here’s a quick guide on how to try {{macro}}:
- Create ~/macro-try directory
- Create ~/macro-try/lib/limb directory and proceed there
- Checkout the necessary Limb3 packages by running the following commands in console:
$ svn co https://svn.limb-project.com/3.x/trunk/limb/core core $ svn co https://svn.limb-project.com/3.x/trunk/limb/fs fs $ svn co https://svn.limb-project.com/3.x/trunk/limb/toolkit toolkit $ svn co https://svn.limb-project.com/3.x/trunk/limb/macro macro
- Create ~/macro-try/templates directory
- Create ~/macro-try/var directory
- Create template file ~/macro-try/templates/test.phtml as follows:
{{list for="$#items" as="$item"}} List of items: {{list:item}} {$item} {{/list:item}} {{/list}}Tip: $#items is just a shortcut for $this->items
- Create PHP script ~/macro-try/test.php as follows:
<?php set_include_path(dirname(__FILE__) . PATH_SEPARATOR . dirname(__FILE__) . '/lib/'); require_once('limb/macro/common.inc.php'); $macro = new lmbMacroTemplate("test.phtml", new lmbMacroConfig(dirname(__FILE__) . '/var')); $macro->set("items", array("foo", "bar", "hey")); echo $macro->render(); - Finally, try executing this script in console:
$ php test.php
You should see the following:
List of items: foo bar hey
- Jump for joy - you did it
You may also want to have a look at {{macro}} unit tests which may tell way more about this template engine:

November 19th, 2007 at 6:18 am
layout.phtml
В примерах не закрыты теги slot
November 19th, 2007 at 9:36 am
Спасибо!
P.S. How about commenting in English?
January 14th, 2008 at 2:00 am
[…] in this release, I’m especially proud of finally releasing the first version of {{macro}}(I blogged about it before). Sergei Yudin added {{macro}} tags for form elements and pager right before the release. […]
March 3rd, 2008 at 11:16 pm
[…] you are wondering who in the world in his sane mind might need yet another PHP templating engine, I blogged about {{macro}} some time ago where, I hope, I presented some unique arguments in its favour. Here’s the […]
March 17th, 2008 at 11:27 am
There is a “
March 17th, 2008 at 11:28 am
Blog engine eated my first comment
There is a “<php” typo in the first listing after {{list}} tag.
March 17th, 2008 at 12:19 pm
Fixed, thanks!