Skip to content

Commit

Permalink
feature #23603 [Cache] Add (pdo|chain) cache (adapter|simple) prune m…
Browse files Browse the repository at this point in the history
…ethod (robfrawley)

This PR was merged into the 3.4 branch.

Discussion
----------

[Cache] Add (pdo|chain) cache (adapter|simple) prune method

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        | symfony/symfony-docs#8209

This is additional work toward making more cache implementations use the `PruneableInterface` contract. Specifically, this pull request focuses on `(Pdo|Chain)(Cache|Adapter)`, as suggested by @nicolas-grekas in #23451 (comment).

Commits
-------

b20a237 add (pdo|chain) cache (adapter|simple) prune method
  • Loading branch information
nicolas-grekas committed Aug 31, 2017
2 parents 89d85f3 + b20a237 commit 9cce236
Show file tree
Hide file tree
Showing 16 changed files with 284 additions and 9 deletions.
19 changes: 18 additions & 1 deletion src/Symfony/Component/Cache/Adapter/ChainAdapter.php
Expand Up @@ -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.
Expand All @@ -24,7 +25,7 @@
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ChainAdapter implements AdapterInterface
class ChainAdapter implements AdapterInterface, PruneableInterface
{
private $adapters = array();
private $adapterCount;
Expand Down Expand Up @@ -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;
}
}
3 changes: 2 additions & 1 deletion src/Symfony/Component/Cache/Adapter/PdoAdapter.php
Expand Up @@ -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;

Expand Down
6 changes: 3 additions & 3 deletions src/Symfony/Component/Cache/CHANGELOG.md
Expand Up @@ -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
-----
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/Cache/PruneableInterface.php
Expand Up @@ -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
{
Expand Down
19 changes: 18 additions & 1 deletion src/Symfony/Component/Cache/Simple/ChainCache.php
Expand Up @@ -13,6 +13,7 @@

use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\PruneableInterface;

/**
* Chains several caches together.
Expand All @@ -22,7 +23,7 @@
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ChainCache implements CacheInterface
class ChainCache implements CacheInterface, PruneableInterface
{
private $miss;
private $caches = array();
Expand Down Expand Up @@ -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;
}
}
3 changes: 2 additions & 1 deletion src/Symfony/Component/Cache/Simple/PdoCache.php
Expand Up @@ -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;

Expand Down
14 changes: 14 additions & 0 deletions src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php
Expand Up @@ -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
Expand Down Expand Up @@ -103,6 +104,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) {
Expand All @@ -116,6 +118,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'));
Expand Down
71 changes: 71 additions & 0 deletions src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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
{
}
3 changes: 3 additions & 0 deletions src/Symfony/Component/Cache/Tests/Adapter/PdoAdapterTest.php
Expand Up @@ -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()
Expand Down
Expand Up @@ -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()
Expand Down
14 changes: 14 additions & 0 deletions src/Symfony/Component/Cache/Tests/Simple/CacheTestCase.php
Expand Up @@ -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
Expand Down Expand Up @@ -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'));
Expand Down
73 changes: 72 additions & 1 deletion src/Symfony/Component/Cache/Tests/Simple/ChainCacheTest.php
Expand Up @@ -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;
Expand Down Expand Up @@ -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
{
}

0 comments on commit 9cce236

Please sign in to comment.