Skip to content

Commit

Permalink
Implemented filesystem cache, one and only (see #1415)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewtch authored and fabpot committed Sep 11, 2015
1 parent 8af7659 commit 04cc7e4
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 57 deletions.
64 changes: 64 additions & 0 deletions lib/Twig/Cache/CacheInterface.php
@@ -0,0 +1,64 @@
<?php

/*
* This file is part of Twig.
*
* (c) 2014 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/**
* Interface Twig_Cache_CacheInterface needs to be implemented by cache classes
*
* @author Andrew Tch <andrew@noop.lv>
*/
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);
}
103 changes: 103 additions & 0 deletions lib/Twig/Cache/FilesystemCache.php
@@ -0,0 +1,103 @@
<?php

/*
* This file is part of Twig.
*
* (c) 2014 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/**
* Class Twig_Cache_FilesystemCache - the one and only officially supported cache for Twig
*
* @author Andrew Tch <andrew@noop.lv>
*/
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);
}
}
75 changes: 75 additions & 0 deletions lib/Twig/Cache/MemoryCache.php
@@ -0,0 +1,75 @@
<?php

/*
* This file is part of Twig.
*
* (c) 2014 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/**
* Class Twig_Cache_MemoryCache - use this to disable caching
*
* @author Andrew Tch <andrew@noop.lv>
*/
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'];
}
}
84 changes: 27 additions & 57 deletions lib/Twig/Environment.php
Expand Up @@ -213,42 +213,44 @@ 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();
}
}

/**
* Gets the cache filename for a given template.
*
* @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);
}

/**
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
}

/**
Expand Down Expand Up @@ -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));
}
}

0 comments on commit 04cc7e4

Please sign in to comment.