Building a template engine in PHP

Possibly the most common sign of bad code is tight coupling, especially between logic and presentation. It might seem like a good idea to print out the HTML while the data is being generated, but it more often than not leads to a big incoherent mess of tangled code. It also causes other issues, like what happens if an unexpected error occurs in the middle of the page? By then half the page has already been outputted and it might be too late to handle the error nicely.

There are a lot of problems with mixing logic and presentation, but it’s actually very easily solved, especially in PHP. While PHP makes it unnecessarily easy to write shitty code it also provides a lot of ways to avoid it. I’ll use this post to show you how ridiculously easy it is to create a template engine in PHP in three easy steps.

So, we begin by setting up a simple class to wrap our template. The $template variable passed in the constructor is the relative path to our template, e.g. if the templates are in a folder called “views” you should pass “views/.php” to the constructor.

<?php

class Template
{
    protected $template;

    public function __construct($template)
    {
        $this->template = $template;
    }
}

Then we add variables to it, we use magic getters and setters for this to make handling of our template a little more verbose, but you can of course use regular getters and setters if you prefer that.

<?php

class Template
{
    protected $template;
    protected $variables = array();

    public function __construct($template)
    {
        $this->template = $template;
    }

    public function __get($key)
    {
        return $this->variables[$key];
    }

    public function __set($key, $value)
    {
        $this->variables[$key] = $value;
    }
}

Lastly we add the printing of the template, this abuses some pieces of PHP’s functionality. Partly that code can be included anywhere and that all the outputĀ can be stored and loaded into variables. We load all created variables into the functions variable scope with extract(), this has the nice side effect of not conflicting with variables created outside the function and limiting the template file’s access to variables created in the template. Also, by using chdir() before including the file, we change the working directory to the specified template directory (if any). This allows for including other templates within templates without having to concern ourselves with where the template will be loaded from.

public function __toString()
{
    extract($this->variables);
    chdir(dirname($this->template));
    ob_start();

    include basename($this->template);

    return ob_get_clean();
}

We can then use it anywhere we want like this. Of course, if you actually need functionality like this you might have more use for a template engine like Twig, which is a bit more extensible and comes with a ton of cool features.

<?php

require 'templateEngine.php';

$view = new Template("path/to/template.php");

$view->title = "Hello world!";
$view->description = "This is a ridiculously simple template";

echo $view;

9 thoughts on “Building a template engine in PHP

  1. I like the idea of this tutorial. I do know the benefits of using an existing engine, but I still want a go at constructing one of my own.

    I’m just wondering where ‘templateEngine.php’ comes from mainly?

  2. What is the purpose for the ob_start() and ob_get_clean()? I recently created an obviously very similar class, except without the output buffering.

  3. That’s for the __toString() method to behave as expected (i.e. “echo $template”), but of course it could be used without it and just output the template to the browser directly.

  4. That’s so you can use relative links to include other templates so you don’t have to specify the folder of your templates from inside the templates. Does that make sense to you? It’s a bit difficult to explain without a rather complex example.

  5. Damn it… The PHP was stripped out.
    It should be include ‘header.php’ inside PHP tags after “example” and include __DIR__ . ‘/header.php’ inside PHP tags after “of”.

  6. would be a good idea to first check if ob_start () has already been started.

    if (!in_array (“ob_gzhandler”, ob_list_handlers()))
    {
    ob_start (“ob_gzhandler”);

    }else{

    ob_start ();
    }
    extract ($var);

Leave a comment