Article
Beyond The Template Engine
An Alternative Solution
The solution I'm basically advocating is a "template engine" that uses PHP code as its native scripting language. I know this has been done before. And when I first read about it, I thought, "What's the point?" However, after I examined my co-worker's argument and implemented a template system that used straight PHP code, yet still achieved the ultimate goal of separation of business logic from presentation logic (and in around 25 lines of code, not including comments), I realized the advantages.
This system provides developers like us access to a wealth of PHP core functions we can use to format output – tasks like date formatting should be handled in the template. Also, as the templates are all straight PHP files, byte-code caching programs like Zend Performance Suite and PHP Accelerator, the templates can be automatically cached (thus, they don't have to be re-interpreted each time they’re accessed). This is only an advantage if you remember to name your template files such that these programs can recognize them as PHP files (usually, you simply need to make sure they have a .php extension).
While I think this method is far superior to typical template engines, there are of course some issues. The most obvious argument against such a system is that PHP code is too complex, and that designers shouldn't have to learn PHP. In fact, PHP code is just as simple as (if not simpler than) the syntax of the more advanced template engines such as Smarty. Also, designers can use PHP short-hand like this <?=$var;?>. Is that any more complex than {$var}? Sure, it's a few characters longer, but if you can get used to it, you gain all the power of PHP without the overhead involved in parsing a template file.
Second, and perhaps more importantly, there is no inherent security in a PHP-based template. Smarty offers the option to completely disable PHP code in template files. It allows developers to restrict the functions and variables to which the template has access. This is not an issue if you don't have malicious designers. However, if you allow external users to upload or modify templates, the PHP-based solution I presented here provides absolutely no security! Any code could be put into the template and run. Yes, even a print_r($GLOBALS) (which would give the malicious user access to every variable in the script)!
That said, of the projects I've worked with personally and professionally, most did not allow end-users to modify or upload templates. As such, this was not an issue. So now, let's move on to the code.
Examples
Here's a simple example of a user list page.
<?php
require_once('template.php');
/**
* This variable holds the file system path to all our template files.
*/
$path = './templates/';
/**
* Create a template object for the outer template and set its variables.
*/
$tpl = & new Template($path);
$tpl->set('title', 'User List');
/**
* Create a template object for the inner template and set its variables. The
* fetch_user_list() function simply returns an array of users.
*/
$body = & new Template($path);
$body->set('user_list', fetch_user_list());
/**
* Set the fetched template of the inner template to the 'body' variable in
* the outer template.
*/
$tpl->set('body', $body->fetch('user_list.tpl.php'));
/**
* Echo the results.
*/
echo $tpl->fetch('index.tpl.php');
?>
There are two important concepts to note here. The first is the idea of inner and outer templates. The outer template contains the HTML code that defines the main look of the site. The inner template contains the HTML code that defines the content area of the site. Of course, you can have any number of templates in any number of layers. As I typically use a different template object for each area, there are no namespacing issues. For instance, I can have a template variable called 'title' in both the inner and the outer templates, without any fear of conflict.
Here's a simple example of the template that can be used to display the user list. Note that the special foreach and endforeach; syntax is documented in the PHP manual. It is completely optional.
Also, you may be wondering why I end my template file names with a .php extension. Well, many PHP byte-code caching solutions (like phpAccelerator) require files to have a .php extension if they are to be considered a PHP file. As these templates are PHP files, why not take advantage of that?
<table>
<tr>
<th>Id</th>
<th>Name</th>
<th>Email</th>
<th>Banned</th>
</tr>
<? foreach($user_list as $user): ?>
<tr>
<td align="center"><?=$user['id'];?></td>
<td><?=$user['name'];?></td>
<td><a href="mailto:<?=$user['email'];?>"><?=$user['email'];?></a></td>
<td align="center"><?=($user['banned'] ? 'X' : ' ');?></td>
</tr>
<? endforeach; ?>
</table>
Here's a simple example of the layout.tpl.php (the template file that defines what the whole page will look like).
<html>
<head>
<title><?=$title;?></title>
</head>
<body>
<h2><?=$title;?></h2>
<?=$body;?>
</body>
</html>
And here’s the parsed output.
<html>
<head>
<title>User List</title>
</head>
<body>
<h2>User List</h2>
<table>
<tr>
<th>Id</th>
<th>Name</th>
<th>Email</th>
<th>Banned</th>
</tr>
<tr>
<td align="center">1</td>
<td>bob</td>
<td><a href="mailto:bob@mozilla.org">bob@mozilla.org</a></td>
<td align="center"> </td>
</tr>
<tr>
<td align="center">2</td>
<td>judy</td>
<td><a href="mailto:judy@php.net">judy@php.net</a></td>
<td align="center"> </td>
</tr>
<tr>
<td align="center">3</td>
<td>joe</td>
<td><a href="mailto:joe@opera.com">joe@opera.com</a></td>
<td align="center"> </td>
</tr>
<tr>
<td align="center">4</td>
<td>billy</td>
<td><a href="mailto:billy@wakeside.com">billy@wakeside.com</a></td>
<td align="center">X</td>
</tr>
<tr>
<td align="center">5</td>
<td>eileen</td>
<td><a href="mailto:eileen@slashdot.org">eileen@slashdot.org</a></td>
<td align="center"> </td>
</tr>
</table>
</body>
</html>