Skip to main content

Separate form and function in PHP applications with Smarty

Clever template engine cleaves Web page markup from underlying design logic

Martin Streicher (martin.streicher@linux-mag.com), Editor in Chief, Linux Magazine
Martin Streicher is editor-in-chief of Linux Magazine, a Web developer at Hesketh.com (a user experience agency in Raleigh, N.C.), and a regular contributor to developerWorks. He earned a master's degree in computer science from Purdue University and has been programming UNIX-like systems since 1986.

Summary:  The ease of mixing PHP and other Web-page markup leads to a morass of program logic, HTML, Cascading Style Sheets (CSS), and JavaScript, making maintenance a hair-pulling task. The Smarty template engine separates form from function.

Date:  17 Jul 2007
Level:  Intermediate
Activity:  2503 views

PHP Web applications are a cinch to start. The syntax of the PHP language is uncluttered and easy to master. You can mix PHP directly with HTML, JavaScript, and CSS to produce visible results quickly. And PHP applications are a snap to deploy on your own Web server or on a hosting service.

But the ease of mixing PHP and other page markup is also a liability. PHP code is often a tangled web (no pun intended) of program logic, Structured Query Language (SQL) queries, functions, classes, developer comments, HTML, CSS styles, and scripts. Worse, there are many ways to emit content from PHP, from echo to output buffering. Maintaining such a raveled page is strenuous. An innocuous change to code or markup can wreak havoc, and enhancing the page can require the combined effort of a designer and a programmer. With PHP, form (what the page looks like) and function (the purpose and construction of the page) are intertwined.

In a perfect world, form and function exist on separate continents. That's certainly the intent of CSS and HTML, for example. CSS is form, while HTML is function. In the case of PHP, it would be ideal if page markup and code could be segregated. Code would process input, make decisions, and produce data for display, while markup would anticipate the data and provide the scaffolding required to render the information.

For example, the markup for a home page might leave a "fill in the blank" for a user's login and other placeholders for the user's picture and vital information. This template — so named because it provides a pattern for the display of the page — is solely the purview of the designer, who controls the entire look of the page and leaves a placeholder for the name, picture, and other data. Code simply provides data for the placeholder. The developer's job remains focused on computation.

Of course, form and function must cooperate. If a template expects a dollar amount, code shouldn't provide a URL. If a template expects an object, code shouldn't provide a list. Hence, a template system must separate form from function, yet also bridge the divide.

Most popular Web application programming languages (Perl, Python, Ruby, Java™) have template engines, and PHP is no exception. Type PHP template engine into a search engine, and you're likely to find 25 or more options. (See Resources for The PHP Template Engine Roundup, a list that highlights the strengths of each engine surveyed.)

Some PHP template engines optimize for speed. Others are designed to be simple to use while encouraging discrete form and function. In some packages, placeholders are described in PHP itself, while other solutions have a custom abbreviated programming language. Choosing a template engine depends largely on your requirements, so a little research and experimentation is in order.

Here, I introduce you to Smarty, one of the most popular PHP template engines. Smarty "code" has its own syntax and an expansive list of operators, but the system isn't onerous to learn. Read or scan the Smarty documentation to familiarize yourself with all its features. Begin with small Smarty adaptations and expand your repertoire as needs dictate and you grow more proficient.

Get Smarty

The Smarty Web site maintains an active mailing list, a support forum, and an Internet Relay Chat (IRC) forum (see Resources). Development is ongoing, and this article is based on V2.6.18, which was released 7 Mar 2007.

Smarty has two facets: a PHP application programming interface (API) and a display engine. Application code calls the API to associate code variables with template placeholders, while the display engine interprets Smarty markup, performs loops, references placeholders, and displays a final result. Smarty features include:

Operators to display all of PHP's fundamental data structures
Display a simple variable, iterate over an array or associative array, and display the members of a class.
Placeholders' default values
If your PHP code doesn't associate a variable with a placeholder, the default is shown, instead.
Control operators, such as if, then, else, that can choose what to display dynamically based on an incoming datum
For example, a designer may choose to show a negative account balance in bold, red text and a positive balance in black text. You can isolate this kind of display logic in the template (making your life much easier).
Loop controls, which provide special variables that simplify building lists and tables
For example, you can test for the first iteration of a loop and create a table header. You can also cycle through a list of values round-robin as the loop iterates, which is perfect for alternating the colors of table rows.
Modifiers to alter data when rendered
For example, you can display a placeholder — say, $name — in bold, uppercase letters with the Smarty markup: <strong>{$name|upper}</strong>.

The <strong> is plain HTML. The curly braces ({}) delineate Smarty markup, $name is the placeholder, and |upper is the modifier. You can also write your own modifiers to extend Smarty's features.
If you must include scripts and raw PHP code, you can do so with the literal and php operators
Anything within a literal operator is passed to the final page verbatim. Code placed in the php operator executes as if were embedded within <?php ... ?> escapes.

You can also reuse Smarty templates through the {include ...} operator. To boost performance, each template is cached to avoid a conversion penalty each time it's used. The Smarty Web site provides extensive documentation and examples. Packt Publishing also offers a book titled Smarty: PHP Template Programming and Applications (see Resources), suitable for learning and reference. (A caveat: Some newer operators aren't described, and other operators are described incorrectly because the book describes an earlier version of Smarty V2.x.)


Developing with Smarty

Enumerating and demonstrating all of Smarty's features is beyond the scope of a single article. However, even a small example, as shown below, exemplifies the power of templates.

Adding Smarty to your application is easy:

  1. Download the Smarty.zip demonstration code (see Download).
  2. Extract and install Smarty in your path.
  3. Write an application that requires the Smarty class.
  4. Create two directories co-located with your application:
    • templates will contain templates
    • templates_c will contain cached templates

For example, the contents of the example application's folder contains Example.class.php index.php templates/ templates_c/.

The files in the templates directory are read by the Smarty engine. Make sure the Web server has proper access to those files. Separately, the contents of templates_c must be readable and writable because cached copies of templates are placed here. At a minimum, make templates_c writable by the Web server. Or you can change the directory to mode 777, if security isn't a concern.

Listing 1 shows Example.class.php, a representative PHP V5 class. Listing 2 contains index.php, the application. The result of visiting the example application with the browser is shown in Figure 1.


Listing 1. A simple PHP V5 class to store an arbitrary list of named properties of any type
                
<?php

//
// Example is a simple class that stores an arbitrary 
// number of named properties. 
//

class Example {
    private $catalog = array();
    
    public function SetProperties( $arrayVariables ) {
        foreach ( $arrayVariables as $name => $value ) {
            $this->SetProperty( $name, $value );
        }
    }
    
    public function SetProperty( $name, $value ) {
        $this->$name = $value;
        $this->catalog[] = $name;
    }
    
    public function GetProperties( ) {
        $result = array();
        foreach ( $catalog as $name ) {
            $result[$name] = $this->GetProperty( $name );
        }
        
        return( $result );
    }
    
    public function GetProperty( $name ) {
        return ( $this->$name );
    }
}
?>

Example is a simple class that persists an arbitrary number of named properties. The most interesting part of the class is line 18 $this->$name = $value;. This creates a class instance member dynamically. For instance, if you call $example->SetProperty( 'name', 'Groucho' ) you can retrieve the name with the (traditional) $example->name.


Listing 2. A PHP application, devoid of any Web-page markup
                
<?php
require( 'Smarty.class.php' );
require( 'Example.class.php' );

$groucho = new Example();
$groucho->SetProperty( 'name', 'Groucho' );
$groucho->SetProperty( 'quote', 
    'Time flies like an arrow. Fruit flies like a banana.' 
);

$chico = new Example();
$chico->SetProperties( array(
        'name' => 'Chico',
        'quote' => "There's no such thing as a sanity clause!"
    )
);

$movies = new Example();
$movies->SetProperties( array(
    'movies' => array(
        'A Night at the Opera',
        'Horse Feathers',
        'Coconuts',
        'The Big Store'
    ))
);

$looks = new Example();
$looks->SetProperty( 'novelty', array(
        array( name =>'Groucho', 'signature' => 'moustache' ),
        array( name =>'Chico', 'signature' => 'hat' ),
        array( name => 'Harpo', 'signature' => 'raincoat' ),
        array( name => 'Zeppo', 'signature' => 'hair')
    )
);

$smarty = new Smarty();
$smarty->assign( 'person', $chico );
$smarty->assign( 'people', $looks->novelty );
$smarty->assign( 'quote', $groucho->quote );
$smarty->assign( 'movies', $movies->movies );
$smarty->display( 'template.tpl' );
?>

Listing 2 reflects common strategies to associate PHP variables with Smarty placeholders. The line $smarty->assign( 'name', $x ) associates the PHP variable (or array or object) $x with a placeholder name. Everywhere name appears in the template, the value(s) of $x appears.


Figure 1. The rendering of the final page, marrying form and function
The rendering of the final page, marrying form and function

What do Smarty templates look like? Smarty code is very lightweight, as shown in listings 3, 4, and 5. Smarty treats anything in curly braces ({}) as Smarty code. Hence, if any other page markup, such as embedded CSS or JavaScript, uses curly braces, you must enclose that markup with {literal}...{/literal}, as demonstrated in Listing 3.


Listing 3. A simple header template included in the main template
                
<html>
<head>
	<title>{$title|default:'The Marx Brothers'}</title>
	<style type="text/css">
	{literal}
	body {
	    font-family: Verdana, Arial, sans-serif;
	    margin: 5%;
	}
	{/literal}
	</style>
</head>
<body>

As mentioned, Listing 3 applies the {literal} operator to render markup verbatim: Any text between the delimiters is passed through without further interpretation. Line 3 displays the value of the placeholder named <title>, which is associated with a scalar variable in the PHP application. The |default: modifier provides a default value if <title> is unassociated. Pay attention to white space in Smarty operators. Often, you must omit it. Fortunately, the Smarty compiler provides helpful error messages.

Listing 4, the page footer, shows the use of {if condition} to make a decision at rendering time. Here, if the value of placeholder quote is set, the markup interlineated among {if} and {/if} is shown. The line {$quote|upper} emits the value of quote in all uppercase letters. |upper is one of many modifiers that alter string output — again, very useful for separating the content of the string from how it's to be displayed.


Listing 4. A footer template
                
<div style="clear: both;">
    <p align="center">
        {if $quote}
            <{$style|default:'strong'}>
                {$quote|upper}        
            </{$style|default:'strong'}>
        {/if}
    </p>
</div>
</body>
</html>

Listing 5 renders the final page.


Listing 5. The primary Smarty template for the application
                
{include file='header.tpl'}

<p>
    {$person->name} said, "{$person->quote}"
</p>

<ul>
    {section name=index loop=$movies}
    <li>
        {$movies[index]}
    </li>
    {/section}
</ul> 

<table>
{foreach item=person from=$people name=people}
    {if $smarty.foreach.people.first}
        <tr>
            <th>#</th>
            <th>Name</th>
            <th>Signature</th>
        </tr>
    {/if}
    <tr bgcolor="{cycle values="#eeeeee,#d0d0d0}">
        <td>{$smarty.foreach.people.iteration}</td>
        <td>{$person.name}</td>
        <td>{$person.signature}</td>
    </tr>
{foreachelse}
    <tr><td>Print this only if there's no data.</td></tr>
{/foreach}
</table>

{include file='footer.tpl'}

This primary Smarty template for the application employs several Smarty operators:

{include file='filename'}
Works much like PHP's own include() method, inserting and interpreting the contents of filename in place immediately. Although not shown, you can pass variables from one template to another, which encourages reuse.
{$person->GetProperty('name')}
Assumes that person is associated with a method named GetProperty(). You can call an object's methods and reference object members, as {$person->quote} does.
{section name=index loop=$placeholder}
Iterates over arrays. The loop attribute names the placeholder, and the name attribute assigns a name to use for the array index. Within the loop, you reference your array elements as {$placeholder[index]}.
foreach
Iterates like section but offers a very nice feature to process an array of associative arrays, such as a list of rows from a database query. Each associative array is "flattened" into the named item index. For example, in Listing 5, person is the named item. Each time through the loop, person is assigned an associative array from the array people. From then on, throughout the loop, you can refer to values in the associative array through its key, as in {$person.signature}.
name attribute in foreach
Akin to the id attribute of an HTML tag, it uniquely identifies the loop. You use this ID to refer to a special set of variables that reflect the state of the loop. For instance, one special variable is first, which is set only during the first iteration of the loop. Hence, the value $smarty.foreach.people.first refers to the special Smarty variable (smarty) associated with the foreach loop (foreach) named people (people). As you might envision, there is also a last value and an iteration value that begins at 1 and increments each iteration. (If you want a zero-based counter, use index instead of iteration.)
cycle
Great for building tables. If you provide a list for values, Smarty cycles among all the values as the loop iterates. Adding a cycle to bgcolor alternates the color of each table row to make the table more legible.
{foreachelse}
If the array to iterate over is empty, the contents of {foreachelse}...{/foreachelse} are displayed, instead.

Listing 2 is likely plain to read now that you've previewed the templates. As is typical, Listing 2 does computation and passes the work of rendering the page to Smarty. The line $smarty->display('template.tpl') renders the template. To capture the output of Smarty, use $smarty->fetch('template.tpl').


Work Smarty-er, not harder

While this example is contrived, it demonstrates the power and flexibility of Smarty and how simple it is to separate markup from code. Smarty has many more tricks. Chances are, Smarty implements nearly everything you need. Yes, you can capture the output of a template into a Smarty placeholder. Yes, you can filter a template before and after it's compiled, and you can manipulate the output of rendering before it's displayed or fetched. And yes, Smarty allows you to cache templates.

Adding $smarty->caching = 1; to your PHP code is all that's required. If caching is enabled, a call to display('template.tpl') renders the template as usual and saves a copy of the template in the cache. The next call to display('template.tpl') leverages the cached copy, instead of rendering the template again.

You can control the expiration of the cache, empty the cache manually, and make cache refreshes automatic if a template file changes after its cached version was saved. If you prefer to read code that resembles Listing 2, apply Smarty to your Web projects.



Download

DescriptionNameSizeDownload method
Smarty demonstrationos-php-smarty.zip3KB HTTP

Information about download methods


Resources

Learn

Get products and technologies

  • Download the Smarty template engine.

  • Innovate your next open source development project with IBM trial software, available for download or on DVD.

  • Download IBM product evaluation versions, and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.

Discuss

About the author

Martin Streicher is editor-in-chief of Linux Magazine, a Web developer at Hesketh.com (a user experience agency in Raleigh, N.C.), and a regular contributor to developerWorks. He earned a master's degree in computer science from Purdue University and has been programming UNIX-like systems since 1986.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=239956
ArticleTitle=Separate form and function in PHP applications with Smarty
publish-date=07172007
author1-email=martin.streicher@linux-mag.com
author1-email-cc=martin.streicher@linux-mag.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers