-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Cache] Add a Chain adapter #17556
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Cache] Add a Chain adapter #17556
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<?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 Psr\Cache\CacheItemPoolInterface; | ||
|
||
/** | ||
* Marker interface for adapters managing {@see \Symfony\Component\Cache\CacheItem} instances. | ||
* | ||
* @author Kévin Dunglas <dunglas@gmail.com> | ||
*/ | ||
interface AdapterInterface extends CacheItemPoolInterface | ||
{ | ||
} |
10000
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
<?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 Psr\Cache\CacheItemInterface; | ||
use Psr\Cache\CacheItemPoolInterface; | ||
use Symfony\Component\Cache\Exception\InvalidArgumentException; | ||
|
||
/** | ||
* Chains adapters together. | ||
* | ||
* Saves, deletes and clears all registered adapter. | ||
* Gets data from the first chained adapter having it in cache. | ||
* | ||
* @author Kévin Dunglas <dunglas@gmail.com> | ||
*/ | ||
class ChainAdapter implements AdapterInterface | ||
{ | ||
private $adapters = array(); | ||
|
||
/** | ||
* @param AdapterInterface[] $adapters | ||
*/ | ||
public function __construct(array $adapters) | ||
{ | ||
if (2 > count($adapters)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? When making this configurable by the user, you might end up with just one adapter, which is fine. I would change the check to |
||
throw new InvalidArgumentException('At least two adapters must be chained.'); | ||
} | ||
|
||
foreach ($adapters as $adapter) { | ||
if (!$adapter instanceof CacheItemPoolInterface) { | ||
throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', get_class($adapter), CacheItemPoolInterface::class)); | ||
} | ||
|
||
if ($adapter instanceof AdapterInterface) { | ||
$this->adapters[] = $adapter; | ||
} else { | ||
$this->adapters[] = new ProxyAdapter($adapter); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getItem($key) | ||
{ | ||
foreach ($this->adapters as $adapter) { | ||
$item = $adapter->getItem($key); | ||
|
||
if ($item->isHit()) { | ||
return $item; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering: when an item is found in a lower level adapter, shouldn't it be stored in the upper level adapters? |
||
} | ||
} | ||
|
||
return $item; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function getItems(array $keys = array()) | ||
{ | ||
$items = array(); | ||
foreach ($keys as $key) { | ||
$items[$key] = $this->getItem($key); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This implementation is naïve and prevents efficient retrieval of multiple items. We should use getItems() on chained adapters instead, until all of the items are found |
||
} | ||
|
||
return $items; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function hasItem($key) | ||
{ | ||
foreach ($this->adapters as $adapter) { | ||
if ($adapter->hasItem($key)) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function clear() | ||
{ | ||
$cleared = true; | ||
|
||
foreach ($this->adapters as $adapter) { | ||
$cleared = $adapter->clear() && $cleared; | ||
} | ||
|
||
return $cleared; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function deleteItem($key) | ||
{ | ||
$deleted = true; | ||
|
||
foreach ($this->adapters as $adapter) { | ||
$deleted = $adapter->deleteItem($key) && $deleted; | ||
} | ||
|
||
return $deleted; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function deleteItems(array $keys) | ||
{ | ||
$deleted = true; | ||
|
||
foreach ($this->adapters as $adapter) { | ||
$deleted = $adapter->deleteItems($keys) && $deleted; | ||
} | ||
|
||
return $deleted; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function save(CacheItemInterface $item) | ||
{ | ||
$saved = true; | ||
|
||
foreach ($this->adapters as $adapter) { | ||
$saved = $adapter->save($item) && $saved; | ||
} | ||
|
||
return $saved; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function saveDeferred(CacheItemInterface $item) | ||
{ | ||
$saved = true; | ||
|
||
foreach ($this->adapters as $adapter) { | ||
$saved = $adapter->saveDeferred($item) && $saved; | ||
} | ||
|
||
return $saved; | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public function commit() | ||
{ | ||
$committed = true; | ||
|
||
foreach ($this->adapters as $adapter) { | ||
$committed = $adapter->commit() && $committed; | ||
} | ||
|
||
return $committed; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
<?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 Symfony\Component\Cache\Adapter\ApcuAdapter; | ||
use Symfony\Component\Cache\Adapter\ArrayAdapter; | ||
use Symfony\Component\Cache\Adapter\ChainAdapter; | ||
use Symfony\Component\Cache\Tests\Fixtures\ExternalAdapter; | ||
|
||
/** | ||
* @author Kévin Dunglas <dunglas@gmail.com> | ||
*/ | ||
class ChainAdapterTest extends CachePoolTest | ||
{ | ||
protected $skippedTests = array( | ||
'testDeferredSaveWithoutCommit' => 'Assumes a shared cache which ArrayAdapter is not.', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this looks suspicious to me here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, same for the two others below, there are not needed anymore I think |
||
'testSaveWithoutExpire' => 'Assumes a shared cache which ArrayAdapter is not.', | ||
'testDeferredExpired' => 'Failing for now, needs to be fixed.', | ||
); | ||
|
||
public function createCachePool() | ||
{ | ||
if (defined('HHVM_VERSION')) { | ||
$this->skippedTests['testDeferredSaveWithoutCommit'] = 'Fails on HHVM'; | ||
} | ||
if (!function_exists('apcu_fetch') || !ini_get('apc.enabled') || ('cli' === PHP_SAPI && !ini_get('apc.enable_cli'))) { | ||
$this->markTestSkipped('APCu extension is required.'); | ||
} | ||
|
||
return new ChainAdapter(array(new ArrayAdapter(), new ExternalAdapter(), new ApcuAdapter(__CLASS__))); | ||
} | ||
|
||
/** | ||
* @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException | ||
*/ | ||
public function testLessThanTwoAdapterException() | ||
{ | ||
new ChainAdapter(array()); | ||
} | ||
|
||
/** | ||
* @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException | ||
*/ | ||
public function testInvalidAdapterException() | ||
{ | ||
new ChainAdapter(array(new \stdClass(), new \stdClass())); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
<?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\Fixtures; | ||
|
||
use Psr\Cache\CacheItemInterface; | ||
use Psr\Cache\CacheItemPoolInterface; | ||
use Symfony\Component\Cache\Adapter\ArrayAdapter; | ||
|
||
/** | ||
* Adapter not implementing the {@see \Symfony\Component\Cache\Adapter\AdapterInterface}. | ||
* | ||
* @author Kévin Dunglas <dunglas@gmail.com> | ||
*/ | ||
class ExternalAdapter implements CacheItemPoolInterface | ||
{ | ||
private $cache; | ||
|
||
public function __construct() | ||
{ | ||
$this->cache = new ArrayAdapter(); | ||
} | ||
|
||
public function getItem($key) | ||
{ | ||
return $this->cache->getItem($key); | ||
} | ||
|
||
public function getItems(array $keys = array()) | ||
{ | ||
return $this->cache->getItems($keys); | ||
} | ||
|
||
public function hasItem($key) | ||
{ | ||
return $this->cache->hasItem($key); | ||
} | ||
|
||
public function clear() | ||
{ | ||
return $this->cache->clear(); | ||
} | ||
|
||
public function deleteItem($key) | ||
{ | ||
return $this->cache->deleteItem($key); | ||
} | ||
|
||
public function deleteItems(array $keys) | ||
{ | ||
return $this->cache->deleteItems($keys); | ||
} | ||
|
||
public function save(CacheItemInterface $item) | ||
{ | ||
return $this->cache->save($item); | ||
} | ||
|
||
public function saveDeferred(CacheItemInterface $item) | ||
{ | ||
return $this->cache->saveDeferred($item); | ||
} | ||
|
||
public function commit() | ||
{ | ||
return $this->cache->commit(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
imo this interface deserves a dedicated PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in which ProxyAdapter is changed to use it to detect compatible CacheItems