[Cache] Add argument $prefix to AdapterInterface::clear()

This commit is contained in:
Nicolas Grekas 2019-06-25 17:29:23 +02:00
parent 835f6b0373
commit ad6f6cf900
21 changed files with 153 additions and 16 deletions

View File

@ -1,6 +1,11 @@
UPGRADE FROM 4.3 to 4.4
=======================
Cache
-----
* Added argument `$prefix` to `AdapterInterface::clear()`
DependencyInjection
-------------------

View File

@ -16,6 +16,7 @@ Cache
* Removed `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead.
* Removed all PSR-16 adapters, use `Psr16Cache` or `Symfony\Contracts\Cache\CacheInterface` implementations instead.
* Removed `SimpleCacheAdapter`, use `Psr16Adapter` instead.
* Added argument `$prefix` to `AdapterInterface::clear()`
Config
------

View File

@ -34,4 +34,11 @@ interface AdapterInterface extends CacheItemPoolInterface
* @return \Traversable|CacheItem[]
*/
public function getItems(array $keys = []);
/**
* {@inheritdoc}
*
* @param string $prefix
*/
public function clear(/*string $prefix = ''*/);
}

View File

@ -192,14 +192,21 @@ class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
/**
* {@inheritdoc}
*
* @param string $prefix
*/
public function clear()
public function clear(/*string $prefix = ''*/)
{
$prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
$cleared = true;
$i = $this->adapterCount;
while ($i--) {
$cleared = $this->adapters[$i]->clear() && $cleared;
if ($this->adapters[$i] instanceof AdapterInterface) {
$cleared = $this->adapters[$i]->clear($prefix) && $cleared;
} else {
$cleared = $this->adapters[$i]->clear() && $cleared;
}
}
return $cleared;

View File

@ -75,8 +75,10 @@ class NullAdapter implements AdapterInterface, CacheInterface
/**
* {@inheritdoc}
*
* @param string $prefix
*/
public function clear()
public function clear(/*string $prefix = ''*/)
{
return true;
}

View File

@ -147,9 +147,17 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
/**
* {@inheritdoc}
*
* @param string $prefix
*/
public function clear()
public function clear(/*string $prefix = ''*/)
{
$prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
if ($this->pool instanceof AdapterInterface) {
return $this->pool->clear($this->namespace.$prefix);
}
return $this->pool->clear();
}

View File

@ -213,10 +213,26 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac
/**
* {@inheritdoc}
*
* @param string $prefix
*/
public function clear()
public function clear(/*string $prefix = ''*/)
{
$this->deferred = [];
$prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
if ('' !== $prefix) {
foreach ($this->deferred as $key => $item) {
if (0 === strpos($key, $prefix)) {
unset($this->deferred[$key]);
}
}
} else {
$this->deferred = [];
}
if ($this->pool instanceof AdapterInterface) {
return $this->pool->clear($prefix);
}
return $this->pool->clear();
}

View File

@ -167,11 +167,18 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt
/**
* {@inheritdoc}
*
* @param string $prefix
*/
public function clear()
public function clear(/*string $prefix = ''*/)
{
$prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
$event = $this->start(__FUNCTION__);
try {
if ($this->pool instanceof AdapterInterface) {
return $event->result = $this->pool->clear($prefix);
}
return $event->result = $this->pool->clear();
} finally {
$event->end = microtime(true);

View File

@ -5,6 +5,7 @@ CHANGELOG
-----
* added support for connecting to Redis Sentinel clusters
* added argument `$prefix` to `AdapterInterface::clear()`
4.3.0
-----

View File

@ -252,6 +252,26 @@ abstract class AdapterTestCase extends CachePoolTest
$this->assertFalse($this->isPruned($cache, 'foo'));
$this->assertTrue($this->isPruned($cache, 'qux'));
}
public function testClearPrefix()
{
if (isset($this->skippedTests[__FUNCTION__])) {
$this->markTestSkipped($this->skippedTests[__FUNCTION__]);
}
$cache = $this->createCachePool(0, __FUNCTION__);
$cache->clear();
$item = $cache->getItem('foobar');
$cache->save($item->set(1));
$item = $cache->getItem('barfoo');
$cache->save($item->set(2));
$cache->clear('foo');
$this->assertFalse($cache->hasItem('foobar'));
$this->assertTrue($cache->hasItem('barfoo'));
}
}
class NotUnserializable

View File

@ -23,6 +23,7 @@ class DoctrineAdapterTest extends AdapterTestCase
'testDeferredSaveWithoutCommit' => 'Assumes a shared cache which ArrayCache is not.',
'testSaveWithoutExpire' => 'Assumes a shared cache which ArrayCache is not.',
'testNotUnserializable' => 'ArrayCache does not use serialize/unserialize',
'testClearPrefix' => 'Doctrine cannot clear by prefix',
];
public function createCachePool($defaultLifetime = 0)

View File

@ -19,6 +19,7 @@ class MemcachedAdapterTest extends AdapterTestCase
protected $skippedTests = [
'testHasItemReturnsFalseWhenDeferredItemIsExpired' => 'Testing expiration slows down the test suite',
'testDefaultLifeTime' => 'Testing expiration slows down the test suite',
'testClearPrefix' => 'Memcached cannot clear by prefix',
];
protected static $client;

View File

@ -72,7 +72,7 @@ class PhpArrayAdapterTest extends AdapterTestCase
public function createCachePool($defaultLifetime = 0, $testMethod = null)
{
if ('testGetMetadata' === $testMethod) {
if ('testGetMetadata' === $testMethod || 'testClearPrefix' === $testMethod) {
return new PhpArrayAdapter(self::$file, new FilesystemAdapter());
}

View File

@ -23,6 +23,7 @@ class Psr16AdapterTest extends AdapterTestCase
{
protected $skippedTests = [
'testPrune' => 'Psr16adapter just proxies',
'testClearPrefix' => 'SimpleCache cannot clear by prefix',
];
public function createCachePool($defaultLifetime = 0)

View File

@ -23,6 +23,7 @@ class SimpleCacheAdapterTest extends AdapterTestCase
{
protected $skippedTests = [
'testPrune' => 'SimpleCache just proxies',
'testClearPrefix' => 'SimpleCache cannot clear by prefix',
];
public function createCachePool($defaultLifetime = 0)

View File

@ -102,9 +102,12 @@ trait AbstractTrait
/**
* {@inheritdoc}
*
* @param string $prefix
*/
public function clear()
public function clear(/*string $prefix = ''*/)
{
$prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
$this->deferred = [];
if ($cleared = $this->versioningIsEnabled) {
$namespaceVersion = substr_replace(base64_encode(pack('V', mt_rand())), static::NS_SEPARATOR, 5);
@ -120,7 +123,7 @@ trait AbstractTrait
}
try {
return $this->doClear($this->namespace) || $cleared;
return $this->doClear($this->namespace.$prefix) || $cleared;
} catch (\Exception $e) {
CacheItem::log($this->logger, 'Failed to clear the cache: '.$e->getMessage(), ['exception' => $e]);

View File

@ -66,10 +66,22 @@ trait ArrayTrait
/**
* {@inheritdoc}
*
* @param string $prefix
*/
public function clear()
public function clear(/*string $prefix = ''*/)
{
$this->values = $this->expiries = [];
$prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
if ('' !== $prefix) {
foreach ($this->values as $key => $value) {
if (0 === strpos($key, $prefix)) {
unset($this->values[$key], $this->expiries[$key]);
}
}
} else {
$this->values = $this->expiries = [];
}
return true;
}

View File

@ -56,6 +56,10 @@ trait FilesystemCommonTrait
$ok = true;
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->directory, \FilesystemIterator::SKIP_DOTS)) as $file) {
if ('' !== $namespace && 0 !== strpos($this->getFileKey($file), $namespace)) {
continue;
}
$ok = ($file->isDir() || $this->doUnlink($file) || !file_exists($file)) && $ok;
}
@ -114,6 +118,11 @@ trait FilesystemCommonTrait
return $dir.substr($hash, 2, 20);
}
private function getFileKey(string $file): string
{
return '';
}
/**
* @internal
*/

View File

@ -108,4 +108,17 @@ trait FilesystemTrait
return $failed;
}
private function getFileKey(string $file): string
{
if (!$h = @fopen($file, 'rb')) {
return '';
}
fgets($h); // expiry
$encodedKey = fgets($h);
fclose($h);
return rawurldecode(rtrim($encodedKey));
}
}

View File

@ -11,6 +11,7 @@
namespace Symfony\Component\Cache\Traits;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\VarExporter\VarExporter;
@ -121,13 +122,20 @@ EOF;
/**
* {@inheritdoc}
*
* @param string $prefix
*/
public function clear()
public function clear(/*string $prefix = ''*/)
{
$prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
$this->keys = $this->values = [];
$cleared = @unlink($this->file) || !file_exists($this->file);
if ($this->pool instanceof AdapterInterface) {
return $this->pool->clear($prefix) && $cleared;
}
return $this->pool->clear() && $cleared;
}

View File

@ -211,17 +211,19 @@ trait PhpFilesTrait
$value = var_export($value, true);
}
$encodedKey = rawurlencode($key);
if (!$isStaticValue) {
// We cannot use a closure here because of https://bugs.php.net/76982
$value = str_replace('\Symfony\Component\VarExporter\Internal\\', '', $value);
$value = "<?php\n\nnamespace Symfony\Component\VarExporter\Internal;\n\nreturn \$getExpiry ? {$expiry} : {$value};\n";
$value = "namespace Symfony\Component\VarExporter\Internal;\n\nreturn \$getExpiry ? {$expiry} : {$value};";
} else {
$value = "<?php return [{$expiry}, {$value}];\n";
$value = "return [{$expiry}, {$value}];";
}
$file = $this->files[$key] = $this->getFile($key, true);
// Since OPcache only compiles files older than the script execution start, set the file's mtime in the past
$ok = $this->write($file, $value, self::$startTime - 10) && $ok;
$ok = $this->write($file, "<?php //{$encodedKey}\n\n{$value}\n", self::$startTime - 10) && $ok;
if ($allowCompile) {
@opcache_invalidate($file, true);
@ -266,6 +268,18 @@ trait PhpFilesTrait
return @unlink($file);
}
private function getFileKey(string $file): string
{
if (!$h = @fopen($file, 'rb')) {
return '';
}
$encodedKey = substr(fgets($h), 8);
fclose($h);
return rawurldecode(rtrim($encodedKey));
}
}
/**