
Advanced PHP Programming
.pdf
108 Chapter 4 Implementing with PHP: Templates and the Web
Implementing MVC in the Web environment is usually done via templates. In a template system, your HTML and display logic are held via a template.Your application code, which contains no display logic, parses the request, performs any needed work, and then hands raw data to the template so that the template can format it for display.
There are a wide array of template solutions for PHP.This chapter introduces Smarty, one of the most popular and flexible of the template solutions. It also shows how to implement an ad hoc template solution if you decide Smarty is not for you.
As a pure template language, Smarty is simple. But as you start implementing flow control, custom functions, and custom modifiers, the Smarty language can become quite complex. Any designer who can muddle through implementing complex logic in Smarty could do so in PHP. And that is not necessarily a bad thing. PHP itself is a fine template language, providing you the tools to easily integrate formatting and display logic into HTML.
If your environment consists of designers who are comfortable working in PHP and your entire team (designers and developers both) have the discipline necessary to keep business and display logic separate, then a formal template language may be unnecessary. Although I’ve personally never had problems with designers being unable to deal with PHP integrated into their HTML, peers of mine have suffered through integration headaches with design teams who could not handle PHP embedded in their pages and have had great success with using Smarty to address their organizational problems. Even if your design team is comfortable with PHP, template solutions are nice because they try to force the separation of display from application control.
Besides creating a formal separation between display and business logic, the best justification for using a template solution such as Smarty is to give untrusted end users the ability to write dynamic pages, without having to trust them with access to PHP.This situation can arise in offering virtual storefronts, offering customizable personal pages, or offering template solutions for crafting emails.
Smarty
Smarty is one of the most popular and widely deployed template systems for PHP. Smarty was written by Monte Ohrt and Andrei Zmievski as a fast and flexible template system to encourage separation of application and display logic. Smarty works by taking special markup in template files and compiling it into a cached PHP script.This compilation is transparent and makes the system acceptably fast.
Smarty has a good bit of bloat that I think is best left alone. Like many template systems, it has grown in a number of ill-advised ways that allow complex logic to appear in the templates. Of course, features can be ignored or banned on the basis of policy.We’ll talk more about this later in the chapter.

Smarty 109
Installing Smarty
Smarty is made up of a set of PHP classes and is available at http://smarty.php.net. Because I use PEAR frequently, I recommend installing Smarty into the PEAR include path. Smarty is not a PEAR project, but there are no conflicting names, so placing it in the PEAR hierarchy is safe.
You need to download Smarty and copy all the Smarty libraries into a PEAR subdirectory, like this:
>tar zxf Smarty-x.y.z.tar.gz
>mkdir /usr/local/lib/php/Smarty
>cp -R Smarty-x.y.z/libs/* /usr/local/lib/php/Smarty
Of course, /usr/local/lib/php needs to be part of the include path in your php.ini file.
Next, you need to create directories from which Smarty can read its configuration and template files, and you also need to create a place where Smarty can write compiled templates and cache files.
I usually place the configuration and raw template directories alongside
DocumentRoot for my host, so if my DocumentRoot is /data/www/www.example.org/ htdocs, these will be my template and configuration directories:
/data/www/www.example.org/templates
/data/www/www.example.org/smarty_config
Smarty natively incorporates two levels of caching into its design. First, when a template is first viewed, Smarty compiles it into pure PHP and saves the result.This caching step prevents the template tags from having to be processed after the first request. Second, Smarty allows optional caching of the actual displayed content. Enabling this layer of caching is explored later in this chapter.
Compiled templates and cache files are written by the Web server as the templates are first encountered, so their directories need to be writable by the user that the Web server runs as. As a matter of security policy, I do not like my Web server being able to modify any files under its ServerRoot, so these directories get placed into a different directory tree:
/data/cachefiles/www.example.org/templates_c
/data/cachefiles/www.example.org/smarty_cache
The easiest way to inform Smarty of the location of these directories is to extend the base Smarty class for every application (not every page) that will be using it. Here is the code to create a Smarty subclass for example.org:
require_once ‘Smarty/Smarty.class.php’;
class Smarty_Example_Org extends Smarty { public function _ _construct()
{

110 Chapter 4 Implementing with PHP: Templates and the Web
$this->Smarty();
$this->template_dir = ‘/data/www/www.example.org/templates’;
$this->config_dir = ‘/data/www/www.example.org/smarty_config’;
$this->compile_dir = ‘/data/cachefiles/www.example.org/templates_c’;
$this->cache_dir |
= ‘/data/cachefiles/www.example.org/smarty_cache’; |
}
}
Your First Smarty Template: Hello World!
Now that you have Smarty in place and the directories created, you can write your first Smarty page.You will convert this pure PHP page to a template:
<html>
<body> Hello <?php
if(array_key_exists(‘name’, $_GET)) { echo $_GET[‘name’];
else {
echo “Stranger”;
}
?>
</body>
</html>
The template for this should be located at /data/www/www.example.org/templates/ hello.tpl and will look like this:
<html>
<body>
Hello {$name} </body> </html>
By default, Smarty-specific tags are enclosed in brackets ({}).
The PHP page hello.php file that uses this template looks like this:
require_once ‘Smarty_ExampleOrg.php’; // Your Specialized Smarty Class $smarty = new Smarty_ExampleOrg;
$name = array_key_exists(‘name’, $_COOKIE) ? $_COOKIE[‘name’] : ‘Stranger’; $smarty->assign(‘name’, $name);
$smarty->display(‘index.tpl’);
Note that $name in the template and $name in hello.php are entirely distinct.To populate $name inside the template, you need to assign it to the Smarty scope by performing the following:
$smarty->assign(‘name’, $name);

Smarty 111
Requesting www.example.org/hello.php with the name cookie set returns the following page:
<html>
<body>
Hello George
</body>
</html>
Compiled Templates Under the Hood
When hello.php receives its initial request and display() is called, Smarty notices that there is not a compiled version of the template. It parses the template and converts all its Smarty tags into appropriate PHP tags. It then saves this information in a subdirectory of the templates_c directory. Here is what the compiled template for hello.php looks like:
<?php /* Smarty version 2.5.0, created on 2003-11-16 15:31:34 compiled from hello.tpl */ ?>
<html>
<body>
Hello <?php echo $this->_tpl_vars[‘name’]; ?> </body>
</html>
On subsequent requests, Smarty notices that it has a compiled version of the template and simply uses that instead of recompiling it.
The function $this->tpl_vars[‘name’] is the PHP translation of Smarty tag {$name}.The call $smarty->assign(‘name’, $name) in hello.php populated that array.
Smarty Control Structures
Using simple variable substitutions makes Smarty look incredibly powerful.Your templates are simple and clean, and the back-end code is simple as well. Of course, these examples are contrived, and the test of any product is how it behaves when dropped into the real world.
The first challenge you will likely face in using any template system is building tables and conditionally displaying data.
If a registered member of your site visits hello.php, you would like to display a link to the login page for that member.You have two options.The first is to pull the logic into the PHP code, like this:
/* hello.php */
$smarty = new Smarty_ExampleOrg;
$name = array_key_exists(‘name’, $_COOKIE) ? $_COOKIE[‘name’] : ‘Stranger’;
if($name == ‘Stranger’) {

112 Chapter 4 Implementing with PHP: Templates and the Web
$login_link = “Click <a href=\”/login.php\”>here</a> to login.”; } else {
$login_link = ‘’;
}
$smarty->assign(‘name’, $name); $smarty->assign(‘login_link’, $login_link); $smarty->display(‘hello.tpl’);
Then you need to have the template display $login_link, which may or may not be set:
{* Comments in the Smarty templates start look like this. They can also extend over multiple lines.
hello.tpl
*}
<html>
<body>
Hello {$name}.<br> {$login_link} </body>
</html>
This method completely breaks the separation of application and display logic.
The second option is to push the decision on how and whether to display the login information up to the display layer, as follows:
{* hello.tpl *} <html>
<body>
Hello {$name}.<br>
{ if $name == “Stranger” }
Click <a href=”/login.php”>here</a> to login. { /if }
</body>
</html>
/* hello.php */
$smarty = new Smarty_ExampleOrg;
$name = array_key_exists(‘name’, $_COOKIE) ? $_COOKIE[‘name’] : ‘Stranger’; $smarty->assign(‘name’, $name);
$smarty->display(‘hello.tpl’);

Smarty 113
The Pure PHP Version
Both of the preceding examples are much longer than the pure PHP version:
<html>
<body>
<?php
$name = $_COOKIE[‘name’]? $_COOKIE[‘name’]:’Stranger’;
?>
Hello <?php echo $name; ?>.<br><?php if($name == ‘Stranger’) { ?>
Click <a href=”/login.php”>here</a> to login.
<?php } ?> </body> </html>
This is not unusual. In terms of raw code, a template-based solution will always have more code than a nontemplated solution. Abstraction always takes up space. The idea of a template system is not to make your code base smaller but to keep logic separate.
In addition to full conditional syntax via if/elseif/else, Smarty also supports array looping syntax via foreach. Here is a simple template that prints all the current environment variables:
{* getenv.tpl *} <html>
<body>
<table>
{foreach from=$smarty.env key=key item=value } <tr><td>{$key}</td><td>{$value}</td></tr>
{/foreach}
</table>
</body>
</html>
/* getenv.php */
$smarty = new Smarty_ExampleOrg; $smarty->display(‘getenv.tpl’);
This also demonstrates the magic $smarty variable. $smarty is a Smarty associative array that allows you access to the PHP superglobals (such as $_COOKIE and $_GET) and the Smarty configuration variables. Superglobals are accessed like $smarty.cookie or $smarty.get.To access array elements, you append the lowercased name of the element with a dot as a separator. So to access $COOKIE[‘name’] you would use $smarty. cookie.name. This means that the hello example could have the entirety of its logic performed in Smarty template code, as follows:
{* hello.tpl *}
<html>
<body>

114Chapter 4 Implementing with PHP: Templates and the Web
{if $smarty.cookie.name }
Hello {$smarty.cookie.name}.<br>
Click <a href=”/login.php>here</a> to login. {else}
Hello Stranger. {/if}
</body>
</html>
/* hello.php */
$smarty = new Smarty_ExampleOrg; $smarty->display(‘hello.tpl’);
Some might argue that a template itself should contain absolutely no logic. I don’t buy this argument: Completely eliminating logic from the display either means that the display really has no logic in its generation (which is possible but highly unlikely) or that you have fudged it by pulling what should be display logic back into the application.
Having display logic in application code is no better than having application logic in display code. Avoiding both situations is the whole point of a template system.
Allowing logic in templates poses a rather slippery slope, however. As broader functionality is available in templates, it is tempting to push large amounts of logic into the page itself. As long as that is display logic, you are still adhering to the MVC pattern.
Remember: MVC is not about removing all logic from the view; it is about removing domain (or business) logic from the view. Differentiating display and business logic is not always easy.
For many developers, the goal is not simply to have separation of the display and application but to extract as much logic as possible from the display.The commonly expressed desire is to “keep designers out of my PHP”; the implication is that designers either can’t learn PHP or can’t be trusted with PHP. Smarty cannot solve this problem. Any template language that provides the ability to implement complex logic gives you more than enough rope to hang yourself if you aren’t careful.
Smarty Functions and More
In addition to basic flow control, Smarty also provides the ability to call on built-in and user-defined functions.This increases the flexibility of what you can do inside the template code itself, but it comes at the cost of making the templates complex.
To me, the most useful built-in function is include. Analogous to PHP’s include() construct, the Smarty include function allows you to have one template include another. A common application of this is to place common headers and footers in their own includes, as demonstrated in this trivial example:
{* header.tpl *}
<html>
<head>

Smarty 115
<title>{$title}</title> {if $css}
<link rel=”stylesheet” type=”text/css” href=”{$css}” /> {/if}
</head>
<body>
{* footer.tpl *}
<!-- Copyright © 2003 George Schlossnagle. Some rights reserved. --> </body>
</html>
Then, in any template that needs headers and footers, you include them as follows:
{* hello.tpl *}
{include file=”header.tpl”}
Hello {$name}.
{include file=”footer.tpl”}
Smarty also supports the php function, which allows for PHP to be inlined in the template.This allows you to execute something like the following:
{* hello.tpl *}
{include file=”header.tpl”}
Hello {php}print $_GET[‘name’];{/php} {include file=”footer.tpl”}
The php smarty tag is pure evil: If you want to write templates using raw PHP, you should write them in PHP, not in Smarty. Mixing languages inside a single document is almost never a good idea. It needlessly increases the complexity of the application, making it more difficult to determine where a piece of functionality has been implemented.
Smarty supports custom functions and custom variable modifiers. Custom functions are useful for creating helpers to automate complex tasks. An example is the mailto function, which formats an email address into an HTML mailto: link, as shown here:
{mailto address=”george@omniti.com}
This renders to the following:
<a href=”mailto:george@omniti.com”>george@omniti.com</a>
You can register your own custom PHP functions with the Smarty register_ function() method.This is useful for creating your own helper code. A function registered with register_function() takes the array $params as its input; this array is the optional arguments passed in the Smarty function call.The following is a helper function that renders a two-dimensional array as an HTML table (this function has been defined in the following application code):
function create_table($params)
{

116 Chapter 4 Implementing with PHP: Templates and the Web
if(!is_array($params[‘data’])) { return;
}
$retval = “<table>”; foreach($params[‘data’] as $row) {
$retval .= “<tr>”; foreach($row as $col) {
$retval .= “<td>$col</td>”;
}
$retval .= “</tr>”;
}
$retval .= “</table>”; return $retval;
}
Note
create_table() is different from the Smarty built-in function html_table because it takes a two-
dimensional array.
You can use create_table() to print a table of all your template files:
{* list_templates.tpl *} {include file=”header.tpl”} {create_table data=$file_array} {include file=”footer.tpl”}
/* list_templates.php */ $smarty = new Smarty_ExampleOrg;
$smarty->register_function(‘create_table’, ‘create_table’); $data = array(array(‘filename’, ‘bytes’));
$files = scandir($smarty->template_dir); foreach($files as $file) {
$stat = stat(“$smarty->template_dir/$file”); $data[] = array($file, $stat[‘size’]);
}
$smarty->assign(‘file_array’, $data); $smarty->display(‘list_templates.tpl’);
Smarty also supports variable modifiers, which are functions that modify variable display. For example, to call the PHP function nl2br() on the Smarty variable $text, the template code would look like this:
{$text|nl2br}

TEAM
FLY |
Smarty |
117 |
|
As with functions, you can register custom modifiers, and you do so by using the register_modifier() method. Here is the code to register a modifier that passes the variable through PHP’s urlencode() function:
$smarty->register_modifier(‘encode’, ‘urlencode’);
You can reference the Smarty manual, available at http://smarty.php.net/manual/en, to find the complete list of functions and modifiers available. Of course, you should register in your class constructor custom functions that you plan on using across multiple templates.
Caching with Smarty
Even faster than using compiled versions of templates is caching the output of templates so that the template does not need to be executed at all. Caching in general is a powerful technique.This book dedicates three chapters (Chapter 9,“External Performance Tunings,” Chapter 10,“Data Component Caching,” and Chapter 11,“Computational Reuse”) exclusively to different caching techniques.
To cache content in Smarty, you first enable caching in the class via the following line:
$smarty->cache = true;
Now, whenever you call display(), the entire output of the page will be cached for $smart->cache_lifetime (default 3,600 seconds). In many pages, the most expensive part happens in the PHP script, where you set up the data for generating the page.To short-circuit this process, you can use the method is_cached() to check whether a cached copy exists. Inside your PHP script, this would be used as follows:
$smarty = new Smarty_ExampleOrg; if(!is_cached(‘index.tpl’)) {
/* perform setup */
}
$smarty->display(‘index.tpl’);
If your page has any sort of personalization information on it, this is not what you want because it will cache the first user’s personalized data and serve that up to all subsequent users.
If you need to conditionally cache data, you can pass a second parameter into display().This causes the caching system to use that as a key to return the cached content to another request, using that same key. For example, to cache the template homepage.tpl for 10 minutes uniquely for each requesting user, you could identify the user by the MD5 hash of his or her username:
$smarty = new Smarty_ExampleOrg; if(!is_cached(‘homepage.tpl’, md5($_COOKIE[‘name’])))
{
/* perform setup */