Using Recursive Methods in Object-based PHP Applications - A final example of recursion: creating a template processor class (
Page 4 of 4 )
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:
<!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>
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 saw how 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!