HomePHP Page 4 - Using Recursive Methods in Object-based PHP Applications
A final example of recursion: creating a template processor class - PHP
Welcome to the second tutorial of the series “Recursion in PHP.” Comprised of three parts, this series introduces the fundamentals of recursion in PHP, including the definition and use of recursive functions in procedural PHP scripts, as well as the creation of recursive methods in object-oriented Web applications.
The final hands-on example of this article consists of developing a template processor class in PHP 5, which among other things has the capacity to recursively parse template files by replacing nested placeholders with data coming from different sources.
To begin with, this is how a typical template file looks, before being parsed by the respective class:
As you can see, the template file that I just created has some nested placeholders that require the use of a recursive method to replace them with actual data. That’s precisely the function of the “TemplateProcessor” class that I’ve listed 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.=' '.$col.' '; } $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'); } }
As you can see, aside from using recursion for parsing template files, the above class is also capable of caching and compressing the (X)HTML output of parsed web pages. This makes it a quite useful piece of code, particularly if you work very often with template systems. But now, turn your attention to the following class method:
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.=' '.$col.' '; } $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); } }
Regarding the above method, you can see that it calls itself (in other words, uses recursion), in order to replace all the nested placeholders that eventually might be included inside the corresponding template file. Additionally, the method is capable of processing MySQL result sets, parsing dynamic PHP files and evaluating input strings as PHP code.
Now, pay attention to the example below, which illustrates how to use the recursive capabilities of the “TemplateProcessor” class:
try{ // define input tags for template processor class $tags=array('title'=>'PHP 5 Template Processor','header'=>'This is the header section','navbar'=>array ('subnavigationbar1'=>'subnavbar1.php','navigationbar2'=> 'subnavbar2.php'),' leftcontent'=>'leftcontent','maincontent'=>'This is the main section','rightcontent'=>'right content','footer'=>'This is the footer section'); // instantiate a new template processor object $tpl=new TemplateProcessor($tags); // display compressed page echo $tpl->getHTML(); } catch(Exception $e){ echo $e->getMessage(); exit(); }
In this case, I defined a recursive array of input tags, to be passed directly to the constructor of the “TemplateProcessor” class. Since the template file you saw previously contains some nested placeholders, I specified the elements corresponding to the "navbar" key to also be arrays (hence the concept of recursive array), thus the “subnavigationbar1” and “subnavigationbar2” placeholders will be replaced with the parsed output of the “subnavbar1.php” and “subnavbar2.php” PHP files respectively.
Considering that these dynamic files might be created as follows:
<?php // definition for 'subnavbar1.php' file echo 'This navigation section was generated at '.date('H:i:s'); ?>
<?php // definition for 'subnavbar2.php' file echo 'This navigation section was generated at '.date('H:i:s'); ?>
The output of the previous script would look like this:
This is the header section
This navigation section was generated at 12:45:03 This navigation section was generated at 12:45:03 left content This is the main section
right content
This is the footer section
As you can see, the recursive “processTemplate” method has iterated over the array of input tags and replaced all the placeholders with the respective data, which demonstrates the benefits of having at your disposal a method that uses recursion for parsing template files.
Final thoughts
In this tutorial you learned how to define recursive methods in PHP when developing object-oriented applications. I showed you two specific examples: a web page generator class and a template processor. In both cases you sawhow recursion can be quite useful for performing a deep scan on recursive arrays.
However, are you thinking this is it about recursion? You’re wrong. Over the last part of the series I’ll explain how to develop a MySQL-driven discussion forum, which uses recursive methods for fetching user messages from a database table. See you in the next part!