diff --git a/CHANGELOG b/CHANGELOG index 8ab0e1ef5..5d63dd046 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ -* 1.21.3 (2015-XX-XX) +* 1.22.0 (2015-XX-XX) + * deprecated Twig_Environment::clearCacheFiles() + * added a way to override the filesystem template cache system * added a way to get the original template source from Twig_Template * 1.21.2 (2015-09-09) diff --git a/composer.json b/composer.json index 2d3438ec2..5d31ac1c9 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.21-dev" + "dev-master": "1.22-dev" } } } diff --git a/doc/deprecated.rst b/doc/deprecated.rst index 1489174a6..8bcfe84ec 100644 --- a/doc/deprecated.rst +++ b/doc/deprecated.rst @@ -143,6 +143,9 @@ Miscellaneous * As of Twig 1.x, ``Twig_Environment::clearTemplateCache()`` is deprecated and will be removed in 2.0. +* As of Twig 1.x, ``Twig_Environment::clearCacheFiles()`` is deprecated and + will be removed in 2.0. + * As of Twig 1.x, ``Twig_Template::getEnvironment()`` and ``Twig_TemplateInterface::getEnvironment()`` are deprecated and will be removed in 2.0. diff --git a/ext/twig/php_twig.h b/ext/twig/php_twig.h index 5fb311c39..6abe4a8a0 100644 --- a/ext/twig/php_twig.h +++ b/ext/twig/php_twig.h @@ -15,7 +15,7 @@ #ifndef PHP_TWIG_H #define PHP_TWIG_H -#define PHP_TWIG_VERSION "1.21.3-DEV" +#define PHP_TWIG_VERSION "1.22.0-DEV" #include "php.h" diff --git a/lib/Twig/Cache/CacheInterface.php b/lib/Twig/Cache/CacheInterface.php deleted file mode 100644 index 99eba0a86..000000000 --- a/lib/Twig/Cache/CacheInterface.php +++ /dev/null @@ -1,64 +0,0 @@ - - */ -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/Filesystem.php b/lib/Twig/Cache/Filesystem.php new file mode 100644 index 000000000..c62c7111a --- /dev/null +++ b/lib/Twig/Cache/Filesystem.php @@ -0,0 +1,89 @@ + + */ +class Twig_Cache_Filesystem implements Twig_CacheInterface +{ + private $directory; + + /** + * @param $directory string The root cache directory + */ + public function __construct($directory) + { + $this->directory = $directory; + } + + /** + * {@inheritdoc} + */ + public function generateKey($className, $prefix) + { + $class = substr($className, strlen($prefix)); + + return $this->directory.'/'.$class[0].'/'.$class[1].'/'.$class.'.php'; + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + return is_file($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)) { + 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($key)); + if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $key)) { + @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/FilesystemCache.php b/lib/Twig/Cache/FilesystemCache.php deleted file mode 100644 index 2fc465845..000000000 --- a/lib/Twig/Cache/FilesystemCache.php +++ /dev/null @@ -1,103 +0,0 @@ - - */ -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 deleted file mode 100644 index 96fc37ef6..000000000 --- a/lib/Twig/Cache/MemoryCache.php +++ /dev/null @@ -1,75 +0,0 @@ - - */ -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/Cache/Null.php b/lib/Twig/Cache/Null.php new file mode 100644 index 000000000..4812600e9 --- /dev/null +++ b/lib/Twig/Cache/Null.php @@ -0,0 +1,58 @@ + + */ +class Twig_Cache_Null implements Twig_CacheInterface +{ + /** + * {@inheritdoc} + */ + public function generateKey($className, $prefix) + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function has($key) + { + return false; + } + + /** + * {@inheritdoc} + */ + public function write($key, $content) + { + eval('?>'.$content); + } + + /** + * {@inheritdoc} + */ + public function load($key) + { + } + + /** + * {@inheritdoc} + */ + public function getTimestamp($key) + { + // never called as has() always returns false + return 0; + } +} diff --git a/lib/Twig/CacheInterface.php b/lib/Twig/CacheInterface.php new file mode 100644 index 000000000..390e7b039 --- /dev/null +++ b/lib/Twig/CacheInterface.php @@ -0,0 +1,65 @@ + + */ +interface Twig_CacheInterface +{ + /** + * Generates a cache key for the given template class name. + * + * @param string $className The template class name + * @param string $prefix A template class prefix + * + * @return string + */ + public function generateKey($className, $prefix); + + /** + * Checks if the cache key exists. + * + * @param string $key The cache key + * + * @return bool true if the cache key exists, false otherwise + */ + public function has($key); + + /** + * Writes the compiled template to cache. + * + * @param string $key The cache key + * @param string $content The template representation as a PHP class + */ + public function write($key, $content); + + /** + * Loads a template from the cache. + * + * @param string $key The cache key + */ + public function load($key); + + /** + * Returns the modification timestamp of a key. + * + * @param string $key The cache key + * + * @return int + */ + public function getTimestamp($key); +} diff --git a/lib/Twig/Environment.php b/lib/Twig/Environment.php index e7a2ef550..1a1a933db 100644 --- a/lib/Twig/Environment.php +++ b/lib/Twig/Environment.php @@ -16,7 +16,7 @@ */ class Twig_Environment { - const VERSION = '1.21.3-DEV'; + const VERSION = '1.22.0-DEV'; protected $charset; protected $loader; @@ -45,6 +45,10 @@ class Twig_Environment protected $filterCallbacks = array(); protected $staging; + private $originalCache; + private $bcWriteCacheFile = false; + private $bcGetCacheFilename = false; + /** * Constructor. * @@ -58,8 +62,9 @@ class Twig_Environment * * base_template_class: The base template class to use for generated * templates (default to Twig_Template). * - * * cache: An absolute path where to store the compiled templates, or - * false to disable compilation cache (default). + * * cache: An absolute path where to store the compiled templates, + * a Twig_Cache_Interface implementation, + * or false to disable compilation cache (default). * * * auto_reload: Whether to reload the template if the original source changed. * If you don't provide the auto_reload option, it will be @@ -112,6 +117,23 @@ class Twig_Environment $this->addExtension(new Twig_Extension_Escaper($options['autoescape'])); $this->addExtension(new Twig_Extension_Optimizer($options['optimizations'])); $this->staging = new Twig_Extension_Staging(); + + // For BC + if (is_string($this->originalCache)) { + $r = new ReflectionMethod($this, 'writeCacheFile'); + if ($r->getDeclaringClass()->getName() !== __CLASS__) { + @trigger_error('The Twig_Environment::writeCacheFile method is deprecated and will be removed in Twig 2.0.', E_USER_DEPRECATED); + + $this->bcWriteCacheFile = true; + } + + $r = new ReflectionMethod($this, 'getCacheFilename'); + if ($r->getDeclaringClass()->getName() !== __CLASS__) { + @trigger_error('The Twig_Environment::getCacheFilename method is deprecated and will be removed in Twig 2.0.', E_USER_DEPRECATED); + + $this->bcGetCacheFilename = true; + } + } } /** @@ -215,29 +237,36 @@ class Twig_Environment /** * Gets the current cache implementation. * - * @return Twig_Cache_CacheInterface + * @param bool $original Whether to return the original cache option or the real cache instance + * + * @return Twig_CacheInterface|string|false A Twig_CacheInterface implementation, + * an absolute path to the compiled templates, + * or false to disable cache */ - public function getCache() + public function getCache($original = true) { - return $this->cache; + return $original ? $this->originalCache : $this->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 - */ + /** + * Sets the current cache implementation. + * + * @param Twig_CacheInterface|string|false $cache A Twig_CacheInterface implementation, + * an absolute path to the compiled templates, + * or false to disable cache + */ public function setCache($cache) { - if ($cache instanceof Twig_Cache_CacheInterface) { - $this->cache = $cache; - } elseif (is_string($cache)) { - $this->cache = new Twig_Cache_FilesystemCache($cache); + if (is_string($cache)) { + $this->originalCache = $cache; + $this->cache = new Twig_Cache_Filesystem($cache); + } elseif (false === $cache) { + $this->originalCache = $cache; + $this->cache = new Twig_Cache_Null(); + } elseif ($cache instanceof Twig_CacheInterface) { + $this->originalCache = $this->cache = $cache; } else { - $this->cache = new Twig_Cache_MemoryCache(); + throw new LogicException(sprintf('Cache can only be a string, false, or a Twig_CacheInterface implementation.')); } } @@ -246,11 +275,17 @@ class Twig_Environment * * @param string $name The template name * - * @return string The cache file name + * @return string|false The cache file name or false when caching is disabled + * + * @deprecated since 1.22 (to be removed in 2.0) */ public function getCacheFilename($name) { - return $this->cache->getCacheKey($this->getTemplateClass($name), $this->templateClassPrefix); + @trigger_error(sprintf('The %s method is deprecated and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + + $key = $this->cache->generateKey($this->getTemplateClass($name), $this->templateClassPrefix); + + return !$key ? false : $key; } /** @@ -328,12 +363,18 @@ class Twig_Environment } if (!class_exists($cls, false)) { - $key = $this->cache->getCacheKey($cls, $this->templateClassPrefix); + if ($this->bcGetCacheFilename) { + $key = $this->getCacheFilename($name); + } else { + $key = $this->cache->generateKey($cls, $this->templateClassPrefix); + } - 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)); + if (!$this->cache->has($key) || ($this->isAutoReload() && !$this->isTemplateFresh($name, $this->cache->getTimestamp($key)))) { + if ($this->bcWriteCacheFile) { + $this->writeCacheFile($key, $this->compileSource($this->getLoader()->getSource($name), $name)); + } else { + $this->cache->write($key, $this->compileSource($this->getLoader()->getSource($name), $name)); + } } $this->cache->load($key); @@ -455,10 +496,20 @@ class Twig_Environment /** * Clears the template cache files on the filesystem. + * + * @deprecated since 1.22 (to be removed in 2.0) */ public function clearCacheFiles() { - $this->cache->clear(); + @trigger_error(sprintf('The %s method is deprecated and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + + if (is_string($this->originalCache)) { + foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->originalCache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if ($file->isFile()) { + @unlink($file->getPathname()); + } + } + } } /** @@ -1271,4 +1322,12 @@ class Twig_Environment $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]); } } + + /** + * @deprecated since 1.22 (to be removed in 2.0) + */ + protected function writeCacheFile($file, $content) + { + $this->cache->write($file, $content); + } } diff --git a/test/Twig/Tests/EnvironmentTest.php b/test/Twig/Tests/EnvironmentTest.php index c34903200..64baecf1f 100644 --- a/test/Twig/Tests/EnvironmentTest.php +++ b/test/Twig/Tests/EnvironmentTest.php @@ -150,15 +150,14 @@ class Twig_Tests_EnvironmentTest extends PHPUnit_Framework_TestCase public function testExtensionsAreNotInitializedWhenRenderingACompiledTemplate() { - $options = array('cache' => sys_get_temp_dir().'/twig', 'auto_reload' => false, 'debug' => false); + $cache = new Twig_Cache_Filesystem($dir = sys_get_temp_dir().'/twig'); + $options = array('cache' => $cache, 'auto_reload' => false, 'debug' => false); // force compilation $twig = new Twig_Environment($loader = new Twig_Loader_Array(array('index' => '{{ foo }}')), $options); - $cache = $twig->getCacheFilename('index'); - if (!is_dir(dirname($cache))) { - mkdir(dirname($cache), 0777, true); - } - file_put_contents($cache, $twig->compileSource('{{ foo }}', 'index')); + + $key = $cache->generateKey($twig->getTemplateClass('index'), $twig->getTemplateClassPrefix()); + $cache->write($key, $twig->compileSource('{{ foo }}', 'index')); // check that extensions won't be initialized when rendering a template that is already in the cache $twig = $this @@ -174,7 +173,7 @@ class Twig_Tests_EnvironmentTest extends PHPUnit_Framework_TestCase $output = $twig->render('index', array('foo' => 'bar')); $this->assertEquals('bar', $output); - unlink($cache); + unlink($key); } public function testAddExtension() diff --git a/test/Twig/Tests/FileCachingTest.php b/test/Twig/Tests/FileCachingTest.php index 6f5bfee41..14e0144c6 100644 --- a/test/Twig/Tests/FileCachingTest.php +++ b/test/Twig/Tests/FileCachingTest.php @@ -38,6 +38,9 @@ class Twig_Tests_FileCachingTest extends PHPUnit_Framework_TestCase $this->removeDir($this->tmpDir); } + /** + * @group legacy + */ public function testWritingCacheFiles() { $name = 'index'; @@ -48,6 +51,9 @@ class Twig_Tests_FileCachingTest extends PHPUnit_Framework_TestCase $this->fileName = $cacheFileName; } + /** + * @group legacy + */ public function testClearingCacheFiles() { $name = 'index2';