Template Engines

In general, template engines are a "good thing." I say that as a long time PHP/perl programmer, user of many template engines (fastTemplate, Smarty, perl's HTML::Template), and author of my own, bTemplate. However, after some long discussions with a co-worker, I've decided that the vast majority of template engines (including my own) simply have it wrong. I think the one exception to this rule would be Smarty, although I think it's simply too big, and considering the rest of this article, pretty pointless.

Let's delve a little into the background of template engines. Template engines were designed to allow the separation of business logic (say, getting data from a database) from the presentation of data. Template engines solved two major problems. 1) How to acheive this separation, and 2) How to separate "complex" php code from the HTML. This, in theory, allows HTML designers with no PHP experience to modify the look of the site without having to look at any PHP code.

Template systems have also introduced some complexities. First, we now have one "page" built off multiple files. In general, you have the main PHP page in charge of business logic, an outer "layout" template in charge of rendering the main layout of the site, an inner content-specific template, a database abstraction layer, and the template engine itself (which may or may not be built of multiple files). This is an incredible amount of files to generate a single page. Considering the PHP parser is pretty fast, it's probably not that important unless your site gets insane amounts of traffic.

However, keep in mind that template systems introduce yet another level of processing. Not only do the template files have to be included, they also have to be parsed (depending on the template system, this happens in different ways - regular expressions, str_replaces, compiling, etc.). This is why template benchmarking came to be. Since template engines use a variety of different methods of parsing, some are faster and some are slower (also keep in mind, some template engines offer more features than others).

So basically what we have going on here is a scripting language (PHP) written in C. Inside this embedded scripting language, you have yet another pseudo-scripting language (whatever tags your template engine supports). Some offer simple variable interpolation and loops. Others offer conditionals and nested loops. Still others (well, Smarty at least) offer an interface into pretty much all of PHP.

Why do I say Smarty has it closest to right? Simply stated, because Smarty's goal is "the separation of business logic from presentation," not "separation of PHP code from HTML code." While this seems like a small distinction, it is one that's very important. The ultimate goals of template engines shouldn't really be to remove all logic from HTML. It should be to separate presentation logic from business logic.

There are plenty of cases where you simply need logic to display your data correctly. For instance, say your business logic is to retrieve a list of users in your database. Your presentation logic would be to display the user list in 3 columns. It would be silly to modify the user list function to return 3 arrays. After all, it shouldn't be concerned with what's going to happen to the data. Without some sort of logic in your template file, that's exactly what you would have to do.

While Smarty gets it right in that sense (it allows you to harness pretty much every aspect of PHP), there are still some problems. Basically, it just provides an interface to PHP with new syntax. When stated like that, it seems sort of silly. Is it actually more simple to write {foreach --args} than <? foreach --args ?>? If you do think it's simpler, consider this. Is it so much simpler that there is value in including a huge template library to get that separation? Granted, Smarty offers many other great features (caching, for instance), but it seems like the same benefits could be gained without the huge overhead of including the Smarty class libraries.

I'm basically advocating a "template engine" that uses PHP code as it's native scripting language. I know, this has been done before. When I read about it, I thought simply, "what's the point?" After examining my co-worker's argument and implementing a template system that uses straight PHP code, but still acheives the ultimate goal of separation of business logic from presentation logic (and in 40 lines of code!), I have realized the advantages and honestly, can probably never go back.

While I think this method is far superior, there are of course some issues. The first argument against such a system (well, the first argument I would expect) is that PHP code is too complex, and that designers shouldn't be bothered with learning PHP. In fact, PHP code is just as simple (if not moreso) as the syntax of the more advanced template engines (such as Smarty). Also, you can use PHP short-hand like this: <?=$var;?>. Honestly, is that any more complex than {$var}? Sure, it's a few characters shorter, but if you can get used to it, you get all the power of PHP without all the overhead of parsing a template file.

Here's a simple example of a user list page.

<?php
require_once('./includes/global.php');

$page = & new Page('User List');
$tpl  = & new Template('user_list.tpl');
$user = & new User();

/*
 * The get_list() method of the User class simply runs a query on
 * a database - nothing fancy or complex going on here.
 */
$tpl->set('users'$user->get_list());

$page->set('content'$tpl);
$page->render('layout.tpl');
?>

Here's a simple example of the template to display the user list.

<table cellpadding="3" border="0" cellspacing="1" bgcolor="#CCCCCC">
    <tr>
        <td bgcolor="#F0F0F0">Id</td>
        <td bgcolor="#F0F0F0">Name</td>
        <td bgcolor="#F0F0F0">Email</td>
        <td bgcolor="#F0F0F0">Banned</td>
    </tr>

<?php foreach($this->get('users') as $user) { ?>
    <tr>
        <td bgcolor="#FFFFFF" align="center"><?=$user['id'];?></td>
        <td bgcolor="#FFFFFF"><?=$user['name'];?></td>
        <td bgcolor="#FFFFFF"><a href="mailto:<?=$user['email'];?>"><?=$user['email'];?></a></td>
        <td bgcolor="#FFFFFF" align="center"><?=($user['banned'] ? 'X' '&nbsp;');?></td>
    </tr>
<?php ?>

</table>

Here's a simple example of the layout.tpl (the temlate file that defines what the whole page will look like).

<html>
    <head>
        <title><?=$this->get('title');?></title>
        <link href="style.css" rel="stylesheet" type="text/css" />
    </head>

    <body>
        <h1><?=$this->get('title');?></h1>

        <?=$this->get('content');?>
    </body>
</html>

And finally, the Page & Template classes (the Template class is simply a wrapper around to the Page class methods to keep it more intuitive - of course your implementation may handle this differently).

<?php
/*
 * Page Class
 * A very simple class to handle the rendering of pages.  Does not include any
 * special template tags.  After all, PHP is a very effective template engine
 * in and of itself!
 */
class Page {
    var 
$tags;

    
/*
     * Constructor
     * @param $title The title of the page.
     */
    
function Page($title 'Untitled') {
        
$this->tags['title'] = $title;
    }

    
/*
     * Return the contents of a tag that has been previously set.
     * If a tag hasn't been set, send a message stating so.
     */
    
function get($tag) {
        if(isset(
$this->tags[$tag])) {
            
/*
             * If the tag is an object, assume it's one of our template
             * objects and call it's fetch() method.
             */
            
if(is_object($this->tags[$tag])) {
                return 
$this->tags[$tag]->fetch();
            }
            else {
                return 
$this->tags[$tag];
            }
        }
        else {
            return 
'<!-- ' $tag ' not set! -->';
        }
    }

    
/*
     * Set a template tag to a specific value so we can access it later.
     */
    
function set($tag$value) {
        
$this->tags[$tag] = $value;
    }

    
/*
     * Render the template.  This is done with simple includes and output
     * buffering (thus the syntax inside the template: $this->get('tag')).
     * The $this refers to the current object which is including the file.
     *
     * @param $file The file to include.
     * @param $return If true, return the contents instead of printing it.
     */
    
function render($file$return FALSE) {
        global 
$TPL_ROOT;

        if(
$return) {
            
ob_start();
            include(
$TPL_ROOT $file);
            
$buf ob_get_contents();
            
ob_end_clean();
            return 
$buf;
        }
        else {
            include(
$TPL_ROOT $file);
        }
    }
}

/*
 * Template Class
 * A separate class designed to make it easier to logically distinguish a
 * "page" from the contents of a page.
 */
class Template extends Page {
    
/*
     * Constructor
     * @param $file The file to include.
     */
    
function Template($file) {
        global 
$TPL_ROOT;

        if(
file_exists($TPL_ROOT $file)) {
            
$this->file $file;
        }
        else {
            die(
'Template file: <b>' $file '</b> does not exist.');
        }
    }

    
/*
     * A wrapper method for parent render().  Pass the second argument because
     * we want the contents of the file returned instead of printed (so they
     * can be injected in other template variables).
     */
    
function fetch() {
        return 
$this->render($this->fileTRUE);
    }
}
?>

In short, the point of template engines should be to separate your business logic from your presentation logic, not separate your PHP code from your HTML code.