Using PEAR HTML_Template_IT For Modular Interface Design

Simplify your PHP application design with modular interface templates.

Mixing it Up

When creating PHP applications for the web, it's common to see PHP code mixed in with HTML user interface elements. This ability to integrate smoothly with a standard HTML document is one of the reasons most developers like PHP so much - it's convenient, and you don't have to jump through multiple hoops to add sophisticated business logic to your site.

However, this convenience can sometimes come at a price: most PHP scripts are so closely interwoven with HTML code that maintaining them is a nightmare. Since both the HTML user interface elements and the business logic are in the same physical file, it becomes difficult for say, a graphic designer to alter a site's user interface without the assistance of a developer. And even after making the changes, it's usually necessary to re-test the code to make sure a semi-colon or variable didn't get lost along the way.

What is needed in such situations is a way to keep the business logic and the user interface in separate files, so that they can each be altered independently, and merge them together at run-time to generate the final product. PHP's PEAR repository already includes such a solution, and it goes by the name of HTML_Template_IT.

The Cookie-Cutter Approach

HTML_Template_IT assumes that a single web application is made up of many smaller pieces - it calls these parts templates - and provides an API to link templates together, and to fill them with data. In HTML_Template_IT lingo, a template is simply a text file, typically containing both static HTML elements and variables. These variables are replaced with actual values at run-time; the values may be hard-coded into your PHP script, or retrieved dynamically from a database or other data source.

HTML_Template_IT also makes it possible to nest one template within another, adding a whole new level of flexibility to this template-based method of doing things. By allowing you to split a user interface into multiple smaller parts, HTML_Template_IT adds reusability to your web application, because a template can be used over and over again (even across different projects).

Before proceeding further, you should download and install a copy of the latest version of HTML_Template_IT from PEAR. The package contains the main class file, a set of test cases, and some example scripts.

Questions and Answers

Let's begin with a simple example of how HTML_Template_IT works. Consider the following HTML page - I've called it quiz.tpl - which contains a question and its answer.

<html>
<head></head>
<body>
<!-- BEGIN main -->
Question: {QUESTION}
<br />
Answer: {ANSWER}
<!-- END main -->
</body>
</html>

This is not a traditional HTML page. It's a template, which doesn't contain any useful content; only a set of comment blocks and variables, which serve as placeholders for data. Variables are placed in blocks - the comment marks you see serve as block delimiters - and must be enclosed in curly braces. Once the HTML_Template_IT engine parses this template, these variables will be replaced with actual values. Here's the PHP script that takes care of this:

<?php

// include class
require('HTML/Template/IT.php');

// create object
// set template directory
$template = new HTML_Template_IT("templates/");

// load template
$template->loadTemplateFile("quiz.tpl");

// set block
$template->setCurrentBlock("main");

// assign values
$template->setVariable("QUESTION", "Knock, knock!");
$template->setVariable("ANSWER", "Who's there?");

// parse block
$template->parseCurrentBlock();

// render page
$template->show();

?>

How does this work? Pretty simple, actually: the first step is to create a new HTML_Template_IT() object, and tell it the location of the template files. Typically, templates are stored in a different directory from the PHP scripts that use them.

<?php

require('HTML/Template/IT.php');
$template = new HTML_Template_IT("templates/");

?>

Next, the loadTemplateFile() method is used to load a template file into the template engine. This template file contains the blocks and variables that the template engine will replace with actual data. The template engine looks for this file in the directory specified in the object constructor.

<?php

$template->loadTemplateFile("quiz.tpl");

?>

Once the template is loaded, it is necessary to assign values to the variables inside it. The template above contains two variables, and the setVariable() method is used to assign values to these variables. The setCurrentBlock() method tells the engine which block to look inside for the variables, and the parseCurrentBlock() method takes care of parsing the block and replacing the variables with their values.

<?php

$template->setCurrentBlock("main");
$template->setVariable("QUESTION", "Knock, knock!");
$template->setVariable("ANSWER", "Who's there?");
$template->parseCurrentBlock();

?>

Once the variables have values, all that's left is to display the result. The show() method, when called without any arguments, parses the entire file, replaces the variable placeholders inside it with their values, and renders the final page.

<?php

$template->show();

?>

An alternative here is to use the get() method instead, which retrieves the interpolated version of the template and assigns it to a variable instead of displaying it.

Here's what it looks like:

Since the HTML interface is in a separate template file, it's easy for designers with no programming experience to modify the interface without affecting the program code. All they need to do is remember to put back the placeholders in the correct place. As an illustration, here's a completely reworked version of quiz.tpl:

<html>
<head></head>
<body>
<!-- BEGIN main -->
<table>
<tr>
<td>{QUESTION}</td>
<td>{ANSWER}</td>
</tr>
</table>
<!-- END main -->
</body>
</html>

There is no need to alter the PHP script to accommodate this design modification. Try it for yourself and see.

Rank and File

One of HTML_Template_IT's more useful features is its ability to merge templates scattered across different files. If you have a large project to manage, this can come in handy, as it allows you to split commonly-used pieces of your user interface into separate files and (re)use them wherever needed.

To illustrate, consider this sample page:

Now, if you assume the header and footer to be constant across the site, it makes sense to split the page above into three templates, as follows:

<!-- BEGIN header -->
<html>
<head></head>
<body>
<table width="100%" border="1">
<tr>
    <td align="center"><a href="#">Home</a></td>
    <td align="center"><a href="#">News</a></td>
    <td align="center"><a href="#">Catalog</a></td>
    <td align="center"><a href="#">Support</a></td>
    <td align="center"><a href="#">Contact</a></td>
</tr>
</table>
<p />
<h2>{TITLE}</h2>
<p />
<!-- END header -->
<!-- BEGIN main -->
<table height="80%" border="1">
<tr>
    <td valign="top" width="25%">
    {MENU}
    </td>
    <td valign="top">
    <b>{HEADLINE}</b>
    <p />
    {CONTENT}
    </td>
    <td valign="top" align="center" width="25%">
    Today's feature: <br />
    - {FEATURE}
    </td>
</tr>
</table>
<!-- END main -->
<!-- BEGIN footer -->
<table width="100%" align="center">
<tr>
    <td align="center"><font size="-2">&copy; Big Company Inc. {YEAR} All rights reserved.</font></td>
</tr>
</table>
</body>
</html>
<!-- END footer -->

Each template may be modified independently of the others, making it easier to alter just the top bar or the content layout, for example.

You can now load each of these files into your PHP script, assign values and merge them to create the result page. Here's a script that does this:

<?php

// include class
require('HTML/Template/IT.php');

// create object
// set template directory
$template = new HTML_Template_IT(".");

// load template
$template->loadTemplateFile("header.tpl");

// parse header block
$template->setCurrentBlock("header");
$template->setVariable("TITLE", "Welcome to Africa!");
$template->parseCurrentBlock();
$template->show();

// parse main block
$template->loadTemplateFile("main.tpl");
$template->setCurrentBlock("main");
$template->setVariable("HEADLINE", "Playing With The Lions");
$template->setVariable("CONTENT", "It's a warm day in the bush, and Zizora, the female lion just wants to play...");
$template->setVariable("MENU", "This is where the menu goes");
$template->setVariable("FEATURE", "None available");
$template->parseCurrentBlock();
$template->show();

// parse footer
$template->loadTemplateFile("footer.tpl");
$template->setCurrentBlock("footer");
$template->setVariable("YEAR", date("Y", mktime()));
$template->parseCurrentBlock();
$template->show();

?>

Nesting Season

HTML_Template_IT also lets you nest one block inside another. Here's a template to illustrate this:

<!-- BEGIN main -->
<html>
<head><basefont face="{FONT}"></head>
<body>
<!-- BEGIN content -->
<h2>{TITLE}</h2>
<img src="{IMG}" align="center">
<br />
{DESC}
<!-- END content -->
</body>
</html>
<!-- END main -->

And here's a script that parses and displays it:

<?php

// include class
require('HTML/Template/IT.php');

// create object
// set template directory
$template = new HTML_Template_IT(".");

// load template
$template->loadTemplateFile("page.tpl");

// set values for inner block
$template->setCurrentBlock("content");
$template->setVariable("TITLE", "Images >> Singapore");
$template->setVariable("IMG", "/images/galleries/singapore/01.jpg");
$template->setVariable("DESC", "Shopping at the biggest mall in Singapore...");
$template->parseCurrentBlock();

// set values for outer block
$template->setCurrentBlock("main");
$template->setVariable("FONT", "Arial");
$template->parseCurrentBlock();

// show page
$template->show();

?>

In this case, values are first assigned to the inner block's variables, and then to the outer block. A call to show() then takes care of displaying the entire page.

Repeating Yourself

This ability to nest templates inside each other makes it easy to create a sequence of repeated items - for example, list items or table elements. Consider the following template, which illustrates the concept:

<html>
<head>
</head>
<body>
<!-- BEGIN list -->
<ul>
<!-- BEGIN list_item -->
<li>{ITEM}
<!-- END list_item -->
</ul>
<!-- END list -->
</body>
</html>

Here, there are two blocks, one nested inside the other. The inner block is a list element, and it's this block which I have to repeat over and over again to create a complete list. How does one do this? Conceptually, it's not difficult to understand: assign a value to the variable in the inner block, parse it once, assign a value to it again, parse it again, append it to the output of the previous run, and repeat until the list is complete.

Here is the code to do just this:

<?php

// include class
require('HTML/Template/IT.php');

// create object
// set template directory
$template = new HTML_Template_IT(".");

// load template
$template->loadTemplateFile("list.tpl");

// set values for inner block
$template->setCurrentBlock("list_item");
$template->setVariable("ITEM", "Item A");
$template->parseCurrentBlock();
$template->setVariable("ITEM", "Item B");
$template->parseCurrentBlock();
$template->setVariable("ITEM", "Item C");
$template->parseCurrentBlock();
$template->setVariable("ITEM", "Item D");
$template->parseCurrentBlock();

// parse outer block
$template->parse("list");

// render page
$template->show();

?>

Most of this should be familiar to you by now. Multiple calls to parseCurrentBlock() take care of replacing the template variable with a value and then appending the result to the output of the previous run. Once all the variables have been added to the inner block, the parse() method is used to parse the outer block, and the show() method is used to display the complete page.

No News is Good News

Now that you know how HTML_Template_IT works, let's take it for a test drive in the real world. Assume here that I want to build a web page listing news headlines and containing links to other sections of the site. The headlines and news summaries will come from a MySQL database, while the links will be hardwired into the script.

Here's what I want my page to look like:

Yes, I know I won't be winning any interface design awards this year. But hey, it sure loads fast...

It's obvious that there are two blocks of repeating elements in the page above. The first block is for the news headline and its summary, and the second is the block for a link in the right menu bar. Given this, it's easy to design a simple template for the page above:

<html>
<head></head>
<body>
<!-- this is a two-cell table
one cell is for the headlines
the second is for the links on the right -->
<table>
<tr>
<td valign="top">
<h2>Latest Headlines</h2>

<!-- this block is for the headline and its summary -->
<!-- BEGIN item -->
<a href="fullstory.php?id={ID}">{TITLE}</a>
<br />
{SUMMARY}
<p />
<!-- END item -->

</td>
<td valign="top">
<h2>Quick Links</h2>
<ul>

<!-- this block is for a category and its URL -->
<!-- BEGIN link -->
<li><a href="{CATEGORY_URL}">{CATEGORY_NAME}</a>
<!-- END link -->

</ul>
</td>
</tr>
</table>
</body>
</html>

Once the template is set up, I need a PHP script that will connect to the database and retrieve the latest headlines and summaries. I can then iterate over the result set to generate a repeating block of news headlines and summaries, in the same way I iteratively built a list in the previous example. Once the headlines are done, I can use the same technique to built the list of quick links, from an array.

Here's a script that performs these tasks:

<?php

// array of links and URLs
// this is hardwired here for demo purposes
$links = array(
    'Sports' => '/site/content/index.php?category=8584',
    'Entertainment' => '/site/content/index.php?category=364',
    'Finance' => '/site/content/index.php?category=1209',
    'Politics' => '/site/content/index.php?category=13',
    'Lifestyle' => '/site/content/index.php?category=1745'
);

// include class
require('HTML/Template/IT.php');

// create object
// set template directory
$template = new HTML_Template_IT(".");

// load template
$template->loadTemplateFile("index.tpl");

// now set up database link
// set server access variables
$host = "localhost";
$user = "user";
$pass = "pass";
$db = "mydb";

// open connection to database
$connection = mysql_connect($host, $user, $pass) or die("Unable to connect!");

// select database to use
mysql_select_db($db) or die("Unable to select database!");

// create SQL query string
$query = "SELECT * FROM news ORDER BY date DESC LIMIT 0, 10";

// execute query and obtain result set
$result = mysql_query($query) or die("Error in query: $query. " . mysql_error());

// are there any rows in the result?
if (mysql_num_rows($result) > 0) {
    // yes
    // create list of news items and summaries
    $template->setCurrentBlock("item");
    while ($row = mysql_fetch_object($result)) {
        $template->setVariable("TITLE", $row->title);
        $template->setVariable("SUMMARY", $row->synopsis);
        $template->setVariable("ID", $row->id);
        $template->parseCurrentBlock();
    }
} else {
    // no
    // place an error message in the template variables instead of data
    $template->setCurrentBlock("item");
    $template->setVariable("SUMMARY", "No data available");
    $template->parseCurrentBlock();
}

// close connection
mysql_close($connection);

// now handle the link bar
$template->setCurrentBlock("link");

// iterate over links array
// set link name and URL
foreach ($links as $k => $v) {
    $template->setVariable("CATEGORY_NAME", $k);
    $template->setVariable("CATEGORY_URL", $v);
    $template->parseCurrentBlock();
}

// once all the inner blocks are done
// parse the outer block and show() it
$template->parse();
$template->show();

?>

The first part of this script is focused solely on extracting information to display from a database. Once a result set is obtained, the script assigns values to the "TITLE", "SUMMARY" and "ID" variables in the first block, repeating until all the records in the result set are processed. Once that's done, the various link boxes are built up by iterating over the $links array and assigning values to the "CATEGORY_NAME" and "CATEGORY_URL" template variables. At each stage, the parseCurrentBlock() method is used to append the output of the current run to that of the previous run, thus iteratively building the page from the individual template blocks.

Once both the headline and link blocks are generated, the entire page is parsed and displayed with show(). Here's what it looks like:

The best part? You can futz all you want with the interface, and the PHP code will still remain valid, with no requirement to retest it when you change the layout of the page. Thus, HTML_Template_IT makes it possible to separate the user interface from the program logic, allowing designers with little or no programming knowledge to alter web pages quickly and easily. And with its ability to nest and repeat chunks of HTML code, it can speed up development time and reduce the effort involved in modifying a web application.

This article was first published on 25 Jun 2004.