OO PHP templating

Templating is a common technique for separation of concerns in applications – separating presentational logic from domain (or business) logic. This kind of separation promotes higher maintainability and a better chance to reuse presentational code (by encapsulating it in templates), the kind of traits we would all love to have in our code base.

PHP as a language can be considered a templating system, as in its root it was meant to modify HTML pages dynamically. The need for more structured templating systems arose as PHP applications have grown more and more complex, giving birth to much more specialized and focused solutions.

Search-and-replace Vs. Include

As the title suggests, there are two prevalent forms of PHP templating, each with its own cons and pros:

  • Search-and-replace systems (to which Smarty belongs) involve running string replacement filters on a template, replacing placeholders with dynamic content. Search-and-replace systems suffer from parsing overhead which can be significant depending on the amount of content being parsed. More advanced systems in this category attempt to offset the overhead by caching parse results (Smarty for example relies heavily on caching for its performance), but this introduces the concern of maintaining the cache (determining when it becomes stale).

    Search-and-replace systems are also better suited for serving mostly static content, as regenerating content dynamically incurs the parsing overhead. Hence it is more suited for content web-sites than highly dynamic web-applications, in which every user sees a different page than the others, limiting the effectiveness of caching.

  • Include systems involve including PHP scripts which serve as templates, and passing variables directly to those. WordPress for example uses a basic implementation of such a system for its templating mechanism.Include systems suffer much less overhead for parsing templates than search-and-replace schemes, simply due to the fact that no such action takes place.

    On the other hand, include systems are prone to scoping problems. If templates are included in the normal flow of the application, then they share and pollute the scope they are in – meaning they could affect data in other parts of the application which could lead to weird behavior and decrease in reusability of templates.

Templating with Objects

Ideally, we would like to combine the pros and solve the cons of both systems. We need a way to pass an arbitrary number of variables into a template without polluting the general scope of the application, while avoiding the parsing overhead of search-and-replace routines.

Fortunately PHP presents us with such a construct – Classes. Class methods are different from regular functions in that they have a shared local scope always available – the object instance ($this). Recent PHP templating systems use this property of classes to their advantage, creating a separate scope for templates thereby allowing them to process without affecting the rest of the application.

Savant is a good example of such a system, and so is the View component of the Zend Framework. I’ll be borrowing concepts from both to show how a simple implementation of such a system can be built.

In Code

In the most basic form, a templating system built with the principles I’ve mentioned above will look like this:

class View {
     public function render($script) {
        ob_start();
        $this->_include($script);
        return ob_get_clean();
    }

    public function __get($key) {
    	return (isset($this -> $key) ? $this -> $key : null);
    }

    protected function _include() {
        include func_get_arg(0);
    }
}

Usage is pretty simple – load some vars into a View class instance and pass a template into the render method. For a template that looks like:

 

name; ?>

Current position: job; ?>

Passing parameters into a view instance:

$view = new View();

$view -> name = 'Obama';
$view -> job = 'President of the USA';

echo $view -> render('/path/to/template.phtml');

Will result in:

Obama

Current position: President of the USA

We can also invoke the render() method inside a template to chain several templates together:

name; ?>

Current position: position; ?>

render('footer.phtml'); ?>

‘footer.phtml’ being another separate template file.

In Review

Lets review what the class does:

     public function render($script) {
        ob_start();
        $this->_include($script);
        return ob_get_clean();
}

The render() method receives a $script parameter which is the path to a template script. It passes that parameter to separate method called _include() and captures the output using output buffering.

Capturing the output into a string instead of outputting to the screen at once is very useful if we want to perform this as a part of a complete process, such as preparing headers and starting sessions(), which has to happen before any output is sent to the browser. It is also useful in case we want to perform additional transformations on the output before sending it to the browser (such as parsing and filtering).

    protected function _include() {
        include func_get_arg(0);
    }

The _include() method is the core of this templating approach. By including the template inside a class method with no arguments (calling func_get_arg() instead), the included script has only the object instance ($this) as the available scope to get information from and pass information to. Any variables we declare inside this scope have no bearing on the outside and so collisions and scoping issues are avoided.

     public function __get($key) {
    	return (isset($this -> $key) ? $this -> $key : null);
    }

This is really a utility method to prevent PHP from throwing a ‘variable is not defined’ notice in case the template attempts to access a non existing parameter.

Complete solutions like Zend_View and Savant offer many more features, such as plug-ins, filters, base directory configuration and more, but the principle is the same. While the same structure could be mimicked to some degree using procedural functions only, the simple and convenient interface of passing and retrieving parameter to/from templates is unique to this approach – making it the clear winner in my opinion.

To know when the next article is published, please subscribe to new articles using your Email below or follow me on Twitter.

Subscribe to Blog via Email

Enter your email address to receive notification about new posts.