[Cache] Optimize & wire PhpFilesAdapter

This commit is contained in:
Nicolas Grekas 2016-05-25 09:44:10 +02:00
parent 14bcd799c7
commit 8983e83d91
10 changed files with 215 additions and 609 deletions

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -11,27 +11,17 @@
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\Adapter\Helper\FilesCacheHelper;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
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);
}
}

View File

@ -0,0 +1,110 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <p@tchwork.com>
*/
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);
}
}

View File

@ -1,137 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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;
}
}

View File

@ -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 <git@trakos.pl>
* @author Nicolas Grekas <p@tchwork.com>
*/
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, '<?php return '.var_export($data, true).';') && $ok;
@opcache_compile_file($file);
}
return $ok;
}
/**
* @param string $file
* @param mixed $value
* @param int $expiresAt
*
* @return bool
*/
private function saveCacheFile($file, $value, $expiresAt)
{
$fileContent = $this->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 '<?php return '.$exportedValue.';';
}
}

View File

@ -1,260 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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);
}
}

View File

@ -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');
}
}

View File

@ -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');
}
}