[Patch] Cache handler (diff)

From: Date: Mon, 16 Oct 2006 02:11:38 +0000
Subject: [Patch] Cache handler (diff)
Groups: php.smarty.dev 
Request: Send a blank email to php-smarty-dev+get-2904@lists.php.net to get a copy of this message
Sorry, the .diff didn't go thorugh; I've given it a .txt extension. Trevor Burnham

? .DS_Store ? smarty.diff ? libs/.DS_Store ? libs/internals/.DS_Store ? libs/plugins/.DS_Store ? plugins/.DS_Store Index: libs/Smarty.class.php =================================================================== RCS file: /repository/smarty/libs/Smarty.class.php,v retrieving revision 1.525 diff -u -r1.525 Smarty.class.php --- libs/Smarty.class.php 28 May 2006 17:35:05 -0000 1.525 +++ libs/Smarty.class.php 15 Oct 2006 23:51:29 -0000 @@ -324,11 +324,21 @@ var $default_resource_type = 'file'; /** - * The function used for cache file handling. If not set, built-in caching is used. + * The function used for cache handling. If not set, built-in caching is + * used. As of v2.6.15, this may be an array of cache handlers. In the + * event of a read, each is tried in sequence. In the event of a write or + * a clear, all of the handlers in the array are called. Smarty's built-in + * cache handler may be included in the array as + * 'Smarty::file_cache_handler'. + * + * Using an array for this method allows for performance improvement by + * using a volatile (memory-based) cache in conjunction with a reliable + * (disk-based) cache. Obviously, the cache handlers should be listed in + * the array in order from fastest to slowest. * - * @var null|string function name + * @var null|string|array function name(s) */ - var $cache_handler_func = null; + var $cache_handler_func = 'Smarty::file_cache_handler'; /** * This indicates which filters are automatically loaded into Smarty. @@ -946,18 +956,8 @@ $_auto_id = $this->_get_auto_id($cache_id, $compile_id); - if (!empty($this->cache_handler_func)) { - return call_user_func_array($this->cache_handler_func, - array('clear', &$this, &$dummy, $tpl_file, $cache_id, $compile_id, $exp_time)); - } else { - $_params = array('auto_base' => $this->cache_dir, - 'auto_source' => $tpl_file, - 'auto_id' => $_auto_id, - 'exp_time' => $exp_time); - require_once(SMARTY_CORE_DIR . 'core.rm_auto.php'); - return smarty_core_rm_auto($_params, $this); - } - + return $this->_cache_clear($tpl_file, $cache_id, $compile_id, + $exp_time); } @@ -986,16 +986,7 @@ if (!$this->caching) return false; - if (!isset($compile_id)) - $compile_id = $this->compile_id; - - $_params = array( - 'tpl_file' => $tpl_file, - 'cache_id' => $cache_id, - 'compile_id' => $compile_id - ); - require_once(SMARTY_CORE_DIR . 'core.read_cache_file.php'); - return smarty_core_read_cache_file($_params, $this); + return $this->_cache_exists($tpl_file, $cache_id, $compile_id); } @@ -1165,15 +1156,14 @@ // save old cache_info, initialize cache_info array_push($_cache_info, $this->_cache_info); $this->_cache_info = array(); - $_params = array( - 'tpl_file' => $resource_name, - 'cache_id' => $cache_id, - 'compile_id' => $compile_id, - 'results' => null - ); - require_once(SMARTY_CORE_DIR . 'core.read_cache_file.php'); - if (smarty_core_read_cache_file($_params, $this)) { - $_smarty_results = $_params['results']; + + /* _cache_read() fills the $_smarty_results var and, if the cache + * handler supports it, additional info in $this->_cache_info. + */ + $_smarty_results = null; + if ($this->_cache_read($_smarty_results, $resource_name, + $cache_id, $compile_id)) + { if (!empty($this->_cache_info['insert_tags'])) { $_params = array('plugins' => $this->_cache_info['insert_tags']); require_once(SMARTY_CORE_DIR . 'core.load_plugins.php'); @@ -1188,7 +1178,6 @@ $_smarty_results = smarty_core_process_compiled_include($_params, $this); } - if ($display) { if ($this->debugging) { @@ -1229,6 +1218,7 @@ return $_smarty_results; } } else { + // data not found in the cache $this->_cache_info['template'][$resource_name] = true; if ($this->cache_modified_check && $display) { header('Last-Modified: '.gmdate('D, d M Y H:i:s', time()).' GMT'); @@ -1273,12 +1263,8 @@ } if ($this->caching) { - $_params = array('tpl_file' => $resource_name, - 'cache_id' => $cache_id, - 'compile_id' => $compile_id, - 'results' => $_smarty_results); - require_once(SMARTY_CORE_DIR . 'core.write_cache_file.php'); - smarty_core_write_cache_file($_params, $this); + $this->_cache_write($_smarty_results, $resource_name, $cache_id, + $compile_id); require_once(SMARTY_CORE_DIR . 'core.process_cached_inserts.php'); $_smarty_results = smarty_core_process_cached_inserts($_params, $this); @@ -1914,6 +1900,310 @@ /** + * call the cache handler function(s) to see if a cache exists + * + * If there are multiple cache handlers, they are tried in order until one + * gives a true or false response. A handler may return a value of 'defer' + * in order to indicate that the next handler should be tried. The last + * cache handler tried is given the action 'check_final', which disallows + * a defer response. + * + * Note that older function handlers may raise an error when called with a + * 'check' or 'check_final' action, or may simply return false without + * performing any action. + * + * @param string $tpl_file name of template file + * @param string $cache_id name of cache_id + * @param string $compile_id name of compile_id + * @return boolean true if the cache exists (see above), false otherwise + */ + function _cache_exists($tpl_file, $cache_id, $compile_id) + { + if (!isset($compile_id)) + $compile_id = $this->compile_id; + + $cache_handler = $this->cache_handler_func; + if (!is_array($cache_handler)) + $cache_handler = array($cache_handler); + + /* Test the caches until one says true or false. */ + $last_func = array_pop($cache_handler); + foreach($cache_handler as $func) { + $exists = $this->_call_function($func, array('check', $this, + null, $tpl_file, $cache_id, + $compile_id, $exp_time)); + if ($exists == true || $exists == false) + return $exists; + } + return $this->_call_function($last_func, array('check_final', $this, + null, $tpl_file, $cache_id, $compile_id, + $exp_time)); + } + + /** + * call the cache handler function(s) for a clear operation + * + * If there are multiple cache handlers, they are all called sequentially + * in reverse order. + * + * @param string $tpl_file name of template file + * @param string $cache_id name of cache_id + * @param string $compile_id name of compile_id + * @param numeric $exp_time + * @return boolean true if all caches were cleared, false otherwise. + */ + function _cache_clear($tpl_file, $cache_id, $compile_id, $exp_time) + { + $cache_handler = $this->cache_handler_func; + if (!is_array($cache_handler)) + $cache_handler = array($cache_handler); + else + $cache_handler = array_reverse($cache_handler); + + /* We attempt to clear all caches. */ + $success = true; + foreach($cache_handler as $func) { + $cleared = $this->_call_function($func, array('clear', $this, + null, $tpl_file, $cache_id, + $compile_id, $exp_time)); + if (! $cleared) + $success = false; + } + + // True is returned only if all caches reported that they cleared. + return $success; + } + + + /** + * call the cache handler function(s) for a read operation + * + * If there are multiple cache handlers, they are attempted sequentially + * until ones gives a positive result. + * + * @param string results data from the cache + * @param string $tpl_file name of template file + * @param string $cache_id name of cache_id + * @param string $compile_id name of compile_id + * @return boolean true if the cache reported success, false otherwise. + */ + function _cache_read(&$results, $tpl_file, $cache_id, $compile_id, + $exp_time = null) + { + if ($this->force_compile) { + // force compile enabled, always regenerate + return false; + } + + $cache_handler = $this->cache_handler_func; + if (!is_array($cache_handler)) + $cache_handler = array($cache_handler); + + /* We try all caches in sequence until we get a result. */ + foreach($cache_handler as $func) { + $success = $this->_call_function($func, array('read', $this, &$results, + $tpl_file, $cache_id, $compile_id, + $exp_time)); + if ($success) + break; + } + + if (! $success) + return false; + + // process inserts and other metadata + $_info_start = strpos($results, "\n") + 1; + $_info_len = (int)substr($results, 0, $_info_start - 1); + $_cache_info = unserialize(substr($results, $_info_start, $_info_len)); + $results = substr($results, $_info_start + $_info_len); + + if ($this->caching == 2 && isset ($_cache_info['expires'])){ + // caching by expiration time + if ($_cache_info['expires'] > -1 && (time() > $_cache_info['expires'])) { + // cache expired, regenerate + return false; + } + } else { + // caching by lifetime + if ($this->cache_lifetime > -1 && (time() - $_cache_info['timestamp'] > $this->cache_lifetime)) { + // cache expired, regenerate + return false; + } + } + + if ($this->compile_check) { + $_params = array('get_source' => false, 'quiet'=>true); + foreach (array_keys($_cache_info['template']) as $_template_dep) { + $_params['resource_name'] = $_template_dep; + if (!$this->_fetch_resource_info($_params) || $_cache_info['timestamp'] < $_params['resource_timestamp']) { + // template file has changed, regenerate cache + return false; + } + } + + if (isset($_cache_info['config'])) { + $_params = array('resource_base_path' => $this->config_dir, 'get_source' => false, 'quiet'=>true); + foreach (array_keys($_cache_info['config']) as $_config_dep) { + $_params['resource_name'] = $_config_dep; + if (!$this->_fetch_resource_info($_params) || $_cache_info['timestamp'] < $_params['resource_timestamp']) { + // config file has changed, regenerate cache + return false; + } + } + } + } + + $content_cache[$tpl_file.','.$cache_id.','.$compile_id] = array($results, $_cache_info); + $this->_cache_info = $_cache_info; + + return true; + } + + + /** + * call the cache handler function(s) for a write operation + + * If there are multiple cache handlers, they are all called sequentially + * in reverse order. + * + * @param string $cache_content data to put in the cache + * @param string $tpl_file name of template file + * @param string $cache_id name of cache_id + * @param string $compile_id name of compile_id + * @return boolean true if all caches reported success, false otherwise. + */ + function _cache_write($cache_content, $tpl_file, $cache_id, $compile_id) + { + $cache_handler = $this->cache_handler_func; + if (!is_array($cache_handler)) + $cache_handler = array($cache_handler); + else + $cache_handler = array_reverse($cache_handler); + + // prepend metadata to $cache_content + + $this->_cache_info['timestamp'] = time(); + if ($this->cache_lifetime > -1){ + // expiration set + $this->_cache_info['expires'] = $this->_cache_info['timestamp'] + $this->cache_lifetime; + } else { + // cache will never expire + $this->_cache_info['expires'] = -1; + } + + // collapse nocache.../nocache-tags + if (preg_match_all('!\{(/?)nocache\:[0-9a-f]{32}#\d+\}!', $cache_content, $match, PREG_PATTERN_ORDER)) { + // remove everything between every pair of outermost noache.../nocache-tags + // and replace it by a single nocache-tag + // this new nocache-tag will be replaced by dynamic contents in + // smarty_core_process_compiled_includes() on a cache-read + + $match_count = count($match[0]); + $results = preg_split('!(\{/?nocache\:[0-9a-f]{32}#\d+\})!', $cache_content, -1, PREG_SPLIT_DELIM_CAPTURE); + + $level = 0; + $j = 0; + for ($i=0, $results_count = count($results); $i < $results_count && $j < $match_count; $i++) { + if ($results[$i] == $match[0][$j]) { + // nocache tag + if ($match[1][$j]) { // closing tag + $level--; + unset($results[$i]); + } else { // opening tag + if ($level++ > 0) unset($results[$i]); + } + $j++; + } elseif ($level > 0) { + unset($results[$i]); + } + } + $cache_content = implode('', $results); + } + $this->_cache_info['cache_serials'] = $this->_cache_serials; + + // prepend the cache header info into cache file + $_cache_info = serialize($this->_cache_info); + $cache_content = strlen($_cache_info) . "\n" . $_cache_info . $cache_content; + + /* We attempt to write to all caches. */ + $success = true; + foreach($cache_handler as $func) { + $written = $this->_call_function($func, array('write', $this, + $cache_content, $tpl_file, + $cache_id, $compile_id, null)); + if (! $written) + $success = false; + } + + // True is returned only if all caches reported success. + return $success; + } + + /** + * static function that serves as the default cache handler + * @param string action 'read', 'write', 'check', 'check_final' or 'clear'. + * @param Smarty smarty_obj + * @param string cache_content + * @param string tpl_file + * @param string cache_id + * @param string compile_id + * @param null exp_time + * @return boolean true on success, false otherwise. + */ + static function file_cache_handler($action, &$smarty_obj, &$cache_content, + $tpl_file=null, $cache_id=null, $compile_id=null, $exp_time=null) + { + $auto_id = $smarty_obj->_get_auto_id($cache_id, $compile_id); + + switch ($action) { + case 'check': + case 'check_final': + $_params = array('tpl_file' => $tpl_file, + 'cache_id' => $cache_id, + 'compile_id' => $compile_id); + require_once(SMARTY_CORE_DIR . 'core.read_cache_file.php'); + return smarty_core_read_cache_file($_params, $smarty_obj); + case 'clear': + $_params = array('auto_base' => $smarty_obj->cache_dir, + 'auto_source' => $tpl_file, + 'auto_id' => $auto_id, + 'exp_time' => $exp_time); + require_once(SMARTY_CORE_DIR . 'core.rm_auto.php'); + return smarty_core_rm_auto($_params, $smarty_obj); + case 'read': + $_params = array('results' => &$cache_content, + 'tpl_file' => $tpl_file, + 'cache_id' => $cache_id, + 'compile_id' => $compile_id); + require_once(SMARTY_CORE_DIR . 'core.read_cache_file.php'); + return smarty_core_read_cache_file($_params, $smarty_obj); + case 'write': + $_params = array('tpl_file' => $tpl_file, + 'cache_id' => $cache_id, + 'compile_id' => $compile_id, + 'results' => $cache_content); + require_once(SMARTY_CORE_DIR . 'core.write_cache_file.php'); + return smarty_core_write_cache_file($_params, $smarty_obj); + } + } + + /** + * wrapper for call_user_func_array() that parses class function and + * object method calls + * @param string function + * @param array params + * @return mixed + */ + function _call_function(&$function, $params) { + if (is_array($function)) { + // already an array, perform no action + } elseif (strstr($function, '::') != false) { + $function = explode('::', $function); // class static function call + } + return call_user_func_array($function, $params); + } + + /** * wrapper for include() retaining $this * @return mixed */ @@ -1936,7 +2226,6 @@ return eval($code); } /**#@-*/ - } /* vim: set expandtab: */ Index: libs/internals/core.read_cache_file.php =================================================================== RCS file: /repository/smarty/libs/internals/core.read_cache_file.php,v retrieving revision 1.3 diff -u -r1.3 core.read_cache_file.php --- libs/internals/core.read_cache_file.php 8 Jul 2005 18:11:22 -0000 1.3 +++ libs/internals/core.read_cache_file.php 15 Oct 2006 23:51:29 -0000 @@ -22,77 +22,22 @@ { static $content_cache = array(); - if ($smarty->force_compile) { - // force compile enabled, always regenerate - return false; - } - + // if this cache file has already been read during this script, don't read it again if (isset($content_cache[$params['tpl_file'].','.$params['cache_id'].','.$params['compile_id']])) { list($params['results'], $smarty->_cache_info) = $content_cache[$params['tpl_file'].','.$params['cache_id'].','.$params['compile_id']]; return true; } - if (!empty($smarty->cache_handler_func)) { - // use cache_handler function - call_user_func_array($smarty->cache_handler_func, - array('read', &$smarty, &$params['results'], $params['tpl_file'], $params['cache_id'], $params['compile_id'], null)); - } else { - // use local cache file - $_auto_id = $smarty->_get_auto_id($params['cache_id'], $params['compile_id']); - $_cache_file = $smarty->_get_auto_filename($smarty->cache_dir, $params['tpl_file'], $_auto_id); - $params['results'] = $smarty->_read_file($_cache_file); - } + // read from the cache file + $_auto_id = $smarty->_get_auto_id($params['cache_id'], $params['compile_id']); + $_cache_file = $smarty->_get_auto_filename($smarty->cache_dir, $params['tpl_file'], $_auto_id); + $params['results'] = $smarty->_read_file($_cache_file); if (empty($params['results'])) { // nothing to parse (error?), regenerate cache return false; } - $_contents = $params['results']; - $_info_start = strpos($_contents, "\n") + 1; - $_info_len = (int)substr($_contents, 0, $_info_start - 1); - $_cache_info = unserialize(substr($_contents, $_info_start, $_info_len)); - $params['results'] = substr($_contents, $_info_start + $_info_len); - - if ($smarty->caching == 2 && isset ($_cache_info['expires'])){ - // caching by expiration time - if ($_cache_info['expires'] > -1 && (time() > $_cache_info['expires'])) { - // cache expired, regenerate - return false; - } - } else { - // caching by lifetime - if ($smarty->cache_lifetime > -1 && (time() - $_cache_info['timestamp'] > $smarty->cache_lifetime)) { - // cache expired, regenerate - return false; - } - } - - if ($smarty->compile_check) { - $_params = array('get_source' => false, 'quiet'=>true); - foreach (array_keys($_cache_info['template']) as $_template_dep) { - $_params['resource_name'] = $_template_dep; - if (!$smarty->_fetch_resource_info($_params) || $_cache_info['timestamp'] < $_params['resource_timestamp']) { - // template file has changed, regenerate cache - return false; - } - } - - if (isset($_cache_info['config'])) { - $_params = array('resource_base_path' => $smarty->config_dir, 'get_source' => false, 'quiet'=>true); - foreach (array_keys($_cache_info['config']) as $_config_dep) { - $_params['resource_name'] = $_config_dep; - if (!$smarty->_fetch_resource_info($_params) || $_cache_info['timestamp'] < $_params['resource_timestamp']) { - // config file has changed, regenerate cache - return false; - } - } - } - } - - $content_cache[$params['tpl_file'].','.$params['cache_id'].','.$params['compile_id']] = array($params['results'], $_cache_info); - - $smarty->_cache_info = $_cache_info; return true; } Index: libs/internals/core.write_cache_file.php =================================================================== RCS file: /repository/smarty/libs/internals/core.write_cache_file.php,v retrieving revision 1.4 diff -u -r1.4 core.write_cache_file.php --- libs/internals/core.write_cache_file.php 1 Feb 2005 10:19:08 -0000 1.4 +++ libs/internals/core.write_cache_file.php 15 Oct 2006 23:51:29 -0000 @@ -20,75 +20,24 @@ function smarty_core_write_cache_file($params, &$smarty) { + //write to the cache file - // put timestamp in cache header - $smarty->_cache_info['timestamp'] = time(); - if ($smarty->cache_lifetime > -1){ - // expiration set - $smarty->_cache_info['expires'] = $smarty->_cache_info['timestamp'] + $smarty->cache_lifetime; - } else { - // cache will never expire - $smarty->_cache_info['expires'] = -1; - } - - // collapse nocache.../nocache-tags - if (preg_match_all('!\{(/?)nocache\:[0-9a-f]{32}#\d+\}!', $params['results'], $match, PREG_PATTERN_ORDER)) { - // remove everything between every pair of outermost noache.../nocache-tags - // and replace it by a single nocache-tag - // this new nocache-tag will be replaced by dynamic contents in - // smarty_core_process_compiled_includes() on a cache-read - - $match_count = count($match[0]); - $results = preg_split('!(\{/?nocache\:[0-9a-f]{32}#\d+\})!', $params['results'], -1, PREG_SPLIT_DELIM_CAPTURE); - - $level = 0; - $j = 0; - for ($i=0, $results_count = count($results); $i < $results_count && $j < $match_count; $i++) { - if ($results[$i] == $match[0][$j]) { - // nocache tag - if ($match[1][$j]) { // closing tag - $level--; - unset($results[$i]); - } else { // opening tag - if ($level++ > 0) unset($results[$i]); - } - $j++; - } elseif ($level > 0) { - unset($results[$i]); - } - } - $params['results'] = implode('', $results); - } - $smarty->_cache_info['cache_serials'] = $smarty->_cache_serials; - - // prepend the cache header info into cache file - $_cache_info = serialize($smarty->_cache_info); - $params['results'] = strlen($_cache_info) . "\n" . $_cache_info . $params['results']; - - if (!empty($smarty->cache_handler_func)) { - // use cache_handler function - call_user_func_array($smarty->cache_handler_func, - array('write', &$smarty, &$params['results'], $params['tpl_file'], $params['cache_id'], $params['compile_id'], null)); - } else { - // use local cache file - - if(!@is_writable($smarty->cache_dir)) { - // cache_dir not writable, see if it exists - if(!@is_dir($smarty->cache_dir)) { - $smarty->trigger_error('the $cache_dir \'' . $smarty->cache_dir . '\' does not exist, or is not a directory.', E_USER_ERROR); - return false; - } - $smarty->trigger_error('unable to write to $cache_dir \'' . realpath($smarty->cache_dir) . '\'. Be sure $cache_dir is writable by the web server user.', E_USER_ERROR); + if(!@is_writable($smarty->cache_dir)) { + // cache_dir not writable, see if it exists + if(!@is_dir($smarty->cache_dir)) { + $smarty->trigger_error('the $cache_dir \'' . $smarty->cache_dir . '\' does not exist, or is not a directory.', E_USER_ERROR); return false; } - - $_auto_id = $smarty->_get_auto_id($params['cache_id'], $params['compile_id']); - $_cache_file = $smarty->_get_auto_filename($smarty->cache_dir, $params['tpl_file'], $_auto_id); - $_params = array('filename' => $_cache_file, 'contents' => $params['results'], 'create_dirs' => true); - require_once(SMARTY_CORE_DIR . 'core.write_file.php'); - smarty_core_write_file($_params, $smarty); - return true; + $smarty->trigger_error('unable to write to $cache_dir \'' . realpath($smarty->cache_dir) . '\'. Be sure $cache_dir is writable by the web server user.', E_USER_ERROR); + return false; } + + $_auto_id = $smarty->_get_auto_id($params['cache_id'], $params['compile_id']); + $_cache_file = $smarty->_get_auto_filename($smarty->cache_dir, $params['tpl_file'], $_auto_id); + $_params = array('filename' => $_cache_file, 'contents' => $params['results'], 'create_dirs' => true); + require_once(SMARTY_CORE_DIR . 'core.write_file.php'); + smarty_core_write_file($_params, $smarty); + return true; } /* vim: set expandtab: */
« previous php.smarty.dev (#2904) next »