Developing an Extensible Template Processor in PHP 5

This is the first part of a three-part series that covers separating logic from presentation in PHP applications. In this article you will learn to develop a template system that is advanced enough to meet the requirements of a majority of applications.


A downloadable file for this article is available here.

Introduction

In consonance with the evolution of PHP to the mature multi-purpose programming language that you see today, template systems have largely grown in popularity, due to their ability to separate the driving logic of an application from its visual presentation.

Definitely, splitting your PHP applications into different sections or layers, which are conceived in most of cases as the data, the application and the presentational layers respectively, helps to make the entire program more flexible and maintainable. In general terms, when you use a template system to keep the application’s logic isolated from the visual appearance, usually you’re going to work with one (or many) template files that contain a specific number of placeholders, which will be filled with real data.

As you probably know, replacing placeholders with actual data (coming either from database tables, flat files or other sources) is the responsibility of the application layer, which implements the required “smart” logic that substitutes data markers with real content. Taking into account  the basic functionality provided by most PHP template systems, you can pick up a particular software package in accordance with the size and complexity of the application that you’re currently developing, and of course in consonance with your own expectations.

Particularly when it comes to choosing a specific template system, you’ll find a plethora of options, ranging from basic placeholders replacing libraries, to full-featured systems like the popular Smarty, which eventually might require learning proprietary template statements, in order to get the most out of it.

Nevertheless, it’s possible to develop an intermediate template system that meets the requirements of a vast majority of applications, without the need to appeal to basic packages or having to deal with the numerous features of Smarty. It’s precisely for this reason that this series will be focused on developing an extensible template processor in PHP 5. The template system that I plan to build will expose a handy set of features, which hopefully will satisfy the most common requirements of modern template software, without the need to learn a new “template-oriented” sub language.

Are you ready to learn how to code an expandable PHP 5 template processor? Right, let’s get started.

{mospagebreak title=Getting started: defining the basic structure of the template processor}

A good point at which to start developing my template processor is with defining its basic structure. Essentially, all the functionality of the template system will be encapsulated within a single PHP 5 class, which will expose originally a few handy methods that can be easily expanded later on. Based on these prerequisites, here’s how the structure of my “TemplateProcessor” class looks:

class TemplateProcessor {
    // data members declaration goes here
    public function __construct(){
    }
    // determine if cache file is valid or not
    private function isCacheValid(){
    }
    // process template file
    private function processTemplate(){
    }
    // process input file
    private function processFile(){
    }
    // write compressed data to cache file
    private function writeCache(){
    }
    // read compressed data from cache file
    private function readCache(){
    }
    // return overall output
    public function getHTML(){
    }
    // return compressed output
    private function getCompressedHTML(){
    }
    // send gzip encoding http header
    public function sendEncodingHeader(){
    }
}

As shown above, the “TemplateProcessor” class presents some structural methods that suggest in some way the tasks they’ll perform further. At first glance, you can see I defined a set of three methods called “isCacheValid(),” “readCache()” and “writeCache()” respectively. This means that the class will be capable of caching the (X)HTML output of the respective web page, after the template file used by the class has been parsed.

Now, turn your attention to the methods “getCompressedHTML()” and “sendEncodingHeader().” As their names indicate, the class will also be provided with the ability to transfer web page contents as encoded data, a handy feature that will help to reduce the download time of the parsed page. In addition, the “sendEncodingHeader()” method will send out to the client the appropriate HTTP header, in order to decompress the data and display parsed web page contents.

Finally, the “processTemplate()” and “processFile()” methods are the core engine of the class, since they’ll perform in conjunction all the required processes for parsing the corresponding template file. As you’ll see in a few moments, the class will be capable of parsing recursively a template file (that is, placeholders nested in other placeholders), along with iterating over MySQL result sets, parsing dynamic PHP files and executing PHP code.

Provided that all the class features I mentioned before are good enough for you, it’s always possible to define even more methods, in order to extend the existing functionality of the “TemplateProcessor” class.

Fine, now you have a clear idea of how this class will work; however my above explanations would be rather useless if I don’t first show you how the template file utilized by the class will look. Thus, here’s a typical sample template file:

<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.01 Transitional//EN” “http://www.w3.org/TR/html4/loose.dtd”>
<html>
<head>
<title>{title}</title>
<meta http-equiv=”Content-Type” content=”text/html; charset=iso-
8859-1″ />
<link rel=”stylesheet” type=”text/css” href=”mystyles.css” />
</head>
<body>
<div id=”header”>{header}</div>
<div id=”navbar”>{navbar {subnavigationbar1}{ subnavigationbar2}}
</div>
<div id=”leftcol”>{leftcontent}</div>
<div id=”content”>{maincontent}</div>
<div id=”rightcol”>{rightcontent}</div>
<div id=”footer”>{footer}</div>
</body>
</html>

If you’ve worked before with template files, the one shown above should be pretty familiar to you. In this example, you can see how the “navbar” placeholder is comprised of two additional placeholders, called “subnavigationbar1″ and “subnavigationbar2″ respectively. In modern template software, recursive placeholder replacement is really a powerful feature that helps designers to expand the versatility of presentational layers, while making template files much more maintainable.

Now that you understand how the “TemplateProcessor” class will work, in addition to seeing the look and feel of a classic template file, it’s time to move forward and start coding each of the class methods.

To learn more about this, please read the next section of the article.

{mospagebreak title=Assembling the template system: coding the “TemplateProcessor” class}

In order to start building the “TemplateProcessor” class, the first thing I’ll do is show the definition of its constructor. The signature for this class method is shown below. 

public function __construct($tags=array()){
    if(count($tags)<1){
            throw new Exception(‘Invalid number of tags’);
    }
    if($this->isCacheValid()){
        // read data from cache file
        $this->output=$this->readCache();
    }
    else{
        $this->tags=$tags;
        // read template file
        $this->output=file_get_contents($this->templateFile);
        // process template file
        $this->processTemplate($this->tags);
        // clean up empty tags
        $this->output=preg_replace(“/{w}|}/”,”,$this->output);
        // write compressed data to cache file
        $this->writeCache();
    }
    // send gzip encoding http header
    $this->sendEncodingHeader();
}

As shown above, the constructor performs some crucial tasks, after checking the validity of the $tags array passed as a parameter. Essentially, this method first determines whether the corresponding cache file is valid, and in accordance with this, reads the parsed web page from the cache.

On the other hand, if the cache has expired, then the method reads the template file in question, then processes it by the private “processTemplate()” method, and finally saves the parsed page to the cache file. Naturally, this operation is performed by the private “writeCache()” method.

The ending line of the constructor calls up the “sendEncodingHeader()” method, in order to tell the browser that data will be sent encoded. Short and understandable, right?

After explaining the logic implemented by the constructor, I’ll move on and show you the definition of the “isCacheValid()” method, which, as I said before, determines the validity of the respective cache file. Please look at the following method:

private function isCacheValid(){
    // determine if cache file is valid or not
    if(file_exists($this->cacheFile)&&filemtime($this->cacheFile)
>(time()-$this->expiry)){
        return true;
    }
    return false;
}

In this case, the definition of the above method is very simple. In short, what it does is check whether the cached contents are valid or not, based on a time expiration caching trigger. If the cache file is found to be valid, the method returns true. Otherwise, the returned value will be false.

Now that you learned how some useful methods of the “TemplateProcessor” class work, go ahead and read the next section of the article. That’s where I’ll show you the definition of the “processTemplate()” method, which is actually the core engine of the class.

{mospagebreak title=Coding the workhorse of the class: defining the “processTemplate()” method}

As I mentioned earlier, the “processTemplate()” method does most of the hard work, since it takes the arrays of tags passed as arguments, and replaces the pertinent placeholders with real data. However, this process isn’t as simple as it sounds, since the placeholder replacement is performed recursively. Additionally, the method is capable of parsing dynamic PHP files, processing MySQL result sets and executing PHP code. Yes, I know you’re curious about how this method looks, so here is its signature:

private function processTemplate($tags){
    foreach($tags as $tag=>$data){
        // if data is array, traverse recursive array of tags
        if(is_array($data)){
            $this->output=preg_replace(“/{$tag/”,”,$this-
>output);
            $this->processTemplate($data);
        }
        // if data is a file, fetch processed file
        elseif(file_exists($data)){
            $data=$this->processFile($data);
        }
        // if data is a MySQL result set, obtain a formatted list
of database rows
        elseif(@get_resource_type($data)==’mysql result’){
            $rows=”;
            while($row=mysql_fetch_row($data)){
                $cols=”;
                foreach($row as $col){
                    $cols.=’&nbsp;’.$col.’&nbsp;';
                }
                $rows.='<‘.$this->rowTag.’>’.$cols.'</’.$this-
>rowTag.’>';
            }
            $data=$rows;
        }
        // if data contains the ‘[code]' elimiter, parse data as
PHP code
        elseif(substr($data,0,6)=='[code]'){
            $data=eval(substr($data,6));
        }
        $this->output=str_replace('{'.$tag.'}',$data,$this-
>output);
    }
}

Indeed, the above method performs a few useful operations that I'll explain right now. As you can see, it takes the $tags array and iterates over it, in order to replace the corresponding placeholders with the values provided by these tags. As I mentioned previously, this is a recursive process (this means that nested placeholders and nested tags are allowed), and additionally, the method has the ability to parse PHP files, via the private "processFile()" method, in addition to processing MySQL data sets.

This last feature implies iterating over database result sets, using the $this->rowTag property, which is used to separate each row of a database table and display them appropriately. Of course, here you have plenty of room to modify the method's source code and process MySQL datasets in a different way.

Finally, the method utilizes the predefined "[code]" tag, which comes in handy for executing any string passed as a part of $tags array as PHP code. As you saw, the wealth of template processing features provided by this method is pretty useful. Again, if you happen to need more complex capabilities for processing template files, it's just a matter of expanding the functionality of this method.

I'd like to end this section by listing the simple definition of the private "processFile()" method, tasked with parsing eventual PHP files that might be included within the incoming $tags array. Here's how this method looks:

private function processFile($file){
    ob_start();
    include($file);
    $contents=ob_get_contents();
    ob_end_clean();
    return $contents;
}

Right, since the above method is actually easy to understand, I'd like to suggest you read the next few lines of this article. There you will see how the remaining methods of the "TemplateProcessor" are defined.

{mospagebreak title=Getting the "TemplateProcessor" class completed: defining the remaining class methods}

Having explained in detail the way the "processTemplate()" method does its thing, the remaining methods are much more comprehensible. So relax and have a look at the private "readCache()" and "writeCache()" methods, which not surprisingly read from and save the web page to the cache file, after parsing the template file in question:

private function writeCache(){
    if(!$fp=fopen($this->cacheFile,'w')){
        throw new Exception('Error writing data to cache file');
    }
    fwrite($fp,$this->getCompressedHTML());
    fclose($fp);
}
private function readCache(){
    if(!$cacheContents=file_get_contents($this->cacheFile)){
        throw new Exception('Error reading data from cache
file');
    }
    return $cacheContents;
}

As illustrated above, these two methods are invoked internally by the class, in order to read and write respectively the parsed web page. Also, notice that the entire read-write sequence is performed on compressed page contents, since the first time (and certainly the subsequent ones) that contents are saved to the cache file, the class encodes the data by calling the "getCompressedHTML()" method.

By the way, now that I mentioned this method, below you can see its signature:

private function getCompressedHTML(){
    // check if browser supports gzip encoding
    if(strstr($_SERVER['HTTP_ACCEPT_ENCODING'],'gzip')){
        // start output buffer
        ob_start();
        // echo page contents to output buffer
        echo $this->output;
        // crunch (X)HTML content & compress it with gzip
        $this->output=gzencode(preg_replace("/
(rn|n)/","",ob_get_contents()),9);
        // clean up output buffer
        ob_end_clean();
        // return compressed (X)HTML content
        return $this->output;
    }
    return false;
}

What this method does essentially is use "Gzip" encoding for compressing the parsed web page, which is fetched either from the cache file, or after being parsed at runtime. In this method, I used the PHP built-in "gzencode()" function, in order to encode the whole web page and return the compressed contents to calling code. Additionally, new line characters are removed from the page, something that can be effective for reducing the size of the parsed web document.

Fine, there's still a couple of simple methods that you need to learn, in order to complete the "TemplateProcessor" class. Below, there are the definitions of the "getHTML()" and "sendEncodingHeader()" methods, respectively:

public function getHTML(){
    return $this->output;
}

public function sendEncodingHeader(){
    header('Content-Encoding: gzip');
}

These two methods are really simple ones. The first returns the whole (X)HTML output to calling code, after parsing the template file and replacing placeholders with real data. The second method is responsible for sending the proper "Content-Encoding: gzip" http header, in order to allow the transference of encoded data.

Okay, the two methods shown above finally complete the definition of the "TemplateProcessor" class. Considering that you may want to see how the entire class looks, I listed its full source code below:

class TemplateProcessor {
    private $output='';// set default value for general class
output
    private $rowTag='p';// set default value for database row tag
    private $tags=array();// set default value for tags
    private $templateFile='default_template.htm';// set default
value for template file
    private $cacheFile='default_cache.txt';// set default value
for cache file
    private $expiry=3600;// set default value for cache
expiration
    public function __construct($tags=array()){
        if(count($tags)<1){
            throw new Exception('Invalid number of tags');
        }
        if($this->isCacheValid()){
            // read data from cache file
            $this->output=$this->readCache();
        }
        else{
            $this->tags=$tags;
            // read template file
            $this->output=file_get_contents($this->templateFile);
            // process template file
            $this->processTemplate($this->tags);
            // clean up empty tags
            $this->output=preg_replace("/{w}|}/",'',$this-
>output);
            // write compressed data to cache file
            $this->writeCache();
        }
        // send gzip encoding http header
        $this->sendEncodingHeader();
    }
    // check cache validity
    private function isCacheValid(){
        // determine if cache file is valid or not
        if(file_exists($this->cacheFile)&&filemtime($this-
>cacheFile)>(time()-$this->expiry)){
            return true;
        }
        return false;
    }
    // process template file
    private function processTemplate($tags){
        foreach($tags as $tag=>$data){
            // if data is array, traverse recursive array of tags
            if(is_array($data)){
                $this->output=preg_replace("/{$tag/",'',$this-
>output);
                $this->processTemplate($data);
            }
            // if data is a file, fetch processed file
            elseif(file_exists($data)){
                $data=$this->processFile($data);
            }
            // if data is a MySQL result set, obtain a formatted
list of database rows
            elseif(@get_resource_type($data)=='mysql result'){
                $rows='';
                while($row=mysql_fetch_row($data)){
                    $cols='';
                    foreach($row as $col){
                        $cols.='&nbsp;'.$col.'&nbsp;';
                    }
                    $rows.='<'.$this-
>rowTag.'>'.$cols.'</'.$this->rowTag.'>';
                }
                $data=$rows;
            }
            // if data contains the '[code]' elimiter, parse data
as PHP code
            elseif(substr($data,0,6)=='[code]'){
                $data=eval(substr($data,6));
            }
            $this->output=str_replace('{'.$tag.'}',$data,$this-
>output);
        }
    }
    // process input file
    private function processFile($file){
          ob_start();
          include($file);
          $contents=ob_get_contents();
          ob_end_clean();
          return $contents;
    }
    // write compressed data to cache file
    private function writeCache(){
        if(!$fp=fopen($this->cacheFile,'w')){
            throw new Exception('Error writing data to cache
file');
        }
        fwrite($fp,$this->getCompressedHTML());
        fclose($fp);
    }
    // read compressed data from cache file
    private function readCache(){
        if(!$cacheContents=file_get_contents($this->cacheFile)){
            throw new Exception('Error reading data from cache
file');
        }
        return $cacheContents;
    }
    // return overall output
    public function getHTML(){
          return $this->output;
    }
    // return compressed output
    private function getCompressedHTML(){
        // check if browser supports gzip encoding
        if(strstr($_SERVER['HTTP_ACCEPT_ENCODING'],'gzip')){
            // start output buffer
            ob_start();
            // echo page contents to output buffer
            echo $this->output;
            // crunch (X)HTML content & compress it with gzip
            $this->output=gzencode(preg_replace("/
(rn|n)/","",ob_get_contents()),9);
            // clean up output buffer
            ob_end_clean();
            // return compressed (X)HTML content
            return $this->output;
        }
        return false;
    }
    // send gzip encoding http header
    public function sendEncodingHeader(){
        header('Content-Encoding: gzip');
    }
}

That's the full source code of my "TemplateProcessor" class. Now that I assembled all the previous methods within the class structure, you can see how each piece of code fits with each other piece. However, if you want to test (and tweak) the class, you can download a sample ZIP file, which contains all the supporting files required to get the class working. You can use this link or the one at the beginning of the article.

Bottom line

That's all for the moment. Over this first article I went through the development of an extensible template processing class in PHP 5 that exposes some interesting features, such as recursive placeholders replacement, processing of PHP files and MySQL datasets, and so forth.

In the next part of the series, I'll set up a step-by-step example that will show you how to use the "TemplateProcessor" class with different input tags. See you in the next part!

[gp-comments width="770" linklove="off" ]
antalya escort bayan antalya escort bayan