diff --git a/src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php b/src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php new file mode 100644 index 000000000000..db6d9ce4cf4e --- /dev/null +++ b/src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Adapter; + +use Symfony\Component\Cache\Exception\InvalidArgumentException; + +/** + * @author Nicolas Grekas + */ +class FilesystemAdapter extends AbstractAdapter +{ + private $directory; + + public function __construct($directory, $defaultLifetime = null) + { + parent::__construct('', $defaultLifetime); + + if (!file_exists($dir = $directory.'/.')) { + @mkdir($directory, 0777, true); + } + if (false === $dir = realpath($dir)) { + throw new InvalidArgumentException(sprintf('Cache directory does not exist (%s)', $directory)); + } + if (!is_writable($dir .= DIRECTORY_SEPARATOR)) { + throw new InvalidArgumentException(sprintf('Cache directory is not writable (%s)', $directory)); + } + // On Windows the whole path is limited to 258 chars + if ('\\' === DIRECTORY_SEPARATOR && strlen($dir) > 190) { + throw new InvalidArgumentException(sprintf('Cache directory too long (%s)', $directory)); + } + + $this->directory = $dir; + } + + /** + * {@inheritdoc} + */ + protected function doFetch(array $ids) + { + $values = array(); + $now = time(); + + foreach ($ids as $id) { + $file = $this->getFile($id); + if (!$h = @fopen($file, 'rb')) { + continue; + } + flock($h, LOCK_SH); + if ($now >= (int) $expiresAt = fgets($h)) { + flock($h, LOCK_UN); + fclose($h); + if (isset($expiresAt[0])) { + @unlink($file); + } + } else { + $value = stream_get_contents($h); + flock($h, LOCK_UN); + fclose($h); + $values[$id] = unserialize($value); + } + } + + return $values; + } + + /** + * {@inheritdoc} + */ + protected function doHave($id) + { + $file = $this->getFile($id); + + return file_exists($file) && (@filemtime($file) > time() || $this->doFetch(array($id))); + } + + /** + * {@inheritdoc} + */ + protected function doClear($namespace) + { + $ok = true; + + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS)) as $file) { + $ok = ($file->isDir() || @unlink($file) || !file_exists($file)) && $ok; + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + protected function doDelete(array $ids) + { + $ok = true; + + foreach ($ids as $id) { + $file = $this->getFile($id); + $ok = (!file_exists($file) || @unlink($file) || !file_exists($file)) && $ok; + } + + return $ok; + } + + /** + * {@inheritdoc} + */ + protected function doSave(array $values, $lifetime) + { + $ok = true; + $expiresAt = $lifetime ? time() + $lifetime : PHP_INT_MAX; + + foreach ($values as $id => $value) { + $file = $this->getFile($id); + $dir = dirname($file); + if (!file_exists($dir)) { + @mkdir($dir, 0777, true); + } + $value = $expiresAt."\n".serialize($value); + if (false !== @file_put_contents($file, $value, LOCK_EX)) { + @touch($file, $expiresAt); + } else { + $ok = false; + } + } + + return $ok; + } + + private function getFile($id) + { + $hash = hash('sha256', $id); + + return $this->directory.$hash[0].DIRECTORY_SEPARATOR.$hash[1].DIRECTORY_SEPARATOR.$hash; + } +} diff --git a/src/Symfony/Component/Cache/Tests/Adapter/FilesystemTest.php b/src/Symfony/Component/Cache/Tests/Adapter/FilesystemTest.php new file mode 100644 index 000000000000..28786501a86e --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/FilesystemTest.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\Adapter; + +use Cache\IntegrationTests\CachePoolTest; +use Symfony\Component\Cache\Adapter\FilesystemAdapter; + +/** + * @group time-sensitive + */ +class FilesystemAdapterTest extends CachePoolTest +{ + public function createCachePool() + { + if (defined('HHVM_VERSION')) { + $this->skippedTests['testDeferredSaveWithoutCommit'] = 'Fails on HHVM'; + } + + return new FilesystemAdapter(sys_get_temp_dir().DIRECTORY_SEPARATOR.'sf-cache'); + } +}