diff --git a/lib/Twig/Cache/CacheInterface.php b/lib/Twig/Cache/CacheInterface.php new file mode 100644 index 0000000000..99eba0a867 --- /dev/null +++ b/lib/Twig/Cache/CacheInterface.php @@ -0,0 +1,64 @@ + + */ +interface Twig_Cache_CacheInterface +{ + /** + * Returns cache key depending on current implementation. + * + * @param string $className Template class name + * @param string $prefix Global template class prefix + * @return string + */ + public function getCacheKey($className, $prefix); + + /** + * Clears all cached templates + */ + public function clear(); + + /** + * Checks if given cache entity exists. + * + * @param string $key + * @return bool + */ + public function has($key); + + /** + * Writes compiled template to cache. + * + * @param $key + * @param $content + */ + public function write($key, $content); + + /** + * Loads cache file. This function returns nothing, it should require or eval() the compiled template. + * + * @param $key + */ + public function load($key); + + /** + * Returns timestamp of cached template.. + * + * @param $key + * + * @return int + */ + public function getTimestamp($key); +} diff --git a/lib/Twig/Cache/FilesystemCache.php b/lib/Twig/Cache/FilesystemCache.php new file mode 100644 index 0000000000..2fc465845a --- /dev/null +++ b/lib/Twig/Cache/FilesystemCache.php @@ -0,0 +1,103 @@ + + */ +class Twig_Cache_FilesystemCache implements Twig_Cache_CacheInterface +{ + /** + * Root cache directory + * + * @var string + */ + protected $directory; + + public function __construct($directory) + { + $this->directory = $directory; + } + + /** + * {@inheritdoc} + */ + public function getCacheKey($className, $prefix) + { + $class = substr($className, strlen($prefix)); + + return $this->directory.'/'.substr($class, 0, 2).'/'.substr($class, 2, 2).'/'.substr($class, 4).'.php'; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->directory), RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if ($file->isFile()) { + @unlink($file->getPathname()); + } + } + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + return is_readable($key); + } + + /** + * {@inheritdoc} + */ + public function load($key) + { + require_once $key; + } + + /** + * {@inheritdoc} + */ + public function write($key, $content) + { + $dir = dirname($key); + if (!is_dir($dir)) { + if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) { + throw new RuntimeException(sprintf("Unable to create the cache directory (%s).", $dir)); + } + } elseif (!is_writable($dir)) { + throw new RuntimeException(sprintf("Unable to write in the cache directory (%s).", $dir)); + } + + $tmpFile = tempnam($dir, basename($key)); + if (false !== @file_put_contents($tmpFile, $content)) { + // rename does not work on Win32 before 5.2.6 + if (@rename($tmpFile, $key) || (@copy($tmpFile, $key) && unlink($tmpFile))) { + @chmod($key, 0666 & ~umask()); + + return; + } + } + + throw new RuntimeException(sprintf('Failed to write cache file "%s".', $key)); + } + + /** + * {@inheritdoc} + */ + public function getTimestamp($key) + { + return filemtime($key); + } +} diff --git a/lib/Twig/Cache/MemoryCache.php b/lib/Twig/Cache/MemoryCache.php new file mode 100644 index 0000000000..96fc37ef6b --- /dev/null +++ b/lib/Twig/Cache/MemoryCache.php @@ -0,0 +1,75 @@ + + */ +class Twig_Cache_MemoryCache implements Twig_Cache_CacheInterface +{ + /** + * Current cached files + * @var array + */ + protected $data = array(); + + /** + * {@inheritdoc} + */ + public function getCacheKey($className, $prefix) + { + return $className; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->data = array(); + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + return isset($this->data[$key]); + } + + /** + * {@inheritdoc} + */ + public function write($key, $content) + { + $this->data[$key] = array( + 'data' => $content, + 'timestamp' => time() + ); + } + + /** + * {@inheritdoc} + */ + public function load($key) + { + eval('?>'.$this->data[$key]['data']); + } + + /** + * {@inheritdoc} + */ + public function getTimestamp($key) + { + return $this->data[$key]['timestamp']; + } +} diff --git a/lib/Twig/Environment.php b/lib/Twig/Environment.php index 4bfe68df84..e7a2ef550c 100644 --- a/lib/Twig/Environment.php +++ b/lib/Twig/Environment.php @@ -213,24 +213,32 @@ public function isStrictVariables() } /** - * Gets the cache directory or false if cache is disabled. + * Gets the current cache implementation. * - * @return string|false + * @return Twig_Cache_CacheInterface */ public function getCache() { return $this->cache; } - /** - * Sets the cache directory or false if cache is disabled. - * - * @param string|false $cache The absolute path to the compiled templates, - * or false to disable cache - */ + /** + * Sets the current cache implementation. Can be absolute path to the directory (Twig_Cache_FilesystemCache will + * be used then), or instance of Twig_Cache_CacheInterface if you need custom cache implementation. Anything else + * will cause Twig_Cache_MemoryCache to be used. + * + * @param Twig_Cache_CacheInterface|string|mixed $cache The absolute path to the compiled templates, + * or false to disable cache + */ public function setCache($cache) { - $this->cache = $cache ? $cache : false; + if ($cache instanceof Twig_Cache_CacheInterface) { + $this->cache = $cache; + } elseif (is_string($cache)) { + $this->cache = new Twig_Cache_FilesystemCache($cache); + } else { + $this->cache = new Twig_Cache_MemoryCache(); + } } /** @@ -238,17 +246,11 @@ public function setCache($cache) * * @param string $name The template name * - * @return string|false The cache file name or false when caching is disabled + * @return string The cache file name */ public function getCacheFilename($name) { - if (false === $this->cache) { - return false; - } - - $class = substr($this->getTemplateClass($name), strlen($this->templateClassPrefix)); - - return $this->getCache().'/'.$class[0].'/'.$class[1].'/'.$class.'.php'; + return $this->cache->getCacheKey($this->getTemplateClass($name), $this->templateClassPrefix); } /** @@ -326,15 +328,15 @@ public function loadTemplate($name, $index = null) } if (!class_exists($cls, false)) { - if (false === $cache = $this->getCacheFilename($name)) { - eval('?>'.$this->compileSource($this->getLoader()->getSource($name), $name)); - } else { - if (!is_file($cache) || ($this->isAutoReload() && !$this->isTemplateFresh($name, filemtime($cache)))) { - $this->writeCacheFile($cache, $this->compileSource($this->getLoader()->getSource($name), $name)); - } + $key = $this->cache->getCacheKey($cls, $this->templateClassPrefix); - require_once $cache; + if (!$this->cache->has($key) || + ($this->isAutoReload() && !$this->isTemplateFresh($name, $this->cache->getTimestamp($key))) + ) { + $this->cache->write($key, $this->compileSource($this->getLoader()->getSource($name), $name)); } + + $this->cache->load($key); } if (!$this->runtimeInitialized) { @@ -456,15 +458,7 @@ public function clearTemplateCache() */ public function clearCacheFiles() { - if (false === $this->cache) { - return; - } - - foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->cache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) { - if ($file->isFile()) { - @unlink($file->getPathname()); - } - } + $this->cache->clear(); } /** @@ -1277,28 +1271,4 @@ protected function initExtension(Twig_ExtensionInterface $extension) $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]); } } - - protected function writeCacheFile($file, $content) - { - $dir = dirname($file); - if (!is_dir($dir)) { - if (false === @mkdir($dir, 0777, true)) { - clearstatcache(false, $dir); - if (!is_dir($dir)) { - throw new RuntimeException(sprintf('Unable to create the cache directory (%s).', $dir)); - } - } - } elseif (!is_writable($dir)) { - throw new RuntimeException(sprintf('Unable to write in the cache directory (%s).', $dir)); - } - - $tmpFile = tempnam($dir, basename($file)); - if ((false !== @file_put_contents($tmpFile, $content)) && @rename($tmpFile, $file)) { - @chmod($file, 0666 & ~umask()); - - return; - } - - throw new RuntimeException(sprintf('Failed to write cache file "%s".', $file)); - } }