From b20a2376b217d2fdcd8db4329833de96aef57579 Mon Sep 17 00:00:00 2001 From: Rob Frawley 2nd Date: Thu, 20 Jul 2017 13:13:39 -0400 Subject: [PATCH] add (pdo|chain) cache (adapter|simple) prune method --- .../Component/Cache/Adapter/ChainAdapter.php | 19 ++++- .../Component/Cache/Adapter/PdoAdapter.php | 3 +- src/Symfony/Component/Cache/CHANGELOG.md | 6 +- .../Component/Cache/PruneableInterface.php | 2 +- .../Component/Cache/Simple/ChainCache.php | 19 ++++- .../Component/Cache/Simple/PdoCache.php | 3 +- .../Cache/Tests/Adapter/AdapterTestCase.php | 14 ++++ .../Cache/Tests/Adapter/ChainAdapterTest.php | 71 ++++++++++++++++++ .../Cache/Tests/Adapter/PdoAdapterTest.php | 3 + .../Tests/Adapter/PdoDbalAdapterTest.php | 3 + .../Cache/Tests/Simple/CacheTestCase.php | 14 ++++ .../Cache/Tests/Simple/ChainCacheTest.php | 73 ++++++++++++++++++- .../Cache/Tests/Simple/PdoCacheTest.php | 3 + .../Cache/Tests/Simple/PdoDbalCacheTest.php | 3 + .../Cache/Tests/Traits/PdoPruneableTrait.php | 34 +++++++++ .../Component/Cache/Traits/PdoTrait.php | 23 ++++++ 16 files changed, 284 insertions(+), 9 deletions(-) create mode 100644 src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php diff --git a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php index 7512472150631..c38949975d884 100644 --- a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php @@ -15,6 +15,7 @@ use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\PruneableInterface; /** * Chains several adapters together. @@ -24,7 +25,7 @@ * * @author Kévin Dunglas */ -class ChainAdapter implements AdapterInterface +class ChainAdapter implements AdapterInterface, PruneableInterface { private $adapters = array(); private $adapterCount; @@ -231,4 +232,20 @@ public function commit() return $committed; } + + /** + * {@inheritdoc} + */ + public function prune() + { + $pruned = true; + + foreach ($this->adapters as $adapter) { + if ($adapter instanceof PruneableInterface) { + $pruned = $adapter->prune() && $pruned; + } + } + + return $pruned; + } } diff --git a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php index 832185629b053..9db17805628de 100644 --- a/src/Symfony/Component/Cache/Adapter/PdoAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PdoAdapter.php @@ -11,9 +11,10 @@ namespace Symfony\Component\Cache\Adapter; +use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\Traits\PdoTrait; -class PdoAdapter extends AbstractAdapter +class PdoAdapter extends AbstractAdapter implements PruneableInterface { use PdoTrait; diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index e8172d94988d4..bd637f10df0b0 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -5,9 +5,9 @@ CHANGELOG ----- * added PruneableInterface so PSR-6 or PSR-16 cache implementations can declare support for manual stale cache pruning - * added FilesystemTrait::prune() and PhpFilesTrait::prune() implementations - * now FilesystemAdapter, PhpFilesAdapter, FilesystemCache, and PhpFilesCache implement PruneableInterface and support - manual stale cache pruning + * added prune logic to FilesystemTrait, PhpFilesTrait, PdoTrait, and ChainTrait + * now FilesystemAdapter, PhpFilesAdapter, FilesystemCache, PhpFilesCache, PdoAdapter, PdoCache, ChainAdapter, and + ChainCache implement PruneableInterface and support manual stale cache pruning 3.3.0 ----- diff --git a/src/Symfony/Component/Cache/PruneableInterface.php b/src/Symfony/Component/Cache/PruneableInterface.php index cd366adb55290..42615253689fe 100644 --- a/src/Symfony/Component/Cache/PruneableInterface.php +++ b/src/Symfony/Component/Cache/PruneableInterface.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Cache; /** - * Interface for adapters and simple cache implementations that allow pruning expired items. + * Interface extends psr-6 and psr-16 caches to allow for pruning (deletion) of all expired cache items. */ interface PruneableInterface { diff --git a/src/Symfony/Component/Cache/Simple/ChainCache.php b/src/Symfony/Component/Cache/Simple/ChainCache.php index 08bb4881b463f..8bb944fd4773f 100644 --- a/src/Symfony/Component/Cache/Simple/ChainCache.php +++ b/src/Symfony/Component/Cache/Simple/ChainCache.php @@ -13,6 +13,7 @@ use Psr\SimpleCache\CacheInterface; use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\PruneableInterface; /** * Chains several caches together. @@ -22,7 +23,7 @@ * * @author Nicolas Grekas */ -class ChainCache implements CacheInterface +class ChainCache implements CacheInterface, PruneableInterface { private $miss; private $caches = array(); @@ -219,4 +220,20 @@ public function setMultiple($values, $ttl = null) return $saved; } + + /** + * {@inheritdoc} + */ + public function prune() + { + $pruned = true; + + foreach ($this->caches as $cache) { + if ($cache instanceof PruneableInterface) { + $pruned = $cache->prune() && $pruned; + } + } + + return $pruned; + } } diff --git a/src/Symfony/Component/Cache/Simple/PdoCache.php b/src/Symfony/Component/Cache/Simple/PdoCache.php index 3e698e2f952c8..41730cec57f47 100644 --- a/src/Symfony/Component/Cache/Simple/PdoCache.php +++ b/src/Symfony/Component/Cache/Simple/PdoCache.php @@ -11,9 +11,10 @@ namespace Symfony\Component\Cache\Simple; +use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\Traits\PdoTrait; -class PdoCache extends AbstractCache +class PdoCache extends AbstractCache implements PruneableInterface { use PdoTrait; diff --git a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php index 6ba02b0d158ff..27318a487fc05 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Cache\Tests\Adapter; use Cache\IntegrationTests\CachePoolTest; +use Psr\Cache\CacheItemPoolInterface; use Symfony\Component\Cache\PruneableInterface; abstract class AdapterTestCase extends CachePoolTest @@ -83,6 +84,7 @@ public function testPrune() $this->fail('Test classes for pruneable caches must implement `isPruned($cache, $name)` method.'); } + /** @var PruneableInterface|CacheItemPoolInterface $cache */ $cache = $this->createCachePool(); $doSet = function ($name, $value, \DateInterval $expiresAfter = null) use ($cache) { @@ -96,6 +98,18 @@ public function testPrune() $cache->save($item); }; + $doSet('foo', 'foo-val', new \DateInterval('PT05S')); + $doSet('bar', 'bar-val', new \DateInterval('PT10S')); + $doSet('baz', 'baz-val', new \DateInterval('PT15S')); + $doSet('qux', 'qux-val', new \DateInterval('PT20S')); + + sleep(30); + $cache->prune(); + $this->assertTrue($this->isPruned($cache, 'foo')); + $this->assertTrue($this->isPruned($cache, 'bar')); + $this->assertTrue($this->isPruned($cache, 'baz')); + $this->assertTrue($this->isPruned($cache, 'qux')); + $doSet('foo', 'foo-val'); $doSet('bar', 'bar-val', new \DateInterval('PT20S')); $doSet('baz', 'baz-val', new \DateInterval('PT40S')); diff --git a/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php index b80913c6e089c..293a90cc86783 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php @@ -11,9 +11,11 @@ namespace Symfony\Component\Cache\Tests\Adapter; +use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Cache\Adapter\ChainAdapter; +use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\Tests\Fixtures\ExternalAdapter; /** @@ -44,4 +46,73 @@ public function testInvalidAdapterException() { new ChainAdapter(array(new \stdClass())); } + + public function testPrune() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $cache = new ChainAdapter(array( + $this->getPruneableMock(), + $this->getNonPruneableMock(), + $this->getPruneableMock(), + )); + $this->assertTrue($cache->prune()); + + $cache = new ChainAdapter(array( + $this->getPruneableMock(), + $this->getFailingPruneableMock(), + $this->getPruneableMock(), + )); + $this->assertFalse($cache->prune()); + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject|PruneableCacheInterface + */ + private function getPruneableMock() + { + $pruneable = $this + ->getMockBuilder(PruneableCacheInterface::class) + ->getMock(); + + $pruneable + ->expects($this->atLeastOnce()) + ->method('prune') + ->will($this->returnValue(true)); + + return $pruneable; + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject|PruneableCacheInterface + */ + private function getFailingPruneableMock() + { + $pruneable = $this + ->getMockBuilder(PruneableCacheInterface::class) + ->getMock(); + + $pruneable + ->expects($this->atLeastOnce()) + ->method('prune') + ->will($this->returnValue(false)); + + return $pruneable; + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject|AdapterInterface + */ + private function getNonPruneableMock() + { + return $this + ->getMockBuilder(AdapterInterface::class) + ->getMock(); + } +} + +interface PruneableCacheInterface extends PruneableInterface, AdapterInterface +{ } diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php index 90c9ece8bcc9d..24e3f9bbc9582 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php @@ -12,12 +12,15 @@ namespace Symfony\Component\Cache\Tests\Adapter; use Symfony\Component\Cache\Adapter\PdoAdapter; +use Symfony\Component\Cache\Tests\Traits\PdoPruneableTrait; /** * @group time-sensitive */ class PdoAdapterTest extends AdapterTestCase { + use PdoPruneableTrait; + protected static $dbFile; public static function setupBeforeClass() diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PdoDbalAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PdoDbalAdapterTest.php index b9c396fdc59eb..1e8c6155bdd35 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PdoDbalAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PdoDbalAdapterTest.php @@ -13,12 +13,15 @@ use Doctrine\DBAL\DriverManager; use Symfony\Component\Cache\Adapter\PdoAdapter; +use Symfony\Component\Cache\Tests\Traits\PdoPruneableTrait; /** * @group time-sensitive */ class PdoDbalAdapterTest extends AdapterTestCase { + use PdoPruneableTrait; + protected static $dbFile; public static function setupBeforeClass() diff --git a/src/Symfony/Component/Cache/Tests/Simple/CacheTestCase.php b/src/Symfony/Component/Cache/Tests/Simple/CacheTestCase.php index 600cd338be540..48b972824c2c2 100644 --- a/src/Symfony/Component/Cache/Tests/Simple/CacheTestCase.php +++ b/src/Symfony/Component/Cache/Tests/Simple/CacheTestCase.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Cache\Tests\Simple; use Cache\IntegrationTests\SimpleCacheTest; +use Psr\SimpleCache\CacheInterface; use Symfony\Component\Cache\PruneableInterface; abstract class CacheTestCase extends SimpleCacheTest @@ -80,8 +81,21 @@ public function testPrune() $this->fail('Test classes for pruneable caches must implement `isPruned($cache, $name)` method.'); } + /** @var PruneableInterface|CacheInterface $cache */ $cache = $this->createSimpleCache(); + $cache->set('foo', 'foo-val', new \DateInterval('PT05S')); + $cache->set('bar', 'bar-val', new \DateInterval('PT10S')); + $cache->set('baz', 'baz-val', new \DateInterval('PT15S')); + $cache->set('qux', 'qux-val', new \DateInterval('PT20S')); + + sleep(30); + $cache->prune(); + $this->assertTrue($this->isPruned($cache, 'foo')); + $this->assertTrue($this->isPruned($cache, 'bar')); + $this->assertTrue($this->isPruned($cache, 'baz')); + $this->assertTrue($this->isPruned($cache, 'qux')); + $cache->set('foo', 'foo-val'); $cache->set('bar', 'bar-val', new \DateInterval('PT20S')); $cache->set('baz', 'baz-val', new \DateInterval('PT40S')); diff --git a/src/Symfony/Component/Cache/Tests/Simple/ChainCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/ChainCacheTest.php index 282bb62a6530e..ab28e3bce7b9b 100644 --- a/src/Symfony/Component/Cache/Tests/Simple/ChainCacheTest.php +++ b/src/Symfony/Component/Cache/Tests/Simple/ChainCacheTest.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Cache\Tests\Simple; +use Psr\SimpleCache\CacheInterface; +use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\Simple\ArrayCache; use Symfony\Component\Cache\Simple\ChainCache; use Symfony\Component\Cache\Simple\FilesystemCache; @@ -40,6 +42,75 @@ public function testEmptyCachesException() */ public function testInvalidCacheException() { - new Chaincache(array(new \stdClass())); + new ChainCache(array(new \stdClass())); } + + public function testPrune() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $cache = new ChainCache(array( + $this->getPruneableMock(), + $this->getNonPruneableMock(), + $this->getPruneableMock(), + )); + $this->assertTrue($cache->prune()); + + $cache = new ChainCache(array( + $this->getPruneableMock(), + $this->getFailingPruneableMock(), + $this->getPruneableMock(), + )); + $this->assertFalse($cache->prune()); + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject|PruneableCacheInterface + */ + private function getPruneableMock() + { + $pruneable = $this + ->getMockBuilder(PruneableCacheInterface::class) + ->getMock(); + + $pruneable + ->expects($this->atLeastOnce()) + ->method('prune') + ->will($this->returnValue(true)); + + return $pruneable; + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject|PruneableCacheInterface + */ + private function getFailingPruneableMock() + { + $pruneable = $this + ->getMockBuilder(PruneableCacheInterface::class) + ->getMock(); + + $pruneable + ->expects($this->atLeastOnce()) + ->method('prune') + ->will($this->returnValue(false)); + + return $pruneable; + } + + /** + * @return \PHPUnit_Framework_MockObject_MockObject|CacheInterface + */ + private function getNonPruneableMock() + { + return $this + ->getMockBuilder(CacheInterface::class) + ->getMock(); + } +} + +interface PruneableCacheInterface extends PruneableInterface, CacheInterface +{ } diff --git a/src/Symfony/Component/Cache/Tests/Simple/PdoCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/PdoCacheTest.php index 47c0ee52d99ac..cf5730952dd84 100644 --- a/src/Symfony/Component/Cache/Tests/Simple/PdoCacheTest.php +++ b/src/Symfony/Component/Cache/Tests/Simple/PdoCacheTest.php @@ -12,12 +12,15 @@ namespace Symfony\Component\Cache\Tests\Simple; use Symfony\Component\Cache\Simple\PdoCache; +use Symfony\Component\Cache\Tests\Traits\PdoPruneableTrait; /** * @group time-sensitive */ class PdoCacheTest extends CacheTestCase { + use PdoPruneableTrait; + protected static $dbFile; public static function setupBeforeClass() diff --git a/src/Symfony/Component/Cache/Tests/Simple/PdoDbalCacheTest.php b/src/Symfony/Component/Cache/Tests/Simple/PdoDbalCacheTest.php index 51a10af30663b..0c40c04a2cae7 100644 --- a/src/Symfony/Component/Cache/Tests/Simple/PdoDbalCacheTest.php +++ b/src/Symfony/Component/Cache/Tests/Simple/PdoDbalCacheTest.php @@ -13,12 +13,15 @@ use Doctrine\DBAL\DriverManager; use Symfony\Component\Cache\Simple\PdoCache; +use Symfony\Component\Cache\Tests\Traits\PdoPruneableTrait; /** * @group time-sensitive */ class PdoDbalCacheTest extends CacheTestCase { + use PdoPruneableTrait; + protected static $dbFile; public static function setupBeforeClass() diff --git a/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php b/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php new file mode 100644 index 0000000000000..a9c459fb87171 --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Traits/PdoPruneableTrait.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\Traits; + +trait PdoPruneableTrait +{ + protected function isPruned($cache, $name) + { + $o = new \ReflectionObject($cache); + + if (!$o->hasMethod('getConnection')) { + self::fail('Cache does not have "getConnection()" method.'); + } + + $getPdoConn = $o->getMethod('getConnection'); + $getPdoConn->setAccessible(true); + + /** @var \Doctrine\DBAL\Statement $select */ + $select = $getPdoConn->invoke($cache)->prepare('SELECT 1 FROM cache_items WHERE item_id LIKE :id'); + $select->bindValue(':id', sprintf('%%%s', $name)); + $select->execute(); + + return 0 === count($select->fetchAll(\PDO::FETCH_COLUMN)); + } +} diff --git a/src/Symfony/Component/Cache/Traits/PdoTrait.php b/src/Symfony/Component/Cache/Traits/PdoTrait.php index 20d1eee1bd1b6..dd8c97d8ab1b2 100644 --- a/src/Symfony/Component/Cache/Traits/PdoTrait.php +++ b/src/Symfony/Component/Cache/Traits/PdoTrait.php @@ -34,6 +34,7 @@ trait PdoTrait private $username = ''; private $password = ''; private $connectionOptions = array(); + private $namespace; private function init($connOrDsn, $namespace, $defaultLifetime, array $options) { @@ -63,6 +64,7 @@ private function init($connOrDsn, $namespace, $defaultLifetime, array $options) $this->username = isset($options['db_username']) ? $options['db_username'] : $this->username; $this->password = isset($options['db_password']) ? $options['db_password'] : $this->password; $this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions; + $this->namespace = $namespace; parent::__construct($namespace, $defaultLifetime); } @@ -137,6 +139,27 @@ public function createTable() $conn->exec($sql); } + /** + * {@inheritdoc} + */ + public function prune() + { + $deleteSql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol <= :time"; + + if ('' !== $this->namespace) { + $deleteSql .= " AND $this->idCol LIKE :namespace"; + } + + $delete = $this->getConnection()->prepare($deleteSql); + $delete->bindValue(':time', time(), \PDO::PARAM_INT); + + if ('' !== $this->namespace) { + $delete->bindValue(':namespace', sprintf('%s%%', $this->namespace), \PDO::PARAM_STR); + } + + return $delete->execute(); + } + /** * {@inheritdoc} */ pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy