Документация
HTML CSS PHP PERL другое
Smarty
 
Previous
Table of Contents
Next

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.

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/cache/files/016/www.example.org/templates_c
/data/cache/files/016/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()
  {
   $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/cache/files/016/www.example.org/templates_c';
   $this->cache_dir    = '/data/cache/files/016/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)) {
  print $_GET['name'];
else {
  print "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);

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 print $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') {
  $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');

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>
{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>
  <title>{$title}</title>
  {if $css}
  <link rel="stylesheet" type="text/css" href="{$css}" />
  {/if}
</head>
<body>

{* footer.tpl *}
<!-- Copyright &copy; 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)
{
  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}

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 */
  $smarty->assign('name', $_COOKIE['name']);
}
$smarty->display('homepage.tpl', md5($_COOKIE['name']));

Notice that you can still use is_cached() by passing the cache key into that.

Be aware that Smarty has no built-in garbage collection and that every cached page results in a file being stored on your cache filesystem. This opens you to both accidental and malicious denial-of-service attacks if you have thousands of cached pages accumulated on the filesystem. Selectively caching files based on a key with a relatively low number of possible values is recommended.

A better solution for caching files that have highly dynamic content is to cache everything except the dynamic content. You want to be able to use code like this in your templates:

{* homepage.tpl *}
{* static content that can be cached *}
{nocache}
Hello {$name}!
{/nocache}
{* other static content *}

To accomplish this, you can register a custom block handler for the nocache block via the Smarty method register_block(). The block-handling function itself takes three parameters: any parameters passed into the tag, the content enclosed by the block, and the Smarty object.

The function you want to implement simply returns the block content unchanged, as shown here:

function nocache_block($params, $content, Smarty $smarty)
{
  return $content;
}

The trick is to register the function nocache_block as uncacheable. You do this by setting the third parameter of register_block() to false, as follows:

$smarty->register_block('nocache', 'nocache_block', false);

Now even in templates that are to be cached, the enclosed nocache block will always be dynamically generated.

Be aware that if you use is_cached() to short-circuit your prep work, you need to make sure you unconditionally perform the setup for the uncacheable block.

Advanced Smarty Features

As a final point in this whirlwind coverage of Smarty, some additional features are worth noting:

  • Security settings Smarty can be configured to allow the use of only certain functions and modifiers and to disallow the use of php blocks. It is good practice to disable php blocks immediately and to always think twice before enabling them. Security is globally enabled by setting the Smarty class attribute $security to true. After that is done, individual security settings are toggled via the attribute $security_settings. See the Smarty manual for complete details. The best way to enable security is to simply set that attribute in the class constructor, as shown here for Smarty_ExampleOrg:

    class Smarty_Example_Org extends Smarty {
      function _ _construct()
      {
        $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/cache/files/016/www.example.org/templates_c';
        $this->cache_dir    = '/data/cache/files/016/www.example.org/smarty_cache';
        $this->security     = true;
      }
    }
    

  • Template prefilter Template prefilters allow you to register a function that is run on the template before it is compiled. The standard example is a prefilter to remove all unnecessary whitespace from a template. Prefilters are registered via the method register_prefilter().

  • Template postfilter A template postfilter is run on a template after it is compiled but before it is written to disk. An ideal use of a postfilter is to add some stock PHP code to every compiled template; for example, code that sets HTTP headers that invoke session_start(). Postfilters are registered via the method register_postfiler(). Here is a simple postfilter which ensures that session_start() is enabled:

    function add_session_start($tpl_source, Smarty $smarty)
    {
    return "<?php session_start(); ?>\n".$tpl_source;
    }
    
    $smarty = new Smarty_ExampleOrg;
    $smarty->register_postfilter("add_session_start");
    

  • Output filters This function is run on any Smarty-generated output before it is sent to the browser (or written to the Smarty cache). This is an ideal place to perform any last-minute data munging before content is sent out. Examples of output filters include rewriting all email addresses in output as george at omniti.com (to cut down on email-hunting Web spiders) or replacing all text emoticons such as :) with links to actual emoticon images. Output filters are registered with register_outputfilter().

  • Cache handlers You can register custom cache back ends that allow you to alter the way Smarty reads and writes its cache files. This is useful if you want Smarty to use a database to store its cache files and compiled templates to guarantee that all servers serve identical cached content. Cache handlers are registered by setting the Smarty class attribute $cache_handler_func.

  • Customizable tags If you don't like {} as delimiters, you can change them to whatever you want. I prefer the XML-ish <smarty></smarty>.


Previous
Table of Contents
Next
Главная
Love
Bilan
Fashion
Мода
Пушкин
Esenin
Лермонтов
Чехов
Referat