Fixing some problems in Template Lite

Date November 8, 2007

As a PHP developer probably you are familiar with Smarty, which is maybe the de facto PHP template engine. Smarty is sometimes criticized that it has too many feature and is too big. I think that in an average development Smarty could be a good choice, it gives you a lot of potential, and its speed and memory footprint has no impact on the overall performance of the application. But if you develop a really high traffic website these issues could become very important. Template Lite could be a good compromise between the features of Smarty and the performance. It is a lightweight version of Smarty, it is much faster and has much lower memory footprint. It has almost the same features like Smarty, so you won’t have any problem using it after Smarty. While Template Lite works very well, it needs a few improvements.

The name of the compiled template file

By default the name of the compiled template file is the encoded version of the original template file. If turn off this behavior by setting the $encode_file_name variable to false. The problem is, that in this case Template Lite does not include into the name of the compiled file the $template_dir variable. Consider the following situation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
/*
 * Template layout:
 * /templates
 *     /domain1.com
 *         /header.tpl
 *         /index.tpl
 *         /footer.tpl
 * /templates
 *     /domain2.com
 *         /header.tpl
 *         /index.tpl
 *         /footer.tpl
 */


// This common code handles both domain.
$template = new Template_Lite();
$template->template_dir = $_SERVER['SERVER_NAME'];
$template->display('index.tpl');
?>

The code looks good, but there is a big problem. If you get the index page on domain1.com, and then the index page on domain2.com, the secondly generated compiled file will overwrite the first one. To solve this problem you should modify the source code in the following way:

1
2
3
//$name = ($this->encode_file_name) ? md5((($this->_resource_type == 1) ? $this->template_dir.$file : $this->_resource_type . &qout;_&qout; . $file)).'.php' : str_replace(&qout;.&qout;, &qout;_&qout;, str_replace(&qout;/&qout;, &qout;_&qout;, $this->_resource_type . &qout;_&qout; . $file)).'.php';
$name = ($this->encode_file_name) ? md5((($this->_resource_type == 1) ? $this->template_dir.$file : $this->_resource_type . &qout;_&qout; . $file)).'.php' :
            str_replace(array(&qout;:&qout;, &qout;.&qout;, &qout;/&qout;, &qout;\\&qout;), &qout;_&qout;, $this->_resource_type.&qout;_&qout;.$this->template_dir.$file).'.php';

This modification should be done in several places in the source code. To find all the places you should search for the “encode_file_name” string.

Writing out the compiled template file

Template Lite by default opens a new file, and writes the compiled template content to it. This is not a good solution if the site has high traffic, because in the time of writing out the file it is not accessible for other processes which will cause error. A better solution is if you write to a temporary file, and when you finish it rename the temporary file to the final name. The name of the temporary file should be unique, you can achieve this by using a suffix like this: getmypid()."_".microtime(TRUE). Now if we have multiple request on the same time, and there isn’t a compiled version of the template, every request starts to generate the compiled version. The renames will be atomic and are ordered by the OS, so this way we can eliminate this type of error. The only drawback is that in Windows we can’t rename to an existing file, but usually you won’t serve a high traffic site on a Windows machine with PHP, so this is not a big problem.

To get Template Lite to work in this way, we should slightly modify its source (class.template.php) in the following way:

776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
/*
// Original code
$f = fopen($this->compile_dir.'c_'.$name, &qout;w&qout;);
fwrite($f, $output);
fclose($f);
*/

// We write to a temporary file at first, and then we rename it to the final name.
// Renaming is atomic so we won't have probelm on concurrent requests.
$compiledFile = $this->compile_dir.&qout;c_&qout;.$name;
$tempFile = $compiledFile.&qout;_&qout;.getmypid().&qout;_&qout;.microtime(TRUE).&qout;.tmp&qout;;
$f = fopen($tempFile, &qout;w&qout;);
fwrite($f, $output);
fclose($f);
// This won't work on Windwos, if the target file exists!!!
rename($tempFile, $compiledFile);

The value of the $this->_file variable

The Template_Lite class holds in this variable the name of the currently used template file. If the currently processed template includes an other template, Template Lite stops the processing of the current template and starts to process the included one (it works like a function call). At this stage the value of the $this->_file will be the name of the included template. Unfortunately when the processing step back to the original template, the value of $this->_file won’t change back. Generally this has no effect, but I sucked a little through it when I tried to extend Template Lite (you can read about this soon).

To correct this issue you should modify the class.template.php file on two places:

749
750
$prev_file_name = $this->_file;
$this->_file = $file;
796
797
$this->_file = $prev_file_name;
return $output;

Some other little typos

The following snippet comes from the original template.class.php file:

689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
if($this->_resource_type == 1)
{
    $f = fopen($this->template_dir . $file, &qout;r&qout;);
    $size = filesize($this->template_dir . $file);
    if ($size > 0)
    {
        $file_contents = fread($f, $size);
    }

}
else
if($this->_resource_type == &qout;file&qout;)
{
    $f = fopen($file, &qout;r&qout;);
    $size = filesize($file);
    if ($size > 0)
    {
        $file_contents = fread($f, $size);
    }

}

else
{
    call_user_func_array($this->_plugins['resource'][$this->_resource_type][0], array($file, &$file_contents, &$this));
}

$this->_file = $file;
fclose($f);

Maybe you notice, that no file is opened in the else statement, but fclose($f) is always called. You should move this command to the if and else if statement.

Another little typo is here:

257
258
259
260
261
if (in_array($key, $this->_vars))
{
    // The array key should be $key.
    unset($this->_vars[$index]);
}

You can download the corrected version. The comments are in Hungarian, but after reading the article you won’t need them, and anyway it is a beautiful language. ;)

3 Responses to “Fixing some problems in Template Lite”

  1. kow said:

    You forgot one annoying bug in template lite: http://sourceforge.net/tracker/index.php?func=detail&aid=1804679&group_id=163694&atid=828751
    It made me hours to figure out are those newline characters.

  2. Samuel L. said:

    Hey, nice tips. I’ll buy a bottle of beer to the person from that chat who told me to go to your site :)

  3. Edmar said:

    Thanks a lot Gergely! I like Template Lite a lot, but I think I’ve had some issues with the compilation race condition in the past. Your improvement is great! Cheers from Brazil!

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>