From 8983e83d91a105dc1f558c0d85c86d001468567c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 25 May 2016 09:44:10 +0200 Subject: [PATCH] [Cache] Optimize & wire PhpFilesAdapter --- .travis.yml | 1 + appveyor.yml | 2 + .../Cache/Adapter/AbstractAdapter.php | 9 + .../Cache/Adapter/FilesystemAdapter.php | 65 +---- .../Cache/Adapter/FilesystemAdapterTrait.php | 110 ++++++++ .../Cache/Adapter/Helper/FilesCacheHelper.php | 137 --------- .../Cache/Adapter/PhpFilesAdapter.php | 172 +++++------- .../Adapter/AbstractAppendOnlyAdapterTest.php | 260 ------------------ .../Tests/Adapter/FilesystemAdapterTest.php | 18 +- .../Tests/Adapter/PhpFilesAdapterTest.php | 50 +--- 10 files changed, 215 insertions(+), 609 deletions(-) create mode 100644 src/Symfony/Component/Cache/Adapter/FilesystemAdapterTrait.php delete mode 100644 src/Symfony/Component/Cache/Adapter/Helper/FilesCacheHelper.php delete mode 100644 src/Symfony/Component/Cache/Tests/Adapter/AbstractAppendOnlyAdapterTest.php diff --git a/.travis.yml b/.travis.yml index 36499dd22a..4f950d2b0c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,6 +47,7 @@ before_install: - if [[ $PHP != hhvm ]]; then INI_FILE=~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; else INI_FILE=/etc/hhvm/php.ini; fi - if [[ ! $skip ]]; then echo memory_limit = -1 >> $INI_FILE; fi - if [[ ! $skip ]]; then echo session.gc_probability = 0 >> $INI_FILE; fi + - if [[ ! $skip ]]; then echo opcache.enable_cli = 1 >> $INI_FILE; fi - if [[ ! $skip && $PHP = 5.* ]]; then echo extension = mongo.so >> $INI_FILE; fi - if [[ ! $skip && $PHP = 5.* ]]; then echo extension = memcache.so >> $INI_FILE; fi - if [[ ! $skip && $PHP = 5.* ]]; then (echo yes | pecl install -f apcu-4.0.10 && echo apc.enable_cli = 1 >> $INI_FILE); fi diff --git a/appveyor.yml b/appveyor.yml index e361b4d0c3..4474ce33a6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -35,6 +35,8 @@ install: - IF %PHP%==1 echo date.timezone="UTC" >> php.ini-min - IF %PHP%==1 echo extension_dir=ext >> php.ini-min - IF %PHP%==1 copy /Y php.ini-min php.ini-max + - IF %PHP%==1 echo zend_extension=php_opcache.dll >> php.ini-max + - IF %PHP%==1 echo opcache.enable_cli=1 >> php.ini-max - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini-max - IF %PHP%==1 echo extension=php_apcu.dll >> php.ini-max - IF %PHP%==1 echo apc.enable_cli=1 >> php.ini-max diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index 01207f54e6..74c499e560 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -70,6 +70,15 @@ abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface public static function createSystemCache($namespace, $defaultLifetime, $version, $directory, LoggerInterface $logger = null) { + if (!ApcuAdapter::isSupported() && PhpFilesAdapter::isSupported()) { + $opcache = new PhpFilesAdapter($namespace, $defaultLifetime, $directory); + if (null !== $logger) { + $opcache->setLogger($logger); + } + + return $opcache; + } + $fs = new FilesystemAdapter($namespace, $defaultLifetime, $directory); if (null !== $logger) { $fs->setLogger($logger); diff --git a/src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php b/src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php index cf617c3b0e..6b3360c031 100644 --- a/src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/FilesystemAdapter.php @@ -11,27 +11,17 @@ namespace Symfony\Component\Cache\Adapter; -use Symfony\Component\Cache\Adapter\Helper\FilesCacheHelper; - /** * @author Nicolas Grekas */ class FilesystemAdapter extends AbstractAdapter { - /** - * @var FilesCacheHelper - */ - protected $filesCacheHelper; + use FilesystemAdapterTrait; - /** - * @param string $namespace Cache namespace - * @param int $defaultLifetime Default lifetime for cache items - * @param null $directory Path where cache items should be stored, defaults to sys_get_temp_dir().'/symfony-cache' - */ public function __construct($namespace = '', $defaultLifetime = 0, $directory = null) { - parent::__construct($namespace, $defaultLifetime); - $this->filesCacheHelper = new FilesCacheHelper($directory, $namespace); + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); } /** @@ -43,7 +33,7 @@ class FilesystemAdapter extends AbstractAdapter $now = time(); foreach ($ids as $id) { - $file = $this->filesCacheHelper->getFilePath($id); + $file = $this->getFile($id); if (!$h = @fopen($file, 'rb')) { continue; } @@ -70,41 +60,11 @@ class FilesystemAdapter extends AbstractAdapter */ protected function doHave($id) { - $file = $this->filesCacheHelper->getFilePath($id); + $file = $this->getFile($id); return file_exists($file) && (@filemtime($file) > time() || $this->doFetch(array($id))); } - /** - * {@inheritdoc} - */ - protected function doClear($namespace) - { - $ok = true; - $directory = $this->filesCacheHelper->getDirectory(); - - foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($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->filesCacheHelper->getFilePath($id); - $ok = (!file_exists($file) || @unlink($file) || !file_exists($file)) && $ok; - } - - return $ok; - } - /** * {@inheritdoc} */ @@ -114,22 +74,9 @@ class FilesystemAdapter extends AbstractAdapter $expiresAt = $lifetime ? time() + $lifetime : PHP_INT_MAX; foreach ($values as $id => $value) { - $fileContent = $this->createCacheFileContent($id, $value, $expiresAt); - $ok = $this->filesCacheHelper->saveFileForId($id, $fileContent, $expiresAt) && $ok; + $ok = $this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".serialize($value), $expiresAt) && $ok; } return $ok; } - - /** - * @param string $id - * @param mixed $value - * @param int $expiresAt - * - * @return string - */ - protected function createCacheFileContent($id, $value, $expiresAt) - { - return $expiresAt."\n".rawurlencode($id)."\n".serialize($value); - } } diff --git a/src/Symfony/Component/Cache/Adapter/FilesystemAdapterTrait.php b/src/Symfony/Component/Cache/Adapter/FilesystemAdapterTrait.php new file mode 100644 index 0000000000..809ec15dfb --- /dev/null +++ b/src/Symfony/Component/Cache/Adapter/FilesystemAdapterTrait.php @@ -0,0 +1,110 @@ + + * + * 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 + */ +trait FilesystemAdapterTrait +{ + private $directory; + private $tmp; + + private function init($namespace, $directory) + { + if (!isset($directory[0])) { + $directory = sys_get_temp_dir().'/symfony-cache'; + } + if (isset($namespace[0])) { + if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) { + throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); + } + $directory .= '/'.$namespace; + } + 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) > 234) { + throw new InvalidArgumentException(sprintf('Cache directory too long (%s)', $directory)); + } + + $this->directory = $dir; + $this->tmp = $this->directory.uniqid('', true); + } + + /** + * {@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; + } + + private function write($file, $data, $expiresAt = null) + { + if (false === @file_put_contents($this->tmp, $data)) { + return false; + } + if (null !== $expiresAt) { + @touch($this->tmp, $expiresAt); + } + + if (@rename($this->tmp, $file)) { + return true; + } + @unlink($this->tmp); + + return false; + } + + private function getFile($id, $mkdir = false) + { + $hash = str_replace('/', '-', base64_encode(md5(static::class.$id, true))); + $dir = $this->directory.$hash[0].DIRECTORY_SEPARATOR.$hash[1].DIRECTORY_SEPARATOR; + + if ($mkdir && !file_exists($dir)) { + @mkdir($dir, 0777, true); + } + + return $dir.substr($hash, 2, -2); + } +} diff --git a/src/Symfony/Component/Cache/Adapter/Helper/FilesCacheHelper.php b/src/Symfony/Component/Cache/Adapter/Helper/FilesCacheHelper.php deleted file mode 100644 index 305046c0fb..0000000000 --- a/src/Symfony/Component/Cache/Adapter/Helper/FilesCacheHelper.php +++ /dev/null @@ -1,137 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Cache\Adapter\Helper; - -use Symfony\Component\Cache\Exception\InvalidArgumentException; - -class FilesCacheHelper -{ - /** - * @var string - */ - private $fileSuffix; - - /** - * @var string - */ - private $directory; - - /** - * @param string $directory Path where cache items should be stored, defaults to sys_get_temp_dir().'/symfony-cache' - * @param string $namespace Cache namespace - * @param string $version Version (works the same way as namespace) - * @param string $fileSuffix Suffix that will be appended to all file names - */ - public function __construct($directory = null, $namespace = null, $version = null, $fileSuffix = '') - { - if (!isset($directory[0])) { - $directory = sys_get_temp_dir().'/symfony-cache'; - } - if (isset($namespace[0])) { - if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) { - throw new InvalidArgumentException(sprintf('Cache namespace for filesystem cache contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); - } - $directory .= '/'.$namespace; - } - if (isset($version[0])) { - if (preg_match('#[^-+_.A-Za-z0-9]#', $version, $match)) { - throw new InvalidArgumentException(sprintf('Cache version contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0])); - } - $directory .= '/'.$version; - } - 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) + strlen($fileSuffix) > 234) { - throw new InvalidArgumentException(sprintf('Cache directory too long (%s)', $directory)); - } - - $this->fileSuffix = $fileSuffix; - $this->directory = $dir; - } - - /** - * Returns root cache directory. - * - * @return string - */ - public function getDirectory() - { - return $this->directory; - } - - /** - * Saves entry in cache. - * - * @param string $id Id of the cache entry (used for obtaining file path to write to). - * @param string $fileContent Content to write to cache file - * @param int|null $modificationTime If this is not-null it will be passed to touch() - * - * @return bool - */ - public function saveFileForId($id, $fileContent, $modificationTime = null) - { - $file = $this->getFilePath($id, true); - - return $this->saveFile($file, $fileContent, $modificationTime); - } - - /** - * Saves entry in cache. - * - * @param string $file File path to cache entry. - * @param string $fileContent Content to write to cache file - * @param int|null $modificationTime If this is not-null it will be passed to touch() - * - * @return bool - */ - public function saveFile($file, $fileContent, $modificationTime = null) - { - $temporaryFile = $this->directory.uniqid('', true); - if (false === @file_put_contents($temporaryFile, $fileContent)) { - return false; - } - - if (null !== $modificationTime) { - @touch($temporaryFile, $modificationTime); - } - - return @rename($temporaryFile, $file); - } - - /** - * Returns file path to cache entry. - * - * @param string $id Cache entry id. - * @param bool $mkdir Whether to create necessary directories before returning file path. - * - * @return string - */ - public function getFilePath($id, $mkdir = false) - { - $hash = str_replace('/', '-', base64_encode(md5($id, true))); - $dir = $this->directory.$hash[0].DIRECTORY_SEPARATOR.$hash[1].DIRECTORY_SEPARATOR; - - if ($mkdir && !file_exists($dir)) { - @mkdir($dir, 0777, true); - } - - return $dir.substr($hash, 2, -2).$this->fileSuffix; - } -} diff --git a/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php index 2984762945..d39aa9eb39 100644 --- a/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PhpFilesAdapter.php @@ -11,25 +11,34 @@ namespace Symfony\Component\Cache\Adapter; -use Symfony\Component\Cache\Adapter\Helper\FilesCacheHelper; +use Symfony\Component\Cache\Exception\CacheException; +use Symfony\Component\Cache\Exception\InvalidArgumentException; +/** + * @author Piotr Stankowski + * @author Nicolas Grekas + */ class PhpFilesAdapter extends AbstractAdapter { - /** - * @var FilesCacheHelper - */ - protected $filesCacheHelper; + use FilesystemAdapterTrait; - /** - * @param string $namespace Cache namespace - * @param int $defaultLifetime Default lifetime for cache items - * @param null $directory Path where cache items should be stored, defaults to sys_get_temp_dir().'/symfony-cache' - * @param string $version Version (works the same way as namespace) - */ - public function __construct($namespace = '', $defaultLifetime = 0, $directory = null, $version = null) + private $includeHandler; + + public static function isSupported() { - parent::__construct($namespace, $defaultLifetime); - $this->filesCacheHelper = new FilesCacheHelper($directory, $namespace, $version, '.php'); + return function_exists('opcache_compile_file') && ini_get('opcache.enable'); + } + + public function __construct($namespace = '', $defaultLifetime = 0, $directory = null) + { + if (!static::isSupported()) { + throw new CacheException('OPcache is not enabled'); + } + parent::__construct('', $defaultLifetime); + $this->init($namespace, $directory); + + $e = new \Exception(); + $this->includeHandler = function () use ($e) { throw $e; }; } /** @@ -38,13 +47,31 @@ class PhpFilesAdapter extends AbstractAdapter protected function doFetch(array $ids) { $values = array(); + $now = time(); - foreach ($ids as $id) { - $valueArray = $this->includeCacheFile($this->filesCacheHelper->getFilePath($id)); - if (!is_array($valueArray)) { - continue; + set_error_handler($this->includeHandler); + try { + foreach ($ids as $id) { + try { + $file = $this->getFile($id); + list($expiresAt, $values[$id]) = include $file; + if ($now >= $expiresAt) { + unset($values[$id]); + } + } catch (\Exception $e) { + continue; + } + } + } finally { + restore_error_handler(); + } + + foreach ($values as $id => $value) { + if ('N;' === $value) { + $values[$id] = null; + } elseif (is_string($value) && isset($value[2]) && ':' === $value[1]) { + $values[$id] = unserialize($value); } - $values[$id] = $valueArray[0]; } return $values; @@ -55,32 +82,7 @@ class PhpFilesAdapter extends AbstractAdapter */ protected function doHave($id) { - return 0 !== count($this->doFetch(array($id))); - } - - /** - * {@inheritdoc} - */ - protected function doClear($namespace) - { - $directory = $this->filesCacheHelper->getDirectory(); - - return !(new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS))->valid(); - } - - /** - * {@inheritdoc} - */ - protected function doDelete(array $ids) - { - foreach ($ids as $id) { - $file = $this->filesCacheHelper->getFilePath($id); - if (@file_exists($file)) { - return false; - } - } - - return true; + return (bool) $this->doFetch(array($id)); } /** @@ -89,69 +91,33 @@ class PhpFilesAdapter extends AbstractAdapter protected function doSave(array $values, $lifetime) { $ok = true; - $expiresAt = $lifetime ? time() + $lifetime : PHP_INT_MAX; + $data = array($lifetime ? time() + $lifetime : PHP_INT_MAX, ''); foreach ($values as $id => $value) { - $file = $this->filesCacheHelper->getFilePath($id, true); - if (file_exists($file)) { - $ok = false; - } else { - $ok = $this->saveCacheFile($file, $value, $expiresAt) && $ok; + if (null === $value || is_object($value)) { + $value = serialize($value); + } elseif (is_array($value)) { + $serialized = serialize($value); + $unserialized = unserialize($serialized); + // Store arrays serialized if they contain any objects or references + if ($unserialized !== $value || (false !== strpos($serialized, ';R:') && preg_match('/;R:[1-9]/', $serialized))) { + $value = $serialized; + } + } elseif (is_string($value)) { + // Serialize strings if they could be confused with serialized objects or arrays + if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) { + $value = serialize($value); + } + } elseif (!is_scalar($value)) { + throw new InvalidArgumentException(sprintf('Value of type "%s" is not serializable', $key, gettype($value))); } + + $data[1] = $value; + $file = $this->getFile($id, true); + $ok = $this->write($file, 'createCacheFileContent($value, $expiresAt); - - return $this->filesCacheHelper->saveFile($file, $fileContent); - } - - /** - * @param string $file File path - * - * @return array|null unserialized value wrapped in array or null - */ - private function includeCacheFile($file) - { - $valueArray = @include $file; - if (!is_array($valueArray) || 2 !== count($valueArray)) { - return; - } - - list($serializedValue, $expiresAt) = $valueArray; - if (time() > (int) $expiresAt) { - return; - } - - $unserializedValueInArray = unserialize($serializedValue); - if (!is_array($unserializedValueInArray)) { - return; - } - - return $unserializedValueInArray; - } - - /** - * @param mixed $value - * @param int $expiresAt - * - * @return string - */ - private function createCacheFileContent($value, $expiresAt) - { - $exportedValue = var_export(array(serialize([$value]), $expiresAt), true); - - return ' - * - * 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 Psr\Cache\CacheItemInterface; -use Psr\Cache\CacheItemPoolInterface; - -abstract class AbstractAppendOnlyAdapterTest extends CachePoolTest -{ - /** - * @var mixed - */ - private $cacheVersion; - - /** - * @var CacheItemPoolInterface - */ - private $cache; - - protected function setUp() - { - parent::setUp(); - $this->cacheVersion = $this->createRandomCachePoolVersion(); - $this->cache = $this->createVersionedCachePool($this->cacheVersion); - } - - public function createCachePool() - { - $cacheVersion = $this->createRandomCachePoolVersion(); - - return $this->createVersionedCachePool($cacheVersion); - } - - /** - * @return mixed cache version that will be used by this adapter - */ - abstract public function createRandomCachePoolVersion(); - - /** - * @return CacheItemPoolInterface that is used in the tests that need to recreate the same cache pool - */ - abstract public function createVersionedCachePool($cacheVersion); - - public function testBasicUsage() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - - return; - } - - $item = $this->cache->getItem('key'); - $item->set('4711'); - $this->cache->save($item); - - $item = $this->cache->getItem('key2'); - $item->set('4712'); - $this->cache->save($item); - - $fooItem = $this->cache->getItem('key'); - $this->assertTrue($fooItem->isHit()); - $this->assertEquals('4711', $fooItem->get()); - - $barItem = $this->cache->getItem('key2'); - $this->assertTrue($barItem->isHit()); - $this->assertEquals('4712', $barItem->get()); - - // Removing must always return false - $this->assertFalse($this->cache->deleteItem('key')); - $this->assertTrue($this->cache->getItem('key')->isHit()); - $this->assertTrue($this->cache->getItem('key2')->isHit()); - - // Remove everything - $this->assertFalse($this->cache->clear()); - $this->assertTrue($this->cache->getItem('key')->isHit()); - $this->assertTrue($this->cache->getItem('key2')->isHit()); - } - - public function testClear() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - - return; - } - - $return = $this->cache->clear(); - $this->assertTrue($return, 'clear() should return true when no items are in a cache'); - - $item = $this->cache->getItem('key'); - $item->set('value'); - $this->cache->save($item); - - $return = $this->cache->clear(); - - $this->assertFalse($return, 'clear() must return false for append-only cache when not empty.'); - $this->assertTrue($this->cache->getItem('key')->isHit(), 'Item should still be in an append-only cache, even after clear.'); - } - - public function testDeleteItem() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - - return; - } - - $item = $this->cache->getItem('key'); - $item->set('value'); - $this->cache->save($item); - - $this->assertFalse($this->cache->deleteItem('key')); - $this->assertTrue($this->cache->getItem('key')->isHit(), 'A deleted item should still be a hit in an append-only cache.'); - $this->assertTrue($this->cache->hasItem('key'), 'A deleted item should still be a hit in an append-only cache.'); - - $this->assertTrue($this->cache->deleteItem('key2'), 'Deleting an item that does not exist should return true.'); - } - - public function testDeleteItems() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - - return; - } - - $items = $this->cache->getItems(['foo', 'bar', 'baz']); - - /** @var CacheItemInterface $item */ - foreach ($items as $idx => $item) { - $item->set($idx); - $this->cache->save($item); - } - - // All should be a hit but 'biz' - $this->assertTrue($this->cache->getItem('foo')->isHit()); - $this->assertTrue($this->cache->getItem('bar')->isHit()); - $this->assertTrue($this->cache->getItem('baz')->isHit()); - $this->assertFalse($this->cache->getItem('biz')->isHit()); - - $return = $this->cache->deleteItems(['foo', 'bar', 'biz']); - $this->assertFalse($return, 'Deleting should return false in append-only cache'); - - $this->assertTrue($this->cache->getItem('foo')->isHit(), 'Deleting shouldn\'t work for append-only cache'); - $this->assertTrue($this->cache->getItem('bar')->isHit(), 'Deleting shouldn\'t work for append-only cache'); - $this->assertTrue($this->cache->getItem('baz')->isHit(), 'Deleting shouldn\'t work for append-only cache'); - $this->assertFalse($this->cache->getItem('biz')->isHit()); - } - - public function testSaveExpired() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - - return; - } - - $item = $this->cache->getItem('key'); - $item->set('value'); - $item->expiresAt(\DateTime::createFromFormat('U', time() - 1)); - $this->cache->save($item); - $item = $this->cache->getItem('key'); - $this->assertFalse($item->isHit(), 'Cache should not return expired items'); - } - - public function testSaveWithoutExpire() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - - return; - } - - $item = $this->cache->getItem('test_ttl_null'); - $item->set('data'); - $this->cache->save($item); - - // Use a new pool instance to ensure that we don't it any caches - $pool = $this->createVersionedCachePool($this->cacheVersion); - $item = $pool->getItem('test_ttl_null'); - - $this->assertTrue($item->isHit(), 'Cache should have retrieved the items'); - $this->assertEquals('data', $item->get()); - } - - public function testDeleteDeferredItem() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - - return; - } - - $item = $this->cache->getItem('key'); - $item->set('4711'); - $this->cache->saveDeferred($item); - - $this->cache->deleteItem('key'); - $this->assertFalse($this->cache->hasItem('key'), 'You must be able to delete a deferred item before committed. '); - $this->assertFalse($this->cache->getItem('key')->isHit(), 'You must be able to delete a deferred item before committed. '); - - $this->cache->commit(); - $this->assertFalse($this->cache->hasItem('key'), 'A deleted item should not reappear after commit. '); - $this->assertFalse($this->cache->getItem('key')->isHit(), 'A deleted item should not reappear after commit. '); - } - - public function testDeferredSaveWithoutCommit() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - - return; - } - - $this->prepareDeferredSaveWithoutCommit(); - gc_collect_cycles(); - - $cache = $this->createVersionedCachePool($this->cacheVersion); - $this->assertTrue($cache->getItem('key')->isHit(), 'A deferred item should automatically be committed on CachePool::__destruct().'); - } - - public function testSaveDeferredOverwrite() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - - return; - } - - $item = $this->cache->getItem('key'); - $item->set('value'); - $this->cache->saveDeferred($item); - $item->set('new value'); - $this->cache->saveDeferred($item); - - $this->cache->commit(); - $item = $this->cache->getItem('key'); - $this->assertEquals('new value', $item->get()); - } - - private function prepareDeferredSaveWithoutCommit() - { - $cache = $this->cache; - $this->cache = null; - - $item = $cache->getItem('key'); - $item->set('4711'); - $cache->saveDeferred($item); - } -} diff --git a/src/Symfony/Component/Cache/Tests/Adapter/FilesystemAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/FilesystemAdapterTest.php index 66da9c9078..418cbf9ebc 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/FilesystemAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/FilesystemAdapterTest.php @@ -19,6 +19,15 @@ use Symfony\Component\Cache\Adapter\FilesystemAdapter; */ class FilesystemAdapterTest extends CachePoolTest { + public function createCachePool() + { + if (defined('HHVM_VERSION')) { + $this->skippedTests['testDeferredSaveWithoutCommit'] = 'Fails on HHVM'; + } + + return new FilesystemAdapter('sf-cache'); + } + public static function tearDownAfterClass() { self::rmdir(sys_get_temp_dir().'/symfony-cache'); @@ -45,13 +54,4 @@ class FilesystemAdapterTest extends CachePoolTest } rmdir($dir); } - - public function createCachePool() - { - if (defined('HHVM_VERSION')) { - $this->skippedTests['testDeferredSaveWithoutCommit'] = 'Fails on HHVM'; - } - - return new FilesystemAdapter('sf-cache'); - } } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PhpFilesAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PhpFilesAdapterTest.php index eaeba8a5e6..e3d6024f34 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PhpFilesAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PhpFilesAdapterTest.php @@ -11,60 +11,28 @@ namespace Symfony\Component\Cache\Tests\Adapter; -use Psr\Cache\CacheItemPoolInterface; +use Cache\IntegrationTests\CachePoolTest; use Symfony\Component\Cache\Adapter\PhpFilesAdapter; /** * @group time-sensitive */ -class PhpFilesAdapterTest extends AbstractAppendOnlyAdapterTest +class PhpFilesAdapterTest extends CachePoolTest { - public static function tearDownAfterClass() - { - self::rmdir(sys_get_temp_dir().'/symfony-cache'); - } - - public static function rmdir($dir) - { - if (!file_exists($dir)) { - return; - } - if (!$dir || 0 !== strpos(dirname($dir), sys_get_temp_dir())) { - throw new \Exception(__METHOD__."() operates only on subdirs of system's temp dir"); - } - $children = new \RecursiveIteratorIterator( - new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS), - \RecursiveIteratorIterator::CHILD_FIRST - ); - foreach ($children as $child) { - if ($child->isDir()) { - rmdir($child); - } else { - unlink($child); - } - } - rmdir($dir); - } - - /** - * @param string $cacheVersion - * - * @return CacheItemPoolInterface that is used in the tests that need to recreate the same cache pool - */ - public function createVersionedCachePool($cacheVersion) + public function createCachePool() { if (defined('HHVM_VERSION')) { $this->skippedTests['testDeferredSaveWithoutCommit'] = 'Fails on HHVM'; } + if (!PhpFilesAdapter::isSupported()) { + $this->markTestSkipped('OPcache extension is not enabled.'); + } - return new PhpFilesAdapter('sf-cache', 0, null, $cacheVersion); + return new PhpFilesAdapter('sf-cache'); } - /** - * @return mixed cache version that will be used by this adapter - */ - public function createRandomCachePoolVersion() + public static function tearDownAfterClass() { - return substr(str_replace('/', '-', base64_encode(md5(uniqid(mt_rand(), true), true))), 0, -2); + FilesystemAdapterTest::rmdir(sys_get_temp_dir().'/symfony-cache'); } }