Working with Multiple Template Files to Separate Logic from Presentation

Welcome to the last part of the series “Separating logic from presentation.” In three tutorials, this series teaches you how to develop an expandable template processor class using PHP 5, which exposes some useful features, such as recursive placeholder replacement, MySQL result sets processing, and parsing of dynamic PHP files, among others.


A downloadable file for this article is available here.

Introduction

As you’ll hopefully recall, in the previous article I set up an illustrative hands-on example, aimed at demonstrating the functionality of the “TemplateProcessor” class that I wrote at the beginning of the series. With reference to this example, I created some basic dynamic PHP files, aside from fetching a small MySQL result set, and finally integrated all these data sources within an array of input tags, in order to show how the template processor class was capable of parsing a given template file.

Although the sample PHP files that I included as elements of the respective array of input tags were rather basic, they allowed me to illustrate the way that the “TemplateProcessor” class replaces the placeholders in question with data coming from different data sources. As I said repeatedly over the previous tutorials, the structure of this class is very expandable, thus if you need even more template processing features, in accordance with your particular development requirements, you can easily add more methods to the class, or rewrite the existing ones.

Now, by returning to the subject of this last installment, I’ll use the skeleton of the original “TemplateProcessor” class to develop an improved, production-level template processor, which, as you’ll see in a few moments, will be capable of working with multiple template files, in addition to implementing a chunked caching system. In this way, the class will be able to use several templates that have distinct cache expiration times, aside from utilizing most of the template processing features that you learned before.

Are you ready to start learning how to code this improved template processor? Right, let’s do it together.

{mospagebreak title=Setting up the basics of chunked caching: defining multiple template files}

One of the most remarkable benefits of chunked caching systems rests on the ability to cache different sections of a web page, which can be treated as independent structures. This concept makes a lot of sense if you consider that most websites have different areas, such as headers and footers that don’t change very often, and evidently can take advantage of a caching mechanism.

In this case, keeping in mind the fundamentals of a chunked caching system, I’m going to define three different template files, each of them corresponding to one particular section of a web page: header, body and footer respectively. Then, after defining all the template files, I’ll modify the signature for some methods of the “TemplateProcessor” class, so it can handle the templates as independent files, and eventually assign distinct cache expiration times to each of them, when their contents are cached.

Here are the three basic template files that I plan to use in conjunction with the template processor class. First, the “header_template.htm” 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=”style.css” />
</head>
<body>
<div id=”header”>{header}</div>

Next, you can see the definition of the “body_template.htm” file:

<div id=”content”>{maincontent{staticdata}{dynamicdata}}</div>

And finally, the signature of the “footer_template.htm” file:

<div id=”footer”>{footer}</div>
</body>
</html>

As you can see, I simply split a typical template file into three different areas, so they can be handled separately. Also, I kept their structure basic, thus you can easily understand how distinct template files will be parsed and then cached, in accordance with their time expiries.

Now that you know how each template file looks, it’s time to move on and start coding the improved “TemplateProcessor” class. To see how this will be done, please click on the link below.

{mospagebreak title=Parsing multiple template files: redefining the “TemplateProcessor” class}

Before I list the corresponding source code of the improved “TemplateProcessor” class, let me first explain in a few words how it will work. Since this class must use all the template files previously defined, it will take up an additional array (aside from the array of input tags), which will contain the shortened name of the template file for being parsed, along with its cache expiration time, expressed in seconds.

If this explanation doesn’t ring any bells to you, don’t worry for the moment. When I set up a practical example, you’ll understand how this array will be used by the template processor.

Now, after a short introduction, please take a look at the signature of the modified “TemplateProcessor” class:

class TemplateProcessor{
    // declare data members
    private $output;
    private $rowTag=’p';
    private $tags;
    private $templateFile;
    private $cacheFile;
    private $expiry;
    public function __construct($tags=array(),$pageSections=array
()){
        if(count($tags)<1||count($pageSections)<1){
            throw new Exception(‘Invalid number of parameters for
template processor’);
        }
        foreach($pageSections as $key=>$expiry){
            // define template files
            $this->templateFile[$key]=$key.’_template.htm';
            // define cache files
            $this->cacheFile[$key]=md5($key).’.txt';
            // assign cache expiration values
            $this->expiry[$key]=$expiry;
            // assign tags for replacement
            $this->tags[$key]=$tags[$key];
            // initialize page outputs
            $this->output[$key]=”;
        }
        foreach($this->cacheFile as $key=>$cacheFile){
            // check if cache files are valid
            if($this->isCacheValid($key)){
                // read data from cache files
                $this->output[$key]=$this->readCache($key);
            }
            else{
                // read template files
                $this->output[$key]=file_get_contents($this-
>templateFile[$key]);
                // process template files
                $this->processTemplate($tags[$key],$key);
                // clean up empty tags
                $this->output[$key]=preg_replace(“/{w}
|}/”,”,$this->output[$key]);
                // write crunched data to cache files
                $this->writeCache($key);
            }
        }
    }
    // check validity of cache files
    private function isCacheValid($key){
        // determine if cache file is valid or not
        if(file_exists($this->cacheFile[$key])&&filemtime($this-
>cacheFile[$key])>(time()-$this->expiry[$key])){
            return true;
        }
        return false;
    }
    // process template file
    private function processTemplate($tags,$key){
        foreach($tags as $tag=>$data){
            // if data is array, traverse recursive array of tags
            if(is_array($data)){
                $this->output[$key]=preg_replace(“/
{$tag/”,”,$this->output[$key]);
                $this->processTemplate($data,$key);
            }
            // 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[$key]=str_replace('{'.$tag.'}',$data,$this->output[$key]);
        }
    }
    // 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($key){
        if(!$fp=fopen($this->cacheFile[$key],'w')){
            throw new Exception('Error writing data to cache
file');
        }
        fwrite($fp,$this->getCrunchedHTML($key));
        fclose($fp);
    }
    // read compressed data from cache file
    private function readCache($key){
        if(!$cacheContents=file_get_contents($this->cacheFile
[$key])){
            throw new Exception('Error reading data from cache
file');
        }
        return $cacheContents;
    }
    // return overall output
    public function getHTML(){
        $html='';
        foreach($this->output as $output){
            $html.=$output;
        }
        return $html;
    }
    // return crunched output
    private function getCrunchedHTML($key){
        // crunch (X)HTML content & compress it with gzip
        $this->output[$key]=preg_replace("/(rn|n)/","",$this-
>output[$key]);
        // return compressed (X)HTML content
        return $this->output[$key];
    }
}

As shown above, now the “TemplateProcessor” class looks a little more complex and intimidating, but essentially it implements the same logic that you learned in the previous articles. Since now the class must deal with multiple template and cache files, most of its properties have been redefined as arrays, a fact that you’ll clearly see if you analyze the signature of the constructor.

Notice how this method uses a “foreach” loop, in order to create the arrays corresponding to the cache and template files, the respective cache expiration times, and the parsed output of each section of the web page. Also, following the same approach, the method checks out, in sequence, the validity of each cache file and determines whether the template file in question should be parsed and saved later on, or the parsed contents should be directly fetched from the cache file.

In general terms, you can see that all the subsequent methods of the class use the same approach in working with different cache and template files, and most of them utilize array keys to handle each element in question. Therefore, if you already read the previous articles of the series, you shouldn’t have any problems understanding how the template processor works.

Well, before I proceed to coding a hands-on example, I’d like to point out one last thing: notice the way I redefined the private “getCrunchedHTML()” method, instead of compressing the parsed template files and saving them to cache. I deliberately defined the method like this, because I wanted to make the class flexible enough to include the data compression method that you most prefer. In this case, I decided only to remove new line characters from the parsed template files, but you can change this and use another technique for compressing data.

At this point, after explaining how the “TemplateProcessor” class implements chunked caching on multiple template files, it’s a good time to demonstrate its useful capabilities. Therefore, go ahead and read the next section, in order to see the template processor in action. It’s really worth it, trust me.

{mospagebreak title=Putting the “TemplateProcessor” class to work: setting up a concrete example}

Undoubtedly, the best way to see the real capabilities of the “TemplateProcessor” class is simply by setting up an example that shows how all the template files I showed you previously are used together.

In this example, I’ll specify different expiration times for each template file, where the “header_template.htm” file will have an expiry of 3600 seconds, then the “body_template.htm” file will be updated each 60 seconds, and finally the “footer_template.htm” file will be parsed at intervals of 3600 seconds too. Additionally, I’ll include a MySQL dataset within the main section of the web page, which will be fetched by the MySQL wrapping classes that I showed in the previous article (they won’t be listed here).

Having specified the set of parameters for this example, here’s the code sample that uses the “TemplateProcessor” class:

try{
    // include 'MySQL' class files
    require_once 'mysqlclass.php';
    require_once 'resultclass.php';
    // connect to MySQL
    $db=new MySQL(array
('host'=>'host','user'=>'user','password'=>'password',
'database'=>'database'));
    // run SQL query
    $result=$db->query('SELECT * FROM users');
    // get query resource
    $queryResult=$result->getQueryResource();
    // build array of page sections and assign expiration values
for each cache file (chunked caching)
    $pageSections=array('header'=>7200,'body'=>60,'footer'=>7200);
    // build template tags
    $tags=array('header'=>array('title'=>'Template
Processor','header'=>'header.php'),'body'=>array
('maincontent'=>array('staticdata'=>'Static
Data','dynamicdata'=>$queryResult)),'footer'=>array
('footer'=>'footer.php'));
    // instantiate template processor object
    $tpl=new TemplateProcessor($tags,$pageSections);
    // display parsed page
    echo $tpl->getHTML();
}
catch(Exception $e){
    echo $e->getMessage();
    exit();
}

As you can see, the above script first connects to MySQL and fetches a trivial result set from a “users” database table. Next, the corresponding “$pageSections” input array is defined, which contains the shortened names of each web page section along with their cache expiration times. At this stage, do you see how this set of parameters is used internally by the “TemplateProcessor” class? I hope you do.

Now, turn your attention to the second $tags array; notice that this is a recursive array, where all the tags of a specific page section are the elements of another array too. Of course, you can go as deep as you want in defining recursive arrays, as long as the template files contain the appropriate structure of nested placeholders.

Right, that was the hard part. Once the proper input arrays have been defined, they’re passed as parameters to an instance of the template processor class, and finally the whole web page is displayed by using the “getHTML()” method.

Of course, you’ll have a much better idea of the functionality of the “TemplateProcessor” class if you test it for yourself, thus I included a ZIP file here (which is also linked to at the beginning of this article), containing the class and additional sample files. I hope you enjoy playing with the source code I provided you, and as usual, feel free to introduce your own modifications.

Final thoughts

In this last tutorial of the series I went through the making of a PHP 5 extensible template processor class, which not only has most of the features that you saw in my previous article, but also is capable of working with multiple template files, in this way implementing the so-called chunked caching. As you’ve seen here, the source code of the class is pretty easy to follow, which means that you shouldn’t have trouble tweaking it, in order to meet your personal requirements. See you in the next PHP tutorial!

[gp-comments width="770" linklove="off" ]

chat sex hikayeleri Ensest hikaye