Skip navigation.
Home

Templating: A Minimalist Templating System

A few years back, my co-worker Josh and I came to the conclusion that we didn't like the Smarty templating system. Not that there's anything wrong with Smarty - it's just that we didn't like the fact it was this software system that didn't seem to do anything except behave like a subset of PHP, and required a lot of extra code.

So, we did some thinking, and thought a bit about Cold Fusion, a really nice language that gets little respect because it looks like HTML. There are a few things CF does to make life easier for HTMLers (but makes it a lousy development language for regular programmers). Some ideas bubbled to the top.

1. All variables are globals.
2. Most data is in arrays.
3. The "IF" syntax of CF sucks. I had a preference for SQLs IIF() or Excel's =IF(). That's a functional IF rather than a structural IF.
4. It's nice to be able to call object methods.
5. Global functions are useful at times.

6. We also established an goal where HTML editors could be used to prototype the design, then SQL used to create the data, generators to make the code, and then the prototype modified to create the output templates.

From these, we defined a subset of PHP to use as a "template language".

<?=function($obj) ?>
<?=($obj->boolean) ? 'value' : 'value'.$obj->var.'string' ?>
<?php while ($obj->next()) : ?>
<?php endwhile; ?>
<?=$obj->var; ?>
<?=$obj->func(); ?>

Believe it or not, that subset is adequate to build websites that display records from fairly complex relational databases.

To use the template, you set up a Template object that creates an "execution environment" for the template, by exporting into it any variables and objects. These objects become "globals" that you can call. Behind the scenes, that's exactly was the templating system did: it created a bunch of globals. The "trick" is, once the environment is set up, you just "include" the template, and PHP parses and executes. It's fast.

Sample code:

include('Template.class.php');
$t = new Template('foo.pht');
$t->registerObjects( $x, $y, $z );
echo $t->toString();

The rationale for using a subset of PHP was this: most template systems give you a subset of a real programming language.

The rationale for using the ternary operator (? and :) instead of "if" is this: the IF..THEN..ELSE structure looks ugly, mainly becaues HTML is verbose, and can lead to big blocks of code. Instead of big IF blocks, alternatives should be used: CSS, logic in the object, and sub-templates.

The CSS way is to emit all objects, but show or hide them using CSS style sheets. The Logic-in-the-Object way is to push the logic back into the object, so that the object sets boolean flags that are used as signals by the template. Sub-templates are template objects that are rendered within the ternary operator. (This wasn't implemented in the early template engines. It turns out that sub-templates aren't extremely important, unless you're going to render lists of different types of objects. They are a useful convenience more than a requirement.)

It also turns out that nested ternary operators are a little easier to manage than nested IF blocks. Generally, there shouldn't be complex logic in templates. The output should be limited in the queries, not in the template layer.

Getting back to the sub-templates. This is the latest feature, where you can include a template within a template. It's just like "include", but in the system, you export the first template object into the second template.

include('Template.class.php');
$t = new Template('t','foo.pht');
$t->registerObjects( $x, $y, $z );
$u = new Template('u','bar.pht');
$u->registerObjects( $t );

The templates now get names of 't', and 'u'. These names match the variable names, in this example, but what they really do is map the object to the variable name used within the template.

In the following example, in foo.pht, we call our object $a, so we set up our objects to have the name 'a'. Then, we pass that to the template.

include('Template.class.php');
$x = new Obj( 'a', 1 );
$t = new Template('t','foo.pht');
$t->registerObjects( $x );

$y = new Obj( 'a', 2 );
$u = new Template('u','foo.pht');
$u->registerObjects( $y );

$z = new Obj( 'a', 3 );
$v = new Template('v','foo.pht');
$v->registerObjects( $z );

$w = new Template('w','bar.pht');
$w->registerObjects( $t, $u, $v );

Note that any objects that will be used by a template need to support naming. It seems onerous, because it kind of is.

So, that's what we have so far. I'm thinking it's a little verbose, and it can be made to read more like this:

$u = new ObjectTemplate( 'foo.pht', new Obj( 2 ) );

That would require that the template refer to the object as "$this" or some other default name. That's a reasonable expectation if we assume each sub-template will display only one object.

If we need to apply the template multiple times:

$u = new ObjectTemplate( 'foo.pht' );
$v = $u->applyToObject( 'v', new Obj(2) );
$w = $u->applyToObject( 'w', new Obj(3) );
$x = new Template( 'bar.pht', $v, $w );

Generally, though, you don't have this situation where you have two objects of the same type, with different names. Usually, you just do an $obj = new Obj('obj',$x)., and there's only one instance of Obj on the page.

When you do have multiples of an object, it's because they're all in a single iterator object, and you're looping over it. So, again, you need only one name. In sum, these above examples are contrived to show off some generally useless flexibility which we rarely ever require.

I'm starting to think Templates should use positional parameters.

----------------

References: A Minimalist Templating System again
PHP Savant (not really an influence, but interesting)