How to make a simple HTML template engine in PHP

A template engine is used to separate the presentation from the business logic. A good developer knows this is very important - not only it allows for delegating responsibilities (the designer works on the presentation layer while the programmer works on the business logic), but it also provides a more easier maintenance.

There are a lot (and I mean A LOT) of template engines for PHP. A very popular example is Smarty. Most of these template engines have a lot of advanced options and require the user to learn a new syntax for building the template files.

What if you just want some easy to understand and simple to use template engine? Why not build your own? In this tutorial we'll do just that - we'll create a very simple template engine in PHP that anyone can use without having to spend time reading manuals.

Our template files will be written in pure HTML with some extra tags for easy replacement. We'll put the tags where we want our content to be - the engine will basically act as a replacement feature, but it could be updated for more advanced operations.

In the next picture I provide an overview of the working of this simple PHP template engine.

A simple HTML template

First, let's start with our HTML template file. We must define the tags' format we're going to use. Most templates use curly brackets surrounding the tags, e.g. {tag}, but I like to use a different syntax: [@tag]. Feel free to define your own conventions.

Imagine a typical case of building a user's profile page. Let's assume we need to display the user's photo, username, real name, age and location. An example HTML is provided below.

<h1>[@username] profile</h1>
<img src="[@photoURL]" class="photo" alt="[@name]" />
<b>Name:</b> [@name]<br />
<b>Age:</b> [@age]<br />
<b>Location:</b> [@location]<br />

Create your template file and save it. I like to use the tpl extension. In this case, let's call this template file user_profile.tpl.

Now we just need to load it in our PHP script and replace those tags with real values.

Template engine class

For easier use and portability we'll need a class - it will be called Template. This class will only need two member variables - one for storing the filename of the template and the other to store the values that will be used for replacing the tags in the template.

Let's start with this. We'll define our class and its constructor. I provide the code for this bellow.

class Template {
    protected $file;
    protected $values = array();
  
    public function __construct($file) {
        $this->file = $file;
    }
}

I'll refrain from putting the comments in the code snippets I provide here, because they are lengthy, but the source code for this tutorial is well documented with comments.

At least two more methods are needed: one for setting the values for each tag and the other for outputting the final result.

We'll store our values in a PHP array where each value is stored with a key that matches the tag it is supposed to replace. Our method for defining the key/value pairs will be called set.

The method for outputting the content is a little more advanced (called output). We'll start by trying to verify if the template file exists. If we're unable to do that we'll return an error message. If the template exists we'll load its contents into a variable. We then loop through our values array and replace each tag with its matching value.

The code for these two methods is provided below. Put them in the Template class.

public function set($key, $value) {
    $this->values[$key] = $value;
}
  
public function output() {
    if (!file_exists($this->file)) {
        return "Error loading template file ($this->file).
"; } $output = file_get_contents($this->file); foreach ($this->values as $key => $value) { $tagToReplace = "[@$key]"; $output = str_replace($tagToReplace, $value, $output); } return $output; }

Putting the engine to use

Now we can finally test our first iteration of the template engine. We'll create a simple PHP file (named user_profile.php) which loads the template with test data and outputs its result.

We'll start by including the file with the definition for our Template class (I called mine template.class.php). We then make a new Template object and we define each value in the template. In the end we want to write its output.

include("template.class.php");
  
$profile = new Template("user_profile.tpl");
$profile->set("username", "monk3y");
$profile->set("photoURL", "photo.jpg");
$profile->set("name", "Monkey man");
$profile->set("age", "23");
$profile->set("location", "Portugal");
  
echo $profile->output();

You can see that this is very simple to use. In a real world example those values would be previously loaded from a database or file.

Something is missing though. If we would output the code above it would output incorrect HTML code because we only defined the main part of our content in the template file (no <html> tag, etc.). What we're missing is our layout. So we need a layout template! The code bellow is the structure of what could be a layout for a simple website. It contains an header, menu, content area and footer. You could define multiple tags throughout the file (remember, this is just a template!). In this case I put a title tag for setting the title of the page and a content tag where we'll put our main content.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>[@title]</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css" />
</head>
<body>
    <div id="header">
        <a href="http://www.broculos.net"><img src="broculo_small.gif" class="logo" alt="Broculos.net" /></a>
        <h1><a href="http://www.broculos.net">Broculos.net</a></h1>
        <h2>Simple PHP Template Engine</h2>
    </div>  
    <div id="menu">
        <h1>Navigation</h1>
        <ul>
            <li><a href="user_profile.php">User profile</a> - example of a user profile page</li>
            <li><a href="list_users.php">List users</a> - example table with listing of users</li>
        </ul>
    </div>
    <div id="content">
        [@content]
    </div>
    <div id="footer">
        Example usage of a simple PHP Template Engine.<br />
        Search <a href="http://www.broculos.net">Broculos.net</a> for more tutorials.
    </div>
</body>
</html>

All the files are included in this tutorial's files.

Now we'll use our user profile template for replacing the content tag in the layout template. This nesting of templates provides the needed flexibility.

So our user_profile.php should be like this now:

include("template.class.php");
  
$profile = new Template("user_profile.tpl");
$profile->set("username", "monk3y");
$profile->set("photoURL", "photo.jpg");
$profile->set("name", "Monkey man");
$profile->set("age", "23");
$profile->set("location", "Portugal");
  
$layout = new Template("layout.tpl");
$layout->set("title", "User profile");
$layout->set("content", $profile->output());
  
echo $layout->output();

First we load the profile template. Then we load the layout template and put the content of the profile template in it. Finally we output the HTML.

More advanced template manipulation

If you looked carefully to the menu area in our layout template you noticed the link to the lists_users.php page. This will be the topic for this chapter.

We want a page that lists the registered users. So, how do we go about doing this? My approach is to split this in two templates: one for the main content (i.e. the needed description/title and the definition of the table) and another for each user's listing (each row on the table).

Let's start by creating our list_users.tpl (the main template for the users' list page):

<h1>Users</h1>
<table>
    <thead>
        <tr>
            <th>Username</th>
            <th>Location</th>
        </tr>
    </thead>
    <tbody>
        [@users]
    </tbody>
</table>

Just a title and the definition of the users' table. We need a template for each row of the table. Let's call it list_users_row.tpl.

    <tr>
        <td>[@username]</td>
        <td>[@location]</td>
    </tr>

Now we'll combine these two into one. Because each user/row will be represented by a different template we'll make a function that will allow us to merge these different templates. This is needed because this merged value will then be used to replace the users tag on the main template (list_users.tpl).

static public function merge($templates, $separator = "n") {
    $output = "";
 
    foreach ($templates as $template) {
        $content = (get_class($template) !== "Template")
            ? "Error, incorrect type - expected Template."
            : $template->output();
        $output .= $content . $separator;
    }
 
    return $output;
}

Add the above method to the Template class. This method loops through each Template object, concatenates its output and puts a separator between them (the default separator is a line break for more easier to read HTML code).

The merge method expects an array of Template objects. If there's another type included in the array an error message will be appended to the output (I think this is preferred than ending the script with an abrupt error).

Now we only need to make our list_users.php page. In this page we'll create a few mock-up users, we'll loop through the array and create a template for each user. Then we'll merge the templates for inclusion in the list_users.tpl template. We'll also use the same layout defined previously.

include("template.class.php");
 
$users = array(
    array("username" => "monk3y", "location" => "Portugal")
    , array("username" => "Sailor", "location" => "Moon")
    , array("username" => "Treix!", "location" => "Caribbean Islands")
);
 
foreach ($users as $user) {
    $row = new Template("list_users_row.tpl");
 
    foreach ($user as $key => $value) {
        $row->set($key, $value);
    }
    $usersTemplates[] = $row;
}
$usersContents = Template::merge($usersTemplates);
 
$usersList  = new Template("list_users.tpl");
$usersList->set("users", $usersContents);
 
$layout = new Template("layout.tpl");
$layout->set("title", "Users");
$layout->set("content", $usersList->output());
 
echo $layout->output();

This is it! We finally did it.

Conclusions - Limitations and thoughts

This is just one approach for structuring your files. A more advanced separation of presentation and business logic is still possible: our files are still filled with a lot of code that its only purpose is processing the content for presentation. One approach is to further include more complex syntax and code in our templates: through a custom template language or using pure PHP mixed with HTML in the template.

Personally I like my HTML files clean without any extra code. An alternative is the use of classes for presentation. These classes could inherit from the Template class. Let's assume that a user is defined through an object of the class User and a VUser class is used for the users' template.

class User {
    var $username;
    // etc.
}
 
class VUser extends Template {
    public function __construct(User $user) {
        $this->file = "user_profile.tpl";
        $this->values["username"] = $user->username;
        // etc.
    }
}

In our PHP page we would only need to do the following:

$user = loadUser();
$tpl = new VUser($user);
echo $tpl->output();

Much cleaner, uh?

Our template engine is still very limited. We could use more advanced features like caching for example and other techniques to improve performance.

Resources

Related articles