* $config = ezcTemplateConfiguration::getInstance() * $config->cacheManager = new MyCacheManager(); * * * The methods in this interface that are called only by ezcTemplate: * * - startCaching: A new or existing cache file is going to be (over)written. * - stopCaching: The cache file is created. * - isValid: The Template component request whether the given (parameter) cache file is valid. * - includeTemplate: A sub template is included. * * Remaining methods from the interface are only called by the user application: * * - register: Register a value (eg. userID) that is related to the cache file currently created. * - update: A registered value is updated. This may indicate that a cache file is outdated. * * * A typical cache creation process is as follows: * * 1. The user application calls $t->process("my_template.ezt"). * * 2. The template "my_template.ezt" uses {cache_template}. A custom function {fetch} retrieves * query results from the database and returns the results in one or an array of objects. The idea * behind this is that when the cache is enabled, the expensive {fetch} query will be omitted. * * 3. If a cache file exists for the requested template, the template component calls * CacheManager::isValid() to see whether the cache file is still correct. If the * cache file is still correct, it will execute it and return. Otherwise it will (over)write * the cache file. * * 4. When the cache file needs to be recreated, it calls CacheManager::startCaching() first. * The CacheManager is now informed that the operations in the user application originate from * the template that is creating the cache. For example, the operations performed on the database * are (indirectly) invoked, via a custom block or function, by the Template engine. * * 5. The application code that returns the requested data should also inform the Cache Manager via the * CacheManager::register() method. For example, the Custom Function {fetch} calls * CacheManager::register( "user", $userID ) for each requested user. * * The CacheManager::register() method stores the values in the database and relates it to the cache file * currently in creation. When one of these values changes, the cache file would be outdated. * * 6. The CacheManager::includeTemplate() is called when another template is included. Relate the current * cache with the included template and store it (for example in the database). The * CacheManager::isValid() can then also outdate the cache when a sub template is modified. * * 7. The CacheManager::stopCaching() is called when the cache file is created. * * 8. The user application should call the CacheManager::update() method when a change in the database or * application may affect any of the caches. * * * Keeping track of the values used in the caches can probably best stored in a database. Using the * file system only is also possible but makes things more complicated. The documentation of each method in * this interface has also an example implementation. The examples rely on a MySQL database with at least * the following tables. * * * The table 'cache_files' has (at least) the following properties: * * +---------------+------------------+------+-----+---------+----------------+ * | Field | Type | Null | Key | Default | Extra | * +---------------+------------------+------+-----+---------+----------------+ * | id | int(10) unsigned | NO | PRI | NULL | auto_increment | * | cache | varchar(255) | NO | MUL | | | * | expired | tinyint(4) | NO | | 0 | | * +---------------+------------------+------+-----+---------+----------------+ * * * - id: Is the unique cache ID * - cache: The (relative) path to the cache file. * - expired: Has the value 0 if the cache is valid, otherwise 1. * * * The table 'cache_values' has the following properties: * * * +-------------+------------------+------+-----+---------+-------+ * | Field | Type | Null | Key | Default | Extra | * +-------------+------------------+------+-----+---------+-------+ * | cache_id | int(10) unsigned | NO | PRI | | | * | name | varchar(50) | NO | PRI | | | * | value | varchar(255) | NO | PRI | | | * +-------------+------------------+------+-----+---------+-------+ * * * - cache_id: The relation to 'cache_files.id'. * - name: Name of the value that is stored. * - value: The value that belongs to the name. Name, value combination is up to user application. * * * Another table can also store the cache keys that belong to the cache. The structure * of this table is similar to the 'cache_values' table. * * @package Template * @version //autogen// */ interface ezcTemplateCacheManager { /** * The template engine calls this method when a new cache file will be created. * * The $template parameter contains the Template object. This allows you to reach * the current template configuration, if needed. * The $templatePath is the (relative) path to the template currently creating the cache file. * The $cachePath is the (relative) path to the cache file. * * $cacheKeys contains all cache keys used in the template (if any). * * The startCaching method commonly performs the following steps, of course it's up to your application: * * 1 Register the cache file in the cache_file table of the database. If the cache file is available set * the expired status to 0. Otherwise add the cache to the table, with a new id and expired set to 0. * * 2. Push the current cache information: cacheKeys, templatePath, Template object on an internal stack. Other * CacheManager methods need this information. * * 3. Store the current template path in a database table that handles the included templates. The current * template includes 'itself'. This makes it easier to mark the this cache outdated. * * * The following code demonstrates the steps from above and uses the ezcSignalSlot component to send the * signals to the register method: * * * // Get the current database handler. * $db = ezcDbInstance::get(); * * // (1) * // Get the current cache ID, if it does exist. * $q = $db->prepare("SELECT id FROM cache_files WHERE cache = :cache" ); * $q->bindValue( ":cache", $cachePath ); * $q->execute(); * * // One result and make sure the query is terminated. * $r = $q->fetchAll(); * * if ( sizeof( $r ) > 0 ) // Do we have any results? * { * $id = $r[0]["id"]; * // Unexpire the cache_file. * $s = $db->prepare( "UPDATE cache_files SET expired=0 WHERE id = :id" ); * $s->bindValue( ":id", $id ); * $s->execute(); * } * else * { * // Insert the new cache file * $q = $db->prepare("INSERT INTO cache_files VALUES( '', :cache, '', 0)" ); * $q->bindValue( ":cache", $cachePath ); * $q->execute(); * $id = $db->lastInsertId(); * * // (3) * // Insert your own template in the value table. * $q = $db->prepare("REPLACE INTO cache_values VALUES(:id, :name, :value)" ); * $q->bindValue( ":id", $id ); * $q->bindValue( ":name", "include" ); * $q->bindValue( ":value", $templatePath ); * $q->execute(); * } * * // (2) * // Depth keeps track of the amount of caches stored on the stack. * $this->depth++; * array_push( $this->keys, array( "cache_path" => $cachePath, "cache_id" => $id)); * * * The code above assumes that the private or protected member variables $depth and $keys * are available in the class. * * * @param ezcTemplate $template * @param string $templatePath * @param string $cachePath * @param array(string=>string) $cacheKeys * @return void */ public function startCaching($template, $templatePath, $cachePath, $cacheKeys ); /** * The stopCaching method is called by the Template Engine when the cache file is created. * * The current cache information: cacheKeys, templatePath, Template object can be popped from the * internal stack. This is demonstrated in the following code: * * * // Remove the current template cache in process. * $this->depth--; * array_pop( $this->keys ); * */ public function stopCaching(); /** * The isValid method is called by ezcTemplate to verify whether the cache is valid. * * The steps that could be implemented: * * 1. Check if the current cache file is registered and if the cache file is expired. * If the cache is not registered or the cache file is marked invalid, return false. * * 2. An extra step could be to check whether the current cache file is newer than all * included templates. The included templates are the templates included during * the creation of this cache file. The check ensures that the templates modified * by hand also renew the affected cache files. Usually, this step should only be * performed during development, because of the overhead. * * The following code is an implementation of the steps above: * * $db = ezcDbInstance::get(); * * // (1) * // Check whether the cache is registered and if it's expired. * $q = $db->prepare("SELECT id, expired FROM cache_files WHERE cache = :cache" ); * $q->bindValue( ":cache", $cacheName ); * $q->execute(); * * $r = $q->fetchAll(); // Expect 0 or 1 result * * if ( count($r) == 0 || $r[0]["expired"] == 1 ) * { * return false; * } * * // (2) * // Go through all modification times. * $q = $db->prepare( "SELECT * FROM cache_values WHERE name = 'include' AND cache_id = :id"); * $q->bindValue( ":id", $r[0]["id"] ); * $q->execute(); * * $r = $q->fetchAll(); * foreach ( $r as $a ) * { * if ( filemtime( $a["value"] ) > filemtime( $cacheName ) ) * { * return false; * } * } * * return true; * * * @param ezcTemplate $template * @param string $templateName * @param string $cacheName */ public function isValid( $template, $templateName, $cacheName ); /** * The user application should call this method to register values used in the current cache creation. * * Typically, the function that does a database query and returns the result set should also call the register() method. * The implementation of the register method should update the 'cache_values' table for all the cache_files on the stack. * See the next example code: * * * $db = ezcDbInstance::get(); * for($i = 0; $i <= $this->depth; $i++) * { * $s = $db->prepare( "REPLACE INTO cache_values VALUES ( :id, :name, :value )" ); * $s->bindValue( ":id", $this->keys[$i]["cache_id"] ); * $s->bindValue( ":name", $name ); * $s->bindValue( ":value", $value ); * $s->execute(); * } * * * The member variables: $this->depth and $this->keys keep the amount of cache files and the cache file data that currently * created. Notice that the amount of cache files on the stack only increases with a 'template include' to another * cache file. * * In the cache_values table maps a name, value to an ID. The update() method uses this information. * * @param string $name * @param string $value * @return void */ public function register( $name, $value ); /** * The user application should call this method when the value changes that is previously registered with the register() method. * * Any name-value combination that is registered should be updated when the value changes. The cache file(s) using this name-value * are marked as expired. * * The next example implementation expires all the cache files that uses the name-value: * * * $db = ezcDbInstance::get(); * $qry = "UPDATE cache_files, cache_values SET cache_files.expired=1 ". * "WHERE cache_files.id = cache_values.cache_id AND cache_values.name = :name AND cache_values.value = :value"; * $s = $db->prepare( $qry ); * $s->bindValue( ":name", $name ); * $s->bindValue( ":value", $value ); * $s->execute(); * * * @param string $name * @param string $value * @return void */ public function update( $name, $value ); /** * This method is called by the template engine when another template is included. * * The implementation of this method should register the current included template. The following code * registers all the included templates and relates them to the cache file in creation. * * * if ( $this->depth >= 0 ) * { * $db = ezcDbInstance::get(); * $id = $this->keys[ $this->depth ]["cache_id"]; * * // Insert your parent template in the value table. * $q = $db->prepare("REPLACE INTO cache_values VALUES(:id, :name, :value)" ); * $q->bindValue( ":id", $id ); * $q->bindValue( ":name", "include" ); * $q->bindValue( ":value", $templatePath ); * $q->execute(); * } * * * @param ezcTemplate $template * @param string $templatePath * @return void */ public function includeTemplate( $template, $templatePath ); /** * The cleanExpired method should remove the expired caches. * * The unused cache files on the hard-disk and the entries from the database tables: * cache_values and cache_files can be removed. This method should be called once * in a while to garantee that the system is not flooded with expired cache data. * * An example implementation: * * $db = ezcDbInstance::get(); * * $q = $db->prepare("SELECT * FROM cache_templates WHERE expired = 1" ); * $q->execute(); * $rows = $q->fetchAll(); * * foreach ($rows as $r) * { * unlink( $r["cache"] ); * } * * $db->exec("DELETE FROM cache_values USING cache_values, cache_templates WHERE cache_templates.id = cache_values.template_id AND cache_templates.expired = 1 "); * $db->exec("DELETE FROM cache_templates WHERE cache_templates.expired = 1"); * * * @return void */ public function cleanExpired(); } ?>