[Discuss at the forums]


What

A set of plugin, a prefilter and an extended Smarty class that allows users to define clipcache blocks within templates that have independant and user specifiable caching and delimiter characteristics.

This project is often used with a ram based cache handler. The relevant thread is [at the forums].


Features


Caveats, Unresolved Issues, Bugs


How It Works

During the template compilation process, a prefilter captures all {clipcache} blocks and writes them to private, individual template files. Within the original template's compiled code, the {clipcache} blocks are replaced with a single call to a special {include_clipcache} (registered as non-caching) that fetches the previously cliped template fragment from its file. {include_clipcache} which can also be used stand-alone provides a mechanism where included files can be called with private caching characteristics and user configurable delimters.

{include_clipcache} can be used independantly of the filter; for example, to provide independant caching of included templates (the Smarty core {include} does not provide this). eg:{include_clipcache file="template.tpl" cache_id="cache|group" cache_lifetime=$cache_lifetime} Note that the {include_clipcache} tag uses different parameters than the prefilter {clipcache}, {include_clipcache}'s syntax being more in line with the standard Smarty::display() syntax.

Since {clipcache} blocks are replaced with {include_clipcache} calls, the {clipcache} blocks effectively have private scopes. This means, for example, that assignments made in a {clipcache} block will not be seen in the original template even though they both originate from the same file.


Installation

Put the plugins in your $smarty->plugins_dir folder. The optional extended Smarty class (Smarty_ClipCache) has methods to register and unregister clipcache features.


History


Sample Code

example.php

<?php
include 'Smarty.class.php';
include 'Smarty_ClipCache.class.php';
$smarty = new Smarty_ClipCache();
// configure to suit your setup

$smarty->register_clipcache();
$smarty->caching = 1;
$smarty->cache_lifetime = 20;
$smarty->display('example.tpl');
?>

example.tpl

<h2>Very Simple Example</h2>

{**
 * Plain Jane static content. As usual, this will be refreshed at the frequency
 * specified for this template. It should be noted that due to the way the caching
 * system works, refresh frequences will be approximate and typically slightly
 * greater than the specified amount. This means that template refreshes between
 * clips and the main template won't be lock-step.
 *}
<div>
Timestamp 1: {$smarty.now|date_format:"%Y-%m-%d %H:%M:%S"}

This first timestamp will refresh every 20 seconds
which is the cache_lifetime of this template.
</div>

{**
 * Create a clipcache block.
 *
 * A clipcache block has its own private refresh frequency and cache id. To
 * enable this, the block is written to a separate template file at compile time
 * which is then compiled separately.
 *
 * Note that the following parameters are required:
 * @id unique identifier for the block within this template
 * @group cache group to associate with the block
 * @ttl time-to-live for the block, in seconds
 *
 * Also note that the close tag must be a duplicate of the open tag!
 * Sorry, I'm too lazy to fix that for now.
 *}
{clipcache id=test group=foo ttl=5}
<div>
Timestamp 2: {$smarty.now|date_format:"%Y-%m-%d %H:%M:%S"}

This clipcache block timestamp is refreshed every 5 seconds.
</div>

{**
 * the following is a nested clipcache block. Tread carefully!
 *}
{clipcache id=foo group="foo|foo" ttl=10}
<div>
Timestamp 3: {$smarty.now|date_format:"%Y-%m-%d %H:%M:%S"}

This nested clipcache block timestamp is refreshed every 10 seconds.
</div>
{/clipcache id=foo group="foo|foo" ttl=10}

{/clipcache id=test group=foo ttl=5}

Code Listings

Extended Smarty Class

Smarty_ClipCache.class.php

<?php

/**
 * Smarty Addon
 * @package Smarty
 */

/**
 * Smarty_ClipCache (Smarty)
 *
 * Extended Smarty class to manage {clipache} plugin functionality.
 *
 * It is important to configure the Smarty instance before calling register_clipcache().
 * A few caveats:
 *     - resources are not supported
 *     - multiple template dirs are not supported
 *     - absolute template paths are only supported by using the helper method
 *       absolute2relative() to first find the relative path for the template.
 *       This feature only works so long as the template_dir is given as an
 *       absolute path and that the given absolute template path is in fact
 *       inside the template_dir's path.       
 *
 * @file        Smarty_ClipCache.class.php
 * @version     0.1.7 2006-May-11
 * @since       2005-APR-08
 *
 * @author      boots {jayboots ~ yahoo com}
 * @copyright   brainpower, boots, 2004-2006
 * @license     LGPL 2.1
 * @link        http://www.phpinsider.com/smarty-forum/viewtopic.php?p=19733#19733
 */
class Smarty_ClipCache extends Smarty
{
    function Smarty_ClipCache()
    {
        $this->Smarty();
    }

   
    function register_clipcache()
    {
        $this->load_filter( 'pre', 'clipcache' );
        require_once $this->_get_plugin_filepath( 'function', 'include_clipcache' );
        $this->register_function( 'include_clipcache', 'smarty_function_include_clipcache', false );
        $write_path = rtrim( $this->compile_dir, "/\\" ) . DIRECTORY_SEPARATOR . 'clipcache' . DIRECTORY_SEPARATOR;
        $this->template_dir = (array)$this->template_dir;

        if ( !in_array( $write_path, $this->template_dir ) ) {
            $this->template_dir[] = $write_path;
        }
    } 

   
    function unregister_clipcache()
    {
        $this->template_dir = $this->template_dir[0];
        $this->unregister_prefilter( 'clipcache' );
        $this->unregister_function( 'include_clipcache' );
    }


    function absolute2relative( $path )
    {
        $template_dir = ( is_array( $this->template_dir ) )
            ? $this->template_dir[0]
            : $this->template_dir;

        if ( !in_array( substr( $template_dir, 0, 1 ), array( '/', '\\' ) ) {
            // if template_dir path is already relative then we throw a fatal error
            // since we can not discover the base path with certainty
            $this->trigger_error( "The template_dir path '{$template_dir}' must be an absolute path. ", E_USER_ERROR );
        }

        if ( in_array( substr( $path, 0, 1 ), array( '/', '\\' ) ) {

            if ( substr( $path, 0, strlen( $template_dir ) == $template_dir ) {
                $result = substr( $path, strlen( $template_dir ) );

            } else {
                // we don't know this template path so we throw a fatal error
                $this->trigger_error( "The path for '{$path}' is not in the template path ('{$template_dir}'). ", E_USER_ERROR );
            }

        } else {
            // if recieved path is already relative, do nothing
            $result = $path;
        }

        return $result;
    }
}
?>

Plugins

function.include_clipcache.php

<?php

/**
 * Smarty plugin
 * @package Smarty
 * @subpackage plugins
 */

/**
 * Smarty {include_clipcache} function plugin
 *
 * Includes a template using private caching parameters. Must be registered as non-caching.
 *
 * @file        function.include_clipcache.php
 * @version     0.1.7 2006-May-11
 * @since       2005-APR-08
 *
 * @author      boots {jayboots ~ yahoo com}
 * @copyright   brainpower, boots, 2004-2006
 * @license     LGPL 2.1
 * @link        http://www.phpinsider.com/smarty-forum/viewtopic.php?p=19733#19733
 *
 * @param array $params
 * @param Smarty $smarty
 *
 * This function observes the following tag attributes (in $params):
 *
 * #param file required template file
 * #param cache_id required specify cache build group
 * #param cache_lifetime required time to live for template part/group
 * #param ldelim optional specify the left delimiter to use for included content
 * #param rdelim optional specify the right delimiter to use for included content
 */
function smarty_function_include_clipcache($params, &$smarty)
{
    // validation
    foreach ( array( 'cache_id', 'file', 'cache_lifetime' ) as $required ) {

        if ( !array_key_exists( $required, $params ) ) {
            $smarty->trigger_error( "include_clipcache: '$required' param missing. Aborted.", E_USER_WARNING );

            return;
        }
    }

    // handle optional delimiters
    foreach ( array( 'rdelim'=>$smarty->right_delimiter, 'ldelim'=>$smarty->left_delimiter) as $optional=>$default ) {
        ${"_{$optional}"} = $default;
        $$optional = ( array_key_exists( $optional, $params ) )
            ? $params[$optional]
            : $default;
    }

    // save smarty environment as proposed by calling template
    $_cache_lifetime = $smarty->cache_lifetime;
    $smarty->cache_lifetime = $params['cache_lifetime'];
    $_caching = $smarty->caching;
    $smarty->caching = 2;
    $smarty->left_delimiter = $ldelim;
    $smarty->right_delimiter = $rdelim;

    // run the requested clipcache template
    $content = $smarty->fetch( $params['file'], $params['cache_id'] );

    // restore smarty environment as proposed by calling template
    $smarty->caching = $_caching;
    $smarty->cache_lifetime = $_cache_lifetime;
    $smarty->left_delimiter = $_ldelim;
    $smarty->right_delimiter = $_rdelim;

    return $content;
}
?>

prefilter.clipcache.php

<?php

/**
 * Smarty plugin
 * @package Smarty
 * @subpackage plugins
 */

/**
 * Smarty clipcache prefilter plugin
 *
 * Clip template source from {clipcache} blocks into a new file and replace
 * them with {include_clipcache} tags that permit isolated caching and private
 * delimiters 
 *
 * @file        prefilter.clipcache.php
 * @version     0.1.7 2006-May-11
 * @since       2005-APR-08
 *
 * @author      boots {jayboots ~ yahoo com}
 * @copyright   brainpower, boots, 2004-2006
 * @license     LGPL 2.1
 * @link        http://www.phpinsider.com/smarty-forum/viewtopic.php?p=19733#19733
 *
 * @param string $source
 * @param Smarty_Compiler $compiler
 *
 * This filter observes the following tag attributes on {clipcache} blocks:
 *
 * #param id required unique id of clipcache block within template
 * #param group required specify cache build group
 * #param ttl required time to live for template part/group
 * #param ldelim optional specify the left delimiter to use for included content
 * #param rdelim optional specify the right delimiter to use for included content
 */
function smarty_prefilter_clipcache($source, &$compiler)
{
    // setup
    require_once $compiler->_get_plugin_filepath( 'outputfilter', 'trimwhitespace' );
    $ld = $compiler->left_delimiter;
    $rd = $compiler->right_delimiter;
    $search = "{$ld}\s*clipcache\s+(.*?)\s*{$rd}(.*){$ld}\s*/clipcache\s+\\1{$rd}";

    // Pull out the clip blocks
    preg_match_all( "!$search!is", $source, $clip_blocks );
    $i=0;
    $replacements = array();

    foreach ( $clip_blocks[1] as $tag_attrs ) {
        $params = $compiler->_parse_attrs( $tag_attrs );

        foreach ( array( 'group'=>'cache_id', 'id'=>'id', 'ttl'=>'cache_lifetime' ) as $required=>$mapto ) {

            if ( !array_key_exists( $required, $params ) ) {
                $compiler->_syntax_error( "clipcache: '$required' param missing. Aborted.", E_USER_WARNING );

                return;

            } else {
                $$mapto = $params[$required];

                if ( substr( $$mapto, 0, 1 ) == "'" ) {
                    $$mapto = substr( $$mapto, 1, strlen( $$mapto ) - 2 );
                }
            }
        }

        foreach ( array( 'rdelim'=>$rd, 'ldelim'=>$ld ) as $optional=>$default ) {
            ${"_{$optional}"} = $default;
            $$optional = ( array_key_exists( $optional, $params ) )
                ? substr( $params[$optional], 1, strlen( $params[$optional] ) - 2 )
                : $default;
        }

    	// write the clip block file source template
        $write_path = rtrim( $compiler->compile_dir, "/\\" ) . DIRECTORY_SEPARATOR; 

        $file_name = $compiler->_current_file.'#' . $id;
        require_once SMARTY_CORE_DIR . 'core.write_file.php';
        smarty_core_write_file( array( 'filename'=>$write_path . 'clipcache' . DIRECTORY_SEPARATOR . $file_name, 'contents'=>$clip_blocks[2][$i++], 'create_dirs'=>true ), $compiler );

	    // prepare replacement source for the clip block
	    if ( $ldelim == $ld && $rdelim == $rd ) {
            $replacements[] = $ld . 'include_clipcache file="' . $file_name . '" cache_id="' . $cache_id . '" cache_lifetime=' . $cache_lifetime . $rd;

        } else {
            $replacements[] = $ld . 'include_clipcache file="' . $file_name . '" cache_id="' . $cache_id . '" cache_lifetime=' . $cache_lifetime . ' ldelim="' . $ldelim . '" rdelim="' . $rdelim.'"' . $rd;
        }
    }

    // replace clip blocks
    $source = preg_replace( "!$search!is", '@@@SMARTY:CLIPCACHE@@@', $source );
    smarty_outputfilter_trimwhitespace_replace( "@@@SMARTY:CLIPCACHE@@@", $replacements, $source );

    return $source;
}
?>