[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 UPGRADE FROM 4.3 to 4.4
======================= =======================
Cache
-----
* Added argument `$prefix` to `AdapterInterface::clear()`
DependencyInjection DependencyInjection
------------------- -------------------

View File

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

View File

@ -34,4 +34,11 @@ interface AdapterInterface extends CacheItemPoolInterface
* @return \Traversable|CacheItem[] * @return \Traversable|CacheItem[]
*/ */
public function getItems(array $keys = []); 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} * {@inheritdoc}
*
* @param string $prefix
*/ */
public function clear() public function clear(/*string $prefix = ''*/)
{ {
$prefix = 0 < \func_num_args() ? (string) func_get_arg(0) : '';
$cleared = true; $cleared = true;
$i = $this->adapterCount; $i = $this->adapterCount;
while ($i--) { 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; return $cleared;

View File

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

View File

@ -147,9 +147,17 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
/** /**
* {@inheritdoc} * {@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(); return $this->pool->clear();
} }

View File

@ -213,10 +213,26 @@ class TagAwareAdapter implements TagAwareAdapterInterface, TagAwareCacheInterfac
/** /**
* {@inheritdoc} * {@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(); return $this->pool->clear();
} }

View File

@ -167,11 +167,18 @@ class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInt
/** /**
* {@inheritdoc} * {@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__); $event = $this->start(__FUNCTION__);
try { try {
if ($this->pool instanceof AdapterInterface) {
return $event->result = $this->pool->clear($prefix);
}
return $event->result = $this->pool->clear(); return $event->result = $this->pool->clear();
} finally { } finally {
$event->end = microtime(true); $event->end = microtime(true);

View File

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

View File

@ -252,6 +252,26 @@ abstract class AdapterTestCase extends CachePoolTest
$this->assertFalse($this->isPruned($cache, 'foo')); $this->assertFalse($this->isPruned($cache, 'foo'));
$this->assertTrue($this->isPruned($cache, 'qux')); $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 class NotUnserializable

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -66,10 +66,22 @@ trait ArrayTrait
/** /**
* {@inheritdoc} * {@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; return true;
} }

View File

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

View File

@ -108,4 +108,17 @@ trait FilesystemTrait
return $failed; 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; namespace Symfony\Component\Cache\Traits;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\VarExporter\VarExporter; use Symfony\Component\VarExporter\VarExporter;
@ -121,13 +122,20 @@ EOF;
/** /**
* {@inheritdoc} * {@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 = []; $this->keys = $this->values = [];
$cleared = @unlink($this->file) || !file_exists($this->file); $cleared = @unlink($this->file) || !file_exists($this->file);
if ($this->pool instanceof AdapterInterface) {
return $this->pool->clear($prefix) && $cleared;
}
return $this->pool->clear() && $cleared; return $this->pool->clear() && $cleared;
} }

View File

@ -211,17 +211,19 @@ trait PhpFilesTrait
$value = var_export($value, true); $value = var_export($value, true);
} }
$encodedKey = rawurlencode($key);
if (!$isStaticValue) { if (!$isStaticValue) {
// We cannot use a closure here because of https://bugs.php.net/76982 // We cannot use a closure here because of https://bugs.php.net/76982
$value = str_replace('\Symfony\Component\VarExporter\Internal\\', '', $value); $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 { } else {
$value = "<?php return [{$expiry}, {$value}];\n"; $value = "return [{$expiry}, {$value}];";
} }
$file = $this->files[$key] = $this->getFile($key, true); $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 // 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) { if ($allowCompile) {
@opcache_invalidate($file, true); @opcache_invalidate($file, true);
@ -266,6 +268,18 @@ trait PhpFilesTrait
return @unlink($file); 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));
}
} }
/** /**