Building a Simple Template Parser in PHP

December 25, 2007

This article is for people looking to build a very simple template parsing system in PHP. Once you understand how a template system works feel free to take the ideas presented here and mold the system to your specifics. Note this article assumes a fair amount of ability with PHP and objects in PHP

About a year ago I decided that I no longer want any HTML in my PHP. It is also just plain good programming to get as much of the display out of the processing code as possible. Since the PHP is usually manipulating data from the database then sending it to the display I figured this could not be too hard to accomplish. So I started off down the path. I will show you here how to build a really simple class to parse template files here.

To start out with you need to decide how your template system will know what it needs to replace. I like to use {brackets}. Let’s make a simple HTML page to test this on.

Hello {who}!

Save this as template.html. That is all you need for a template. Obviously you can get much more complex if you want to, but for now we will keep it simple. The next step to think about is how we are going to get the data to the HTML file. I like using associative array because they are really simple. Our array will have the string in the {brackets} then the value like so: $data[‘title’] = ‘First Template Test’;. You can put anything for the value that you feel like, even more html. Let’s setup our data array now.

$data = array('title' => 'First Template Test', 'who' => 'World');

Save this in index.php

Great! We have our template and our data, but wait, they are in two different files! Well, that is what the template parser is for. We will start making it now.

First we need to name the class. My first templating system I called TemplateParser, my new systems I simply call Template, I will use Template here. Now for some basic class structure. Remember COMMENT EVERYTHING! In two months when you come back and try to figure out why something does not work right comments are incredibly useful.

class Template {

/**
* Class constructor. Currently a stub
**/
function __construct() {}

} // End class template

Save as Template.class.php

That is all you need to build a class, but obviously that will not do us any good. We need a template file to fill the data into. May as well bring in the file when we instantiate our class as an object. But what if the template file does not exist? Maybe we should take that into account as well. We have some significant revision we must do to the constructor.

/**
* Class constructor. Brings in the template file and makes
* sure that it really exists. If the template file does not exist
* the page cannot be displayed so we will need to show an
* error message and die off.
*
* @param String $template_file - Path to the HTML template file
* @return Template
*/
function __construct($template_file) {
// Make sure that the template file exists
if(file_exists($template_file)) {
// We have a file that we can work with
} else {
// AAAAHHHHH TEMPLATE FILE DOES NOT EXISTS!
// HIT THE PANIC BUTTON!
die("SITE PANIC: I cannot find the template file! Please contact your site administrator with this error message.");
}
}

That works great for checking if there is a file. We need to think about something for a minute. We have to have the contents of that template file to find the {brackets} and replace them. But where will we keep it? How about a class level variable. That way when we get down to the parsing the parsing function will have access to the file as well.

Add the following above the constructor:
/**
* Holds the HTML from the template file.
* This also gets manipulated and will contain the
* replaced data to display after parsing
*
* @var String
**/
var $html;

Now we need the code to load the template file. It is very simple. Put this right after the comment in the constructor indicating that we have a file:

$this->html = file_get_contents($template_file);

We now need only to parse the data and display it back. Remember that our data is contained in an associative array. That array is going to be sent to our parsing method.

/**
* Run through the stored html and replace any tags
* found in the html with the associated key in the array.
*
* @param Array $tags - Tag and Replacement associative array
* @return Boolean - False if parsing fails
**/
function parse($tags) {
$ret = FALSE; // Our return is false unless parsing succeeds
// Check $tags and ensure it is an array
if(is_array($tags) && count($tags) > 0) {
/*
* We have some tags. The easiest way to replace their
* content in the template is with a loop and string replace.
*/
foreach($tags as $tag => $data) {
$this->html = str_replace('{'.$tag.'}', $data, $this->html);
}
$ret = TRUE; // We parsed something
}
return $ret;
}

Seems pretty simple right? Well it is! Last thing we need is to display it to the user. This will be one of the easiest methods you ever have to write.

/**
* Displays the parsed template.
* Generally you do not want to print anything inside a class, however
* here is an exception seeing as the entire purpose of this class is for display.
*
* @param $show_now = TRUE - Will show the template now, if false sends back a string.
* @return Mixed - TRUE or String
**/
function display($show_now = TRUE) {
$ret = TRUE;
if($show_now) {
echo $this->html;
} else {
$ret = $this->html;
}
return $ret;
}

That’s it. We are done. You now have a complete working template system. Like I mentioned before this is a really simple system. Once you get better aquainted with template systems you will probably want to expand it. In particular you may want to make a set() method to set an internal class array of tags instead of building up an array on your index page then sending it in. There is a lot you can do with a templating system. Have fun with it.

Here are the files we made put together all pretty like

template.html:

Hello {who}!

index.php:

// Get and make our template
require('Template.class.php');
$t = new Template('template.html');

// Setup the data to send to the template
$data = array('title' => 'First Template Test', 'who' => 'World');

// Parse the template data
$t->parse($data);

// Show the template with the replaced data
$t->display();
/*
* Optionally
* echo $t->dispay(FALSE);
*/
?>

Template.class.php:

class Template {

/**
* Holds the HTML from the template file.
* This also gets manipulated and will contain the
* replaced data to display after parsing
*
* @var String
**/
var $html;

/**
* Class constructor. Brings in the template file and makes
* sure that it really exists. If the template file does not exist
* the page cannot be displayed so we will need to show an
* error message and die off.
*
* @param String $template_file - Path to the HTML template file
* @return Template
*/
function __construct($template_file) {
// Make sure that the template file exists
if(file_exists($template_file)) {
// We have a file that we can work with
$this->html = file_get_contents($template_file);
} else {
// AAAAHHHHH TEMPLATE FILE DOES NOT EXISTS!
// HIT THE PANIC BUTTON!
die("SITE PANIC: I cannot find the template file! Please contact your site administrator with this error message.");
}
}

/**
* Run through the stored html and replace any tags
* found in the html with the associated key in the array.
*
* @param Array $tags - Tag and Replacement associative array
* @return Boolean - False if parsing fails
**/
function parse($tags) {
$ret = FALSE; // Our return is false unless parsing succeeds
// Check $tags and ensure it is an array
if(is_array($tags) && count($tags) > 0) {
/*
* We have some tags. The easiest way to replace their
* content in the template is with a loop and string replace.
*/
foreach($tags as $tag => $data) {
$this->html = str_replace('{'.$tag.'}', $data, $this->html);
}
$ret = TRUE; // We parsed something
}
return $ret;
}

/**
* Displays the parsed template.
* Generally you do not want to print anything inside a class, however
* here is an exception seeing as the entire purpose of this class is for display.
*
* @param $show_now = TRUE - Will show the template now, if false sends back a string.
* @return Mixed - TRUE or String
**/
function display($show_now = TRUE) {
$ret = TRUE;
if($show_now) {
echo $this->html;
} else {
$ret = $this->html;
}
return $ret;
}

} // End class template
?>