Welcome To The Jungle
If you've been programming for a while, you probably already know the basic function of a template engine: to separate presentation and layout information from program code in an application. This separation lets designers and developers work independently on the form and function of an application, and it can substantially reduce the time and effort required in the maintenance phase of a project.
There's no shortage of PHP-based template engines out there: Smarty, Template_IT, Xipe and patTemplate are just some of the names that spring to mind. In that sense, the template engine discussed in this article, Flexy, should just be considered one of many. But it isn't - as you'll shortly see, although Flexy isn't as feature-rich or as well-documented as some of the other options available, it's lightweight, fast and comes with some unique features that will endear it to most developers.
Over the course of this article, I'll introduce you to some of these features, in the hope that it will encourage you to use Flexy in your next development effort. So come on in, and let's get cracking!
Knock, Knock
This tutorial makes a couple of assumptions. First, it assumes that you know the basics of PHP and database programming, and you have at least a passing familiarity with using objects and object methods in PHP. Second, it assumes that you have an Apache/PHP/MySQL development environment already set up, and that you've managed to successfully install the Flexy package, as well as its dependencies. For your reference, the Flexy package is freely available from http://pear.php.net/package/HTML_Template_Flexy, and is currently maintained by Alan Knowles.
I should also mention at this point that the Flexy package was originally written for PHP 4.x. As a result, you might need to set PHP's error reporting level to ignore warnings and notices thrown by the package under PHP 5.x. Don't worry too much about this; in my usage, I found the code stable under both PHP 4.x and PHP 5.x.
With the caveats out of the way, let's get started with the basics. In Flexy-lingo, a "template" is simply a text file, typically containing both static HTML elements and template variables. Flexy works by replacing these variables with actual values before rendering the HTML page. The values for these variables are dynamically set inside your PHP script or page controller, from a database or other data source.
To see how this works, create a simple, illustrative HTML page, as below, and save it as templates/question.tmpl in your working directory:
<html>
<head></head>
<body>
<h2>{question}</h2>
<h3>{answer}</h3>
</body>
</html>
This template doesn't yet contain any useful content; rather, it contains two template variables, $question and $answer, which serve as placeholders for data. By convention, these template variables must be enclosed in curly braces. Once the Flexy engine parses this template, these variables will be replaced with actual values.
Here's the PHP code that turns this template into a "real" page:
<?php
// change error reporting level
error_reporting(E_ALL & ~E_NOTICE);
// include class
require ('HTML/Template/Flexy.php');
// set class configuration
$options = array(
'templateDir' => './templates',
'compileDir' => './templates_c',
);
// initialize template engine
$flexy = new HTML_Template_Flexy($options);
// define object and properties
// corresponding to template variables
$flexy->setData('question', 'Knock, knock!');
$flexy->setData('answer', 'Who\'s there?');
// compile template and interpolate object
// display result
$flexy->compile("question.tmpl");
$flexy->output();
?>
And here's the output:
There's a fairly standard sequence to rendering a template using Flexy:
- Create an instance of the HTML_Template_Flexy object, and configure it. Typically, this configuration consists of an associative array of option-value pairs, which is passed to the object constructor. Flexy supports many different options; the two most important ones are 'templateDir', which defines the disk path to the template files, and 'compileDir', which defines the path to the compiled templates. In most cases, if these directories don't already exist, Flexy will create them for you.
- Assign values to the template variables. There are various ways to do this; the script above uses the simplest, the setData() method, which accepts two arguments: the name of the template variable, and the value it should be assigned.
- Compile the template and display the output. Once values have been assigned to the template variables, the final step is to compile the template, replace the variables within it with actual values, and display the output. This is accomplished via the compile() and output() methods; notice that the compile() method is passed the name of the template file to use.
An alternative here is to use the toString() method instead the output() method; this retrieves the interpolated version of the template and assigns it to a variable instead of displaying it, and comes in handy if you're working with complex template trees or simply want to perform further processing on the output page before displaying it. Try it for yourself and see.
Joking Around
The previous example demonstrated the simplest way to use Flexy. However, one of Flexy's unique features is that it can directly map an object's properties to template variables, without requiring setData() to be manually invoked for each variable. This approach comes in handy when your application is built on OOP principles, as in the following revision of the previous example:
<?php
// change error reporting level
error_reporting(E_ALL & ~E_NOTICE);
// include class
require ('HTML/Template/Flexy.php');
class Page {
// constructor
public function __construct() {
// set template options
$options = array(
'templateDir' => './templates',
'compileDir' => './templates_c',
);
// initialize template engine
$this->flexy = new HTML_Template_Flexy($options);
}
// compile and display page
public function display($tmpl, $obj) {
$this->flexy->compile($tmpl);
$this->flexy->outputObject($obj);
}
}
class Joke {
function __construct($question, $answer) {
$this->question = $question;
$this->answer = $answer;
}
}
// instantiate objects
$joke = new Joke('Knock knock!', 'Who\'s there?');
$page = new Page;
// pass object to template engine
// for variable interpolation
$page->display('question.tmpl', $joke);
?>
This listing creates a Joke object, passing the object constructor a question and answer; these values can be accessed using the $Joke->question and $Joke->answer properties respectively. This object is then passed to the Page object's display() method, which internally calls HTML_Template_Flexy object's outputObject() method to read the object's properties and map them directly to template variables. As a result, the template variables $question and $answer are populated with the values of $Joke->question and $Joke->answer, with no manual assignment needed.
The output of this script is equivalent to the previous one:
[image]image1.gif[/image]
Fortune Favours The Brave
Flexy templates also support the if conditional test, via {if} and {end} blocks. This test makes it easy to display different output depending on how a particular, user-defined condition is evaluated. To illustrate this feature, consider the following template, which displays a different fortune cookie depending on the day of the week:
<html>
<head></head>
<body>
<h2>Today's Fortune</h2>
{if:monday}
Never make anything simple and efficient when a way can be
found to make it complex and wonderful.
{end:}
{if:tuesday}
Life is a game of bridge -- and you've just been finessed.
{end:}
{if:wednesday}
What sane person could live in this world and not be crazy?
{end:}
{if:thursday}
Don't get mad, get interest.
{end:}
{if:friday}
Just go with the flow control, roll with the crunches,
and, when you get a prompt, type like hell.
{end:}
{if:saturday}
Used staples are good with soy sauce.
{end:}
{if:sunday}
Sorry, closed on the weekend.
{end:}
</body>
</html>
And here's the PHP script that sets one of these variables to true, depending on the current day of the week:
<?php
// change error reporting level
error_reporting(E_ALL & ~E_NOTICE);
// include class
require ('HTML/Template/Flexy.php');
// set class configuration
$options = array(
'templateDir' => './templates',
'compileDir' => './templates_c',
);
// initialize template engine
$flexy = new HTML_Template_Flexy($options);
// define object and properties
// corresponding to template variables
$page = new stdClass;
$day = strtolower(date('l', mktime()));
$page->$day = true;
// compile template and interpolate object
// display result
$flexy->compile("fortune.tmpl");
$flexy->outputObject($page);
?>
In this script, the date() function is used to return the current day of the week and this, in turn, becomes a property of the object passed to the outputObject() method. So, if today happens to be a Monday, the property $object->monday will be set, and will be used by the conditional tests within the template to display the fortune cookie corresponding to Monday. As before, the fortune cookies in the template can be updated independently of the PHP code.
Here's what the output looks like:
In addition to the standard {if} block, Flexy also supports an {else} block, which can be used if your template needs to make a logical decision between two branches. Here's an example:
<html>
<head></head>
<body>
{if:formSubmitted}
{if:loginSuccess}
<h2>Welcome</h2>
You have successfully logged in!
{else:}
<h2>Access Denied</h2>
Your username or password was incorrect. Please try again.
{end:}
{else:}
<h2>Log In</h2>
<form method="post" action="login.php">
Username: <br/> <input type="text" name="username"/>
<p/>
Password: <br/> <input type="password" name="password"/>
<p/>
<input type="submit" name="submit" value="Log In"/>
</form>
{end:}
</body>
</html>
Here, depending on the values of $object->formSubmitted, Flexy will either display or hide the log-in form. Similarly, depending on the value of $object->loginSuccess, Flexy will either display a welcome message or a failure notification.
Here's the PHP code that goes with the previous template:
<?php
// change error reporting level
error_reporting(E_ALL & ~E_NOTICE);
// include class
require ('HTML/Template/Flexy.php');
// set class configuration
$options = array(
'templateDir' => './templates',
'compileDir' => './templates_c',
);
// initialize template engine
$flexy = new HTML_Template_Flexy($options);
// define object and properties
// corresponding to template variables
$page = new stdClass;
$page->loginSuccess = false;
$page->formSubmitted = false;
// perform some authentication here
// set 'loginSuccess' to true or false
// depending on the authentication results
if (isset($_POST['submit'])) {
$page->formSubmitted = true;
$username = $_POST['username'];
$password = $_POST['password'];
// dummy authentication for illustrative purposes
if ($username == 'john' && $password == 'doe') {
$page->loginSuccess = true;
}
}
// compile template and interpolate object
// display result
$flexy->compile("login.tmpl");
$flexy->outputObject($page);
?>
Looping The Loop
Why stop with conditionals? Flexy supports PHP's foreach() loop via its {foreach} construct, making it possible to create repeating blocks within your template. Here's a simple example, which sets up an array in the PHP script:
<?php
// change error reporting level
error_reporting(E_ALL & ~E_NOTICE);
// include class
require ('HTML/Template/Flexy.php');
// set class configuration
$options = array(
'templateDir' => './templates',
'compileDir' => './templates_c',
);
// initialize template engine
$flexy = new HTML_Template_Flexy($options);
// define object and properties
// corresponding to template variables
$page = new stdClass;
$page->items = array(
'Eggs',
'Bread',
'Milk',
'Bacon',
'Cornflakes'
);
// compile template and interpolate object
// display result
$flexy->compile("list.tmpl");
$flexy->outputObject($page);
?>
And here's the template, which generates an HTML list from the array using {foreach}:
<html>
<head></head>
<body>
<h2>My Shopping List</h2>
<ul>
{foreach:items,i}
<li>{i}</li>
{end:}
</ul>
</body>
</html>
Here's the output:
You can also pass a Flexy template an array of objects and iterate over this array within a {foreach} block, accessing properties of each object using {object.property} notation. Consider the following example, which passes the template an array of SalesRecord objects, each with location and amount properties:
<?php
// change error reporting level
error_reporting(E_ALL & ~E_NOTICE);
// include class
require ('HTML/Template/Flexy.php');
// set class configuration
$options = array(
'templateDir' => './templates',
'compileDir' => './templates_c',
);
// initialize template engine
$flexy = new HTML_Template_Flexy($options);
// define object and properties
// corresponding to template variables
$page = new stdClass;
$page->records = array(
new SalesRecord('San Francisco', '1859'),
new SalesRecord('Los Angeles', '8492'),
new SalesRecord('Washington', '584'),
new SalesRecord('Seattle', '1984'),
new SalesRecord('Chicago', '8473'),
new SalesRecord('New York', '8483'),
new SalesRecord('Dallas', '4949')
);
// compile template and interpolate object
// display result
$flexy->compile("table.tmpl");
$flexy->outputObject($page);
// data object class
class SalesRecord {
function __construct($location, $amount) {
$this->location = $location;
$this->amount = $amount;
}
}
?>
Here's the template, which iterates over the array of SalesRecord objects and prints each one's location and amount:
<html>
<head></head>
<body>
<table>
{foreach:records,r}
<tr>
<td>{r.location}</td>
<td>{r.amount}</td>
</tr>
{end:}
</table>
</body>
</html>
And here's what the output looks like:
Data Dump
Flexy's support for loops and objects make it a snap to integrate the results of a database query into your output template. To illustrate, consider the following cutaway of a PHP class, which queries a MySQL database and retrieves a list of country records as a collection of objects:
<?php
// data object class
class Countries {
public function get() {
// attempt a connection
try {
$dbh = new PDO('mysql:dbname=world;host=localhost', 'user', 'pass');
} catch (PDOException $e) {
echo "Error: Could not connect. " . $e->getMessage();
}
// set error mode
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// attempt some queries
try {
// execute SELECT query
$sql = "SELECT Name AS name, Population AS pop FROM country LIMIT 0,20";
$sth = $dbh->query($sql);
while ($row = $sth->fetchObject()) {
$this->records[] = $row;
}
} catch (Exception $e) {
echo "Error: Could not execute query \"$sql\". " . $e->getMessage();
unset($dbh);
}
// close connection, clean up
unset($dbh);
}
}
?>
Here, each record in the result set is retrieved as an object using PDO's fetchObject() method. Field values are mapped to object properties, and the entire object is then placed in the Countries.records array. This Countries object can then be passed to the outputObject() method for interpolation into a template, as below:
Here's how you'd use this class:
<?php
// change error reporting level
error_reporting(E_ALL & ~E_NOTICE);
class Page {
// constructor
public function __construct() {
// set options
$options = array(
'templateDir' => './templates',
'compileDir' => './templates_c',
);
// include class
require ('HTML/Template/Flexy.php');
// initialize template engine
$this->flexy = new HTML_Template_Flexy($options);
}
// compile and render template
public function display($tmpl, $obj) {
$this->flexy->compile($tmpl);
$this->flexy->outputObject($obj);
}
}
$page = new Page;
$countries = new Countries;
$countries->get();
$page->display('countries.tmpl', $countries);
?>
And here's what the template contains:
<html>
<head>
<style type="text/css">
table {
border-collapse: collapse;
width: 320px;
}
tr.heading {
font-weight: bolder;
}
td {
border: 1px solid black;
padding: 0 0.5em;
}
</style>
</head>
<body>
<table>
<tr class="heading">
<td>Country</td>
<td>Population</td>
</tr>
{foreach:records,r}
<tr>
<td>{r.name}</td>
<td>{r.pop}</td>
</tr>
{end:}
</table>
</body>
</html>
This template uses {object.property} notation to iterate over the objects stored in the Countries.records array, and print each object's 'name' and 'pop' properties. Here's what the output page looks like:
Of Form And Function
Flexy also comes with additional features specifically for working with Web forms. With Flexy, it's possible to programmatically define form and element attributes elements and set default values for form elements. This is accomplished by creating new instances of the HTML_Template_Flexy_Element class (one for each element), setting attributes and values for these objects, and passing an array of these objects to the outputObject() method as an additional argument.
To illustrate, consider the following form template, which sets up the base framework for a pizza order:
<html>
<head></head>
<body>
<h2>Create Your Pizza</h2>
{if:formSubmitted}
Thank you for your submission!
{else:}
<form name="pform">
Pizza name:
<br/>
<input name="pname"/>
<p/>
Pizza base:
<br/>
<input type="radio" name="pbase" id="pbase_thin" value="thin">Thin</input>
<input type="radio" name="pbase" id="pbase_thick" value="thick">Thick</input>
<p/>
Pizza toppings:
<br/>
<input type="checkbox" name="ptoppings[]" value="bacon">Bacon</input>
<input type="checkbox" name="ptoppings[]" value="tomato">Tomato</input>
<input type="checkbox" name="ptoppings[]" value="ham">Ham</input>
<input type="checkbox" name="ptoppings[]" value="olives">Olives</input>
<input type="checkbox" name="ptoppings[]" value="pepperoni">Pepperoni</input>
<p/>
Comments:
<br/>
<textarea name="comments"></textarea>
<p/>
<input type="submit" name="submit" value="Submit"/>
</form>
{end:}
</body>
</html>
And here's a PHP script, which uses Flexy to set default values for the elements in this form:
<?php
// change error reporting level
error_reporting(E_ALL & ~E_NOTICE);
// include class
require ('HTML/Template/Flexy.php');
require ('HTML/Template/Flexy/Element.php');
// set class configuration
$options = array(
'templateDir' => './templates',
'compileDir' => './templates_c',
);
// initialize template engine
$flexy = new HTML_Template_Flexy($options);
// define object and properties
// corresponding to template variables
$page = new stdClass;
$elements = array();
if (isset($_POST['submit'])) {
$page->formSubmitted = true;
} else {
$page->formSubmitted = false;
// set the form element
$elements['pform'] = new HTML_Template_Flexy_Element;
$elements['pform']->attributes['method'] = 'post';
$elements['pform']->attributes['action'] = $_SERVER['PHP_SELF'];
// create and pre-fill the input element
$elements['pname'] = new HTML_Template_Flexy_Element;
$elements['pname']->attributes['size'] = 35;
$elements['pname']->setValue('Enter your pizza name here');
// create and pre-fill the radio element
$elements['pbase'] = new HTML_Template_Flexy_Element;
$elements['pbase']->setValue('thick');
// create and pre-fill the checkbox element
$elements['ptoppings[]'] = new HTML_Template_Flexy_Element;
$elements['ptoppings[]']->setValue(array('bacon', 'olives'));
// create and pre-fill the textarea element
$elements['comments'] = new HTML_Template_Flexy_Element;
$elements['comments']->attributes['rows'] = 10;
$elements['comments']->attributes['cols'] = 40;
$elements['comments']->setValue('Enter your comments here');
}
// compile template and interpolate object
// display result
$flexy->compile("pizza.tmpl");
$flexy->outputObject($page, $elements);
?>
To work with form elements using Flexy, create instances of HTML_Template_Flexy_Element for each element you wish to modify. Each of these instances exposes an 'attributes' property, which can be used to set attributes of the corresponding form element, and a setValue() method, which can be used to set the element's default value (note that when setting the value for checkboxes, the input argument to setValue() must be an array of checked items, rather than a single string).
The HTML_Template_Flexy_Element objects created in this manner are then placed in an associative array, with the array key in each case corresponding to the element's 'name' in the output form. This array is then passed to Flexy's outputObject() method as an additional argument. When the template is compiled and rendered, the HTML_Template_Flexy_Element objects will be automatically merged with the existing form elements in the template to produce a composite result.
Here's what the form generated by the previous script looks like:
Adding Flexy-bility
In previous examples, you've seen two of the configuration options passed to Flexy: 'templateDir' and 'compileDir'. However, Flexy is particularly versatile and comes with numerous other options as well. Here's a brief list of the most useful ones:
| Option | Description |
|---|---|
| 'debug' | Prints debugging messages |
| 'allowPHP' | Process PHP code embedded in template |
| 'url_rewrite' | Automatically rewrite specified URLs |
| 'globals' | Allow usage of PHP super-global variables within template |
| 'strict' | Display PHP notices for undefined template variables |
| 'fatalError' | Die or return a PEAR_Error object on fatal errors |
Here's an example which demonstrates some of these options in action:
<?php
// change error reporting level
error_reporting(E_ALL & ~E_NOTICE);
// include class
require ('HTML/Template/Flexy.php');
// set class configuration
$options = array(
'templateDir' => './templates',
'compileDir' => './templates_c',
'globals' => true,
'strict' => true,
'fatalError' => HTML_TEMPLATE_FLEXY_ERROR_DIE,
'url_rewrite' => 'HERE/:' . dirname($_SERVER['PHP_SELF']) . '/',
'allowPHP' => true,
'debug' => false
);
// initialize template engine
$flexy = new HTML_Template_Flexy($options);
// define object and properties
// corresponding to template variables
$page = new stdClass;
$page->records = array(
new SalesRecord('San Francisco', '1859'),
new SalesRecord('Los Angeles', '8492'),
new SalesRecord('Washington', '23785'),
new SalesRecord('Seattle', '1984'),
new SalesRecord('Washington', '8473'),
new SalesRecord('New York', '8483'),
new SalesRecord('Dallas', '4949')
);
// define some global variables
// colors for odd and even table rows
$GLOBALS['odd'] = 'silver';
$GLOBALS['even'] = 'white';
// compile template and interpolate object
// display result
$flexy->compile("table.tmpl");
$flexy->outputObject($page);
// data object class
class SalesRecord {
function __construct($location, $amount) {
$this->location = $location;
$this->amount = $amount;
}
}
?>
A common requirement when working with tabular data, such as that generated by the previous example, involves formatting odd- and even-numbered rows differently. Unfortunately, there's no easy way to do this with Flexy, as the engine doesn't natively support loop counters or arithmetic operators (both of which are required to determine if a particular row of a table is odd or even). However, there's still a way out - consider the following template, which illustrates:
<html>
<head>
<style type="text/css">
.odd {
background: {GLOBALS[odd]};
}
.even {
background: {GLOBALS[even]};
}
</style>
</head>
<body>
<table>
{foreach:records,r}
<?php $count++; ?>
<tr class="<?php echo ($count%2) ? 'odd' : 'even'; ?>">
<td>{r.location}</td>
<td>{r.amount}</td>
</tr>
{end:}
</table>
<a href="HERE/flexy4.php">refresh</a>
</body>
</html>
Here's what the output looks like:
This template-and-PHP-script-combination has some points of note:
- This template contains embedded PHP code to automatically count loop iterations and determine if a particular row is odd or even. The appropriate style is then applied to the corresponding table row, to format odd- and even-numbered rows differently. This is only possible because of the 'allowPHP' configuration option.
- The particular styles to use for odd and even rows are imported from the $GLOBALS superglobal array. Flexy is able to read these values because of the 'globals' configuration option, set to true in this case.
- The template constant HERE is automatically replaced by the path to the script on the Web server, using Flexy's 'url_rewrite' configuration option.
And that's about all we have time for. As these examples illustrate, although Flexy has a simple API, it is nevertheless a fairly powerful template engine, suitable for deployment in small-to-medium Web applications. And with its support for conditional tests, loops and object mapping, it can speed up development time and reduce the effort involved in maintaining a Web application. Take a look at it sometime, and see for yourself!
Copyright Melonfire 2008. All rights reserved.

Comments
<table>
<tr><td><flexy:include src="left.html"></td>
<td><flexy:include src="right.html"></td>
</tr>
</table>
The tag is pretty self explanatory. Another thing I've gotten hung up on is getting dynamic attributes into html tags. Because flexy parses your HTML for validity the following construct
<input type=checkbox name="check1" {var_checked}>
Isn't allowed as the pre-substitution source isn't valid. The alternative is the flexy:raw directive like
<input type=checkbox name="check1" flexy:raw="{var_checked}">
Which will do the same thing but look like valid html to the parser.
Also - you didn't mention the best thing about flexy: auto html escaping. Vars by default are html-entitied on output so by default you are protected from XSS attacks. Use {var:h} if you really have to output html in a variable...
<flexy:toJavascript jsVar="phpVar" />
http://pear.php.net/manual/en/package.html.html-template-flexy.attribute.tojavascript.php