Making A Choice
Using templates to separate the “functional” parts of a Web application – the business logic and code – from the “pretty” parts – the user interface – is generally considered a good thing. This separation lets designers and developers work on the application independently, without getting in each other’s hair, and it can substantially reduce the time and effort required for maintenance once the application goes live.
Now, there’s no shortage of template engines out there. You’ve probably already heard of Smarty, Template_IT, Flexy and Xipe…and that’s just the beginning of the list. If you’re looking for something a little different, though, consider Savant, the subject of this article. Although Savant isn’t as well-known as some of the other options available, it’s still a lightweight, OOP-compliant template engine that offers some unique features and is well worth a look.
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 Savant 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 a reasonably-good understanding of 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 Savant. If you’re using the automated PEAR installer, you can install Savant simply by running the following commands at your command prompt:
shell> pear channel-discover phpsavant.com shell> pear install savant/Savant3
Alternatively, you can manually install the Savant package, by downloading it from its official Web site. Savant is currently maintained by Paul Jones and Brett Bieber and this article uses Savant v3.0.1.
Assuming you’ve got Savant installed, let’s get started with the basics. In Savant-lingo, a “template” is a text file, typically containing both static HTML elements and template variables. Savant 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 HTML page, as below, and save it as templates/knock.tpl in your working directory:
<html>
<head></head>
<body>
<h1>Knock! Knock!</h1>
<h2>Who's there?</h2>
<h2><?php echo $this->answer; ?></h2>
<h2><?php echo $this->answer; ?> who?</h2>
<h2><?php echo $this->punchline; ?></h2>
</body>
</html>
This template doesn’t yet contain any useful content; rather, it contains two template variables, $this->answer and $this->punchline, which serve as placeholders for data. These template variables are represented as properties of the Savant template object. Once Savant 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
// include Savant class file
require_once 'Savant3.php';
// initialize template engine
$savant = new Savant3();
// assign template variables
$savant->answer = 'Goat';
$savant->punchline = 'Goat to the door and find out.';
// interpolate variables and display template
$savant->display('templates/knock.tpl');
?>
And here’s the output:

There’s a fairly standard sequence to rendering a template using Savant:
1. Create an instance of the Savant template object.
2. Create and assign values to template variables. With Savant, this is done simply by defining new object properties corresponding to template variable names, and assigning values to them.
3. Render the template . Once values have been assigned to the template variables, the final step is to render the template by interpolating the correct values into it and printing the output. This is accomplished by calling the Savant object’s display() method, and passing it the the name of the template file to use.
An alternative here is to use the fetch() method instead of the display() 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. You’ll see an example of this method in action a little further along.
Fortune Cookies
As the previous example illustrated, Savant templates can make use of regular PHP code and statements. This also means that you can use standard if() and switch() conditional tests inside a template, without learning a Savant-specific template language (as many other template engines require). To illustrate, consider the following template, which uses a switch-case statement to display a different fortune cookie depending on the day of the week:
<html>
<head></head>
<body>
<h2>Today's Fortune</h2>
<?php
switch ($this->day) {
case 'monday':
echo 'Never make anything simple and efficient when a way can be found to make it complex and wonderful.';
break;
case 'tuesday':
echo 'Life is a game of bridge -- and you\'ve just been finessed.';
break;
case 'wednesday':
echo 'What sane person could live in this world and not be crazy?';
break;
case 'thursday':
echo 'Don\'t get mad, get interest.';
break;
case 'friday':
echo 'Just go with the flow control, roll with the crunches, and, when you get a prompt, type like hell.';
break;
case 'saturday':
echo 'Used staples are good with soy sauce.';
break;
case 'sunday':
echo 'Sorry, closed on the weekend.';
break;
}
?>
</body>
</html>
Here’s the PHP script that uses this template:
<?php
// include class
require 'Savant3.php';
// initialize template engine
$savant = new Savant3();
// set search path for templates
$savant->addPath('template', './templates');
// assign template variables
$day = strtolower(date('l', mktime()));
$savant->day = $day;
// interpolate variables and display template
$savant->display('fortune.tpl');
?>
This script uses PHP’s date() function to determine the current day of the week, and assigns that value to an object property. When the value is interpolated into the template, the switch-case block selects the corresponding fortune for display. So, if today happens to be a Monday, the property $savant->day will be set to ‘monday’, 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 might look like:

Notice that this script makes use of one additional method, the addPath() method, which can be used to define one or more directories Savant should look in to find the specified template. You can also specify this information at the time of initializing the template object, by passing the object constructor an associative array of configuration options. Here’s a variant of the script above that demonstrates:
<?php
// include class
require 'Savant3.php';
// set options
$options = array(
'template_path' => './templates'
);
// initialize template engine
$savant = new Savant3($options);
// assign template variables
$day = strtolower(date('l', mktime()));
$savant->day = $day;
// interpolate variables and display template
$savant->display('fortune.tpl');
?>
Just as you can use conditional tests in a Savant template, so too can you use loops. To illustrate, consider this next template, which makes use of a foreach() loop to iterate over an array of list items:
<html>
<head></head>
<body>
<h2>My Shopping List</h2>
<ul>
<?php foreach ($this->list as $item): ?>
<li><?php echo $item; ?></li>
<?php endforeach; ?>
</ul>
</body>
</html>
And here’s the PHP script that sets up the array and handles the variable interpolation:
<?php
// include class
require 'Savant3.php';
// set options
$options = array(
'template_path' => './templates'
);
// initialize template engine
$savant = new Savant3($options);
// assign template variables
$savant->list = array(
'Wing of bat',
'Eye of newt',
'Hair of dog',
'Leg of spider',
'Toe of frog'
);
// interpolate variables and render main template
$savant->display('list.tpl');
?>
Here’s what the output looks like:

For King And Country
Savant’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 getAll() {
// 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 ORDER BY pop DESC 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);
return $this->records;
}
}
?>
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 $obj->records array. This object can then be interpolated into a template, as below:
<?php
// include class
require 'Savant3.php';
// set options
$options = array(
'template_path' => './templates'
);
// initialize template engine
$savant = new Savant3($options);
// get country list
$countries = new Countries;
$savant->countries = $countries->getAll();
// interpolate into main template
$savant->display('countries.tpl');
?>
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>
<?php foreach ($this->countries as $c): ?>
<tr>
<td><?php echo $c->name; ?></td>
<td><?php echo $c->pop; ?></td>
</tr>
<?php endforeach; ?>
</table>
</body>
</html>
This template iterates over the objects stored in the $savant->countries array, and print each object’s ‘name’ and ‘pop’ properties. Here’s what the output looks like:

African Adventure
One of Savant’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 extract them into two separate templates, as follows:
<!-- header.tpl -->
<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>
<h2><?php echo $this->eprint($this->title); ?></h2>
<!-- footer.tpl -->
<table width="100%" align="center">
<tr>
<td align="center">
<font size="-2">Copyright Big Company Inc. <?php echo $this->eprint($this->year); ?> All rights reserved.</font>
</td>
</tr>
</table>
</body>
</html>
The template containing the page body can then reference these header and footer templates, as below:
<!-- BEGIN header -->
<?php echo $this->header; ?>
<!-- END header -->
<!-- BEGIN main -->
<table width="100%" height="80%" border="1">
<tr>
<td valign="top" width="25%">
<h2>Menu</h2>
<ul>
<?php foreach ($this->submenu as $k => $v): ?>
<li><a href="<?php echo $this->eprint($k); ?>"><?php echo $this->eprint($v); ?></a></li>
<?php endforeach; ?>
</ul>
</td>
<td valign="top">
<h2><?php echo $this->eprint($this->headline); ?></h2>
<p />
<?php echo $this->eprint($this->content); ?>
</td>
<td valign="top" width="25%">
<h2>Special Features</h2>
<a href="<?php echo $this->eprint($this->featureUrl); ?>"><?php echo $this->eprint($this->feature); ?></a>
</td>
</tr>
</table>
<!-- END main -->
<!-- BEGIN footer -->
<?php echo $this->footer; ?>
<!-- END footer -->
This arrangement makes it easy to modify each template independently of the others, making it possible to alter just the top bar or the content layout, for example.
To render the complete page, the PHP controller script must first render the header and footer templates, capture the result, interpolate this result into the main template and then render the complete page. This two-step process is accomplished with Savant’s fetch() function, which makes it possible to capture the interpolated template result in a PHP variable. Here’s the script:
<?php
// include class
require 'Savant3.php';
// set options
$options = array(
'template_path' => './templates'
);
// initialize template engine
$savant = new Savant3($options);
// assign template variables for various templates
// header
$savant->title = 'Welcome to Africa!';
// footer
$savant->year = date('Y', mktime());
// main
$savant->headline = 'Playing With The Lions';
$savant->content = 'It\'s a warm day in the bush, and Zizora, the female lion just wants to play...';
$savant->feature = 'Video: Two lions fight over a gazelle, with the eventual victor settling down to a feast.';
$savant->featureUrl = 'http://www.example.org';
$savant->submenu = array(
'a74940.html' => 'Lions',
'a65830.html' => 'Tigers',
'a99382.html' => 'Giraffes',
'bush.php' => 'The Bush'
);
// interpolate variables and fetch header and footer
// merge into main
$savant->header = $savant->fetch('header.tpl');
$savant->footer = $savant->fetch('footer.tpl');
// render main template
$savant->display('main.tpl');
?>
Notice also that these templates make use of another Savant object method, eprint(), which takes care of automatically escaping output before printing it. It’s a good idea to wrap all output in calls to this method to reduce your vulnerability to XSS attacks. By default, eprint() escapes output using the htmlspecialchars() function but, as you’ll shortly see, you can add your own custom methods to the mix as well.
Repeating Yourself
This ability to fetch the rendered output of one template and attach it to another also makes it possible to create a sequence of repeated items – for example, list items or table elements. To illustrate, consider the following template:
<!-- row-inner.tpl --> <tr> <td><?php echo $this->eprint($this->country->name); ?></td> <td><?php echo $this->eprint($this->country->pop); ?></td> </tr>
This template consists of a single table row. By rendering this template multiple times, appending the output of each run to the previous one, it’s possible to iteratively build an “inner block” consisting of multiple table rows. This inner block can then be inserted into an “outer block”, such as the page template shown below:
<!-- page-outer.tpl -->
<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>
<?php echo $this->rows; ?>
</table>
</body>
</html>
Here’s the PHP script that does the hard work of combining these two templates:
<?php
// data object class
class Countries {
public function getAll() {
// 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 ORDER BY pop DESC 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);
return $this->records;
}
}
// include class
require 'Savant3.php';
// set options
$options = array(
'template_path' => './templates'
);
// initialize template engine
$savant = new Savant3($options);
// get country list
// iterate over list and build repeated template
// append the output of each run to the previous
$countries = new Countries;
foreach ($countries->getAll() as $c) {
$savant->country = $c;
$savant->rows .= $savant->fetch('row-inner.tpl');
}
// interpolate into main template
$savant->display('page-outer.tpl');
?>
Here, multiple calls to fetch() take care of replacing the template variables in the inner block with corresponding values and then appending the result to the output of the previous run. Once the inner block is complete, it is attached to the outer block and the display() method is used to display the complete page.
Here’s what the output looks like:

Escape Artist
You’ve already seen Savant’s eprint() method in action a couple of pages ago. The eprint() method automatically escapes output using one or more callback functions. By default, Savant is set up to use htmlspecialchars() as the callback for output escaping, but it’s quite easy to add your own custom output escaping function as well.
Here’s some code which illustrates how to do this:
<?php
// include class
require 'Savant3.php';
// set options
$options = array(
'template_path' => './templates'
);
// initialize template engine
$savant = new Savant3($options);
// returns <title>Jack & Jill</title>
echo $savant->escape("<title>Jack & Jill</title>");
// add new escape function
$savant->addEscape(array('customEscape', 'escape_a'));
// returns &<title&>J[a]ck &&[a]mp; Jill&</title&>
echo $savant->escape("<title>Jack & Jill</title>");
// class definition
class customEscape {
function escape_a($str) {
return ereg_replace('a', '[a]', $str);
}
}
?>
This script defines a new customEscape::escape_a() method, which automatically puts square braces around all instances of the letter ‘a’. This custom method is added to Savant’s output escaping system by means of the addEscape() method, which accepts one or more callbacks as arguments. Once these callbacks have been registered, any call to eprint() or escape() will automatically use these callbacks.
To obtain a list of registered callbacks, use the getEscape() method, as shown below:
<?php
// include class
require 'Savant3.php';
// initialize template engine
$savant = new Savant3($options);
// add new escape function
$savant->addEscape(array('customEscape', 'escape_a'));
// list escape functions
print_r($savant->getEscape());
// class definition
class customEscape {
function escape_a($str) {
return ereg_replace('a', '[a]', $str);
}
}
?>
Here’s what the output will display:
Array
(
[0] => htmlspecialchars
[1] => Array
(
[0] => customEscape
[1] => escape_a
)
)
You Say 1, I Say One
Savant also allows you to “filter” output through a custom function before sending it to the output device. Filters can be user-defined functions or static class methods, and they’re added to Savant by means of the addFilter() method.
To illustrate, consider the following simple template:
<?php echo $this->text; ?>
Here’s a trivial example that assigns a value to this template variable and passes it through a custom filter function before rendering it:
<?php
// include class
require 'Savant3.php';
// set options
$options = array(
'template_path' => './templates',
);
// initialize template engine
$savant = new Savant3($options);
// add filter
$savant->addFilters('makeLowerCaseAndGreen');
// set template variable and render template
$savant->text = 'I am an APPLE';
echo $savant->fetch('simple.tpl');
// simple filter
function makeLowerCaseAndGreen($str) {
return "<font color='green'>" . strtolower($str) . "</font>";
}
?>
Here, the user-defined filter changes the output case to lowercase and adds a <font> element around it to make it appear in green. This filter is applied to the output before the template is rendered, producing the following output:

Filters may also be implemented as static methods of a class extending the Savant3_Filter abstract class. Here’s an example of one such filter, which automatically replaces numbers with their word equivalents:
<?php
// create new filter
require 'Savant3/Filter.php';
class Savant3_Filter_num2words extends Savant3_Filter {
public static function filter($buffer) {
$search = array(1,2,3,4,5,6,7,8,9,10);
$replace = array('one','two','three','four','five','six','seven','eight','nine','ten');
$buffer = str_replace($search, $replace, $buffer);
return $buffer;
}
}
?>
Here's a PHP script that uses this filter:
<?php
// include class
require 'Savant3.php';
// set options
$options = array(
'template_path' => './templates',
);
// initialize template engine
$savant = new Savant3($options);
// add filter
$savant->addFilters(array('Savant3_Filter_num2words', 'filter'));
// set template variable and render template
$savant->text = '1 aeroplane, 2 cats and 3 happy children make 4 a great story.';
$savant->display('simple.tpl');
?>
And here’s what the output looks like:

Incidentally, the Savant package includes a Savant3_Filter_trimwhitespace filter, which can be used to automatically remove unnecessary whitespace in a template before sending it to the output device. This is useful to optimize data transmission in a Web application.
Plugging In
Savant also includes a full-featured plugin system that allows developers to add their own functionality to the template engine. As with filters, plugins are created by extending the Savant3_Plugin abstract class, and the default Savant package includes plugins for generating HTML link and image tags, formatting dates and creating HTML attribute sequences.
Plugins can be accessed within templates as methods of the Savant object. Here’s an example of using the ‘ahref’ plugin to generate a link to Google.com:
Click here for <?php echo $this->ahref('http://www.google.com/', 'Google'); ?>. <br/>
And here’s an example of the ‘date’ plugin in action to generate a formatted date string using strftime() conventions:
Today is <?php echo $this->date('today', '%d %B %Y'); ?>. <br/>
Savant also comes with plugins designed specifically for working with Web forms. These plugins make it possible to programmatically define form and element attributes elements and set default values for form elements. These plugins are not included in the base Savant package and must be downloaded separately and then installed to the Savant resources folder.
To illustrate the form plugins in action, consider the following template, which sets up the base framework for a pizza order:
<?php
if ($this->formSubmitted == true) {
echo 'Thank you for your submission';
} else {
// begin form
echo $this->form('begin');
// add a new block
echo $this->form('block', 'begin', 'Basics');
// build text box
$pname = array(
'name' => 'pname',
'type' => 'text',
'label' => 'Pizza name:',
'disable' => false,
);
echo $this->form('element', $pname);
// build radio buttons
$pbase = array(
'name' => 'pbase',
'type' => 'radio',
'label' => 'Pizza base:',
'disable' => false,
'options' => array('thin' => 'Thin', 'thick' => 'Thick'),
);
echo $this->form('element', $pbase);
// end block
echo $this->form('block', 'end');
// add a new block
echo $this->form('block', 'begin', 'Toppings');
// build checkboxes
$ptoppings[] = array(
'name' => 'ptoppings[]',
'type' => 'checkbox',
'label' => 'Bacon',
'options' => bacon,
'require' => false,
);
$ptoppings[] = array(
'name' => 'ptoppings[]',
'type' => 'checkbox',
'label' => 'Tomato',
'options' => 'tomato',
'require' => false,
);
$ptoppings[] = array(
'name' => 'ptoppings[]',
'type' => 'checkbox',
'label' => 'Olives',
'options' => 'olives',
'require' => false,
);
$ptoppings[] = array(
'name' => 'ptoppings[]',
'type' => 'checkbox',
'label' => 'Pepperoni',
'options' => 'pepperoni',
'require' => false,
);
foreach ($ptoppings as $t) {
echo $this->form('element', $t);
}
// end block
echo $this->form('block', 'end');
// build submit and reset buttons
// add a new group
echo $this->form('group', 'begin');
$submit = array(
'type' => 'submit',
'name' => 'submit',
'value' => 'Save'
);
echo $this->form('element', $submit);
$reset = array(
'type' => 'reset'
);
echo $this->form('element', $reset);
// end group
echo $this->form('group', 'end');
// end form
echo $this->form('end');
}
?>
Savant comes with plugins for both individual form elements, such as text inputs and checkboxes, and for integrated form management and design. The template above uses the ‘form’ plugin, which falls into the latter category. This plugin provides a “one-stop shop” for form implementation by supporting not only the basic form elements, but additional functions for row- and column-based display, element groups, blocks, and form layout with CSS.
From the code above, it should be clear that the first argument to the form() method is a string indicating the type of construct required, whether ‘element’, ‘group’ or ‘block’. The arguments that follow differ depending on the construct, and are generally provided as an array of option-value pairs that contain enough information to construct the element. For example, consider the following snippet, which contains the options needed for a text box:
<?php
// build text box
$pname = array(
'name' => 'pname',
'type' => 'text',
'label' => 'Pizza name:',
'disable' => false,
);
echo $this->form('element', $pname);
?>
For a complete list of options applicable to each element type, as well as to groups and blocks, take a look at the wiki page.
Here’s what the output looks like:

Making An Exception
In previous examples, you’ve seen how you can pass configuration options to the Savant object constructor. You’ve already seen the ‘template_path’ option in action, but there are others as well. Here’s a brief list of the most important ones:
- The ‘template_path’ option specifies the default search path for template files;
- The ‘resource_path’ option specifies the default search path for resources such as plugins or filters;
- The ‘error_text’ option specifies the text to display when an error occurs;
- The ‘compiler’ option specifies the compiler to use when compiling templates, if needed;
- The ‘filters’, ‘plugins’ and ‘escape’ options specify a list of additional filters, plugins and output escaping callbacks to load into the template engine;
- The ‘exceptions’ option controls whether errors should be thrown as PHP exceptions;
- The ‘extract’ option controls whether template variables are also converted to regular PHP variables.
The ‘exceptions’ option, in particular, is useful if you want to use PHP5-style exception handling in your controller scripts. Consider the following example, which illustrates:
<?php
// include class
require 'Savant3.php';
// set options
$options = array(
'template_path' => './templates',
'exceptions' => true,
);
// initialize template engine
$savant = new Savant3($options);
try {
// try to display a non-existent template
$savant->display('x.tpl');
} catch (Savant3_Exception $e) {
die('Something bad happened. Call Ghostbusters.');
}
?>
The 'extract' option is also interesting, because it automatically represents template variables as "regular" PHP variables when turned on. Here's an example which illustrates:
<?php
// include class
require 'Savant3.php';
// set options
$options = array(
'template_path' => './templates',
'extract' => true
);
// initialize template engine
$savant = new Savant3($options);
// set template variable and render template
$savant->text = 'I am a giraffe';
$savant->display('simple.tpl');
?>
Within the template, you can now access the template variable as either $obj->text or simply as $text. Be aware of variable collisions when using this option, though!
And that’s about all we have time for. As these examples illustrate, although Savant has a simple OOP-compliant API, it is nevertheless a fairly powerful template engine, suitable for deployment in small-to-medium Web applications. And with its support for filters, plugins and native PHP syntax, 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, 2009. All rights reserved.




September 10, 2009 at 1:58 pm
Excellent article, great introduction to this template engine.
I’ve worked with Smarty and it is extremely useful, this template engine is very easy to install and to use.
I definitely will use this on my next non-framework PHP project.
Cheers!
November 6, 2009 at 7:55 pm
I’ve a question. Is Savant a php framework?
December 3, 2009 at 11:05 am
After going through this tutorial I do not see real separation.. but maybe it is because I am so used to smarty. I still root for smarty, I think it dies a better job. It is good that numerous ideas are being floated as far as separation of business logic from presentation logic is concerned. I would advise more people to jump on the smarty bandwagon though.