-
-
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 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
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 \Symfony\Component\Cache\CacheItem instances. | ||
* | ||
* @author Kévin Dunglas <dunglas@gmail.com> | ||
*/ | ||
interface AdapterInterface extends CacheItemPoolInterface | ||
{ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
<?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 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; | ||
|
||
/** | ||
* @param AdapterInterface[] $adapters | ||
*/ | ||
public function __construct(array $adapters) | ||
{ | ||
if (2 < count($adapters)) { | ||
throw new InvalidArgumentException('At least two adapters must be chained.'); | ||
} | ||
|
||
foreach ($adapters as $adapter) { | ||
if (!$adapter instanceof AdapterInterface) { | ||
throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', get_class($adapter), AdapterInterface::class)); | ||
} | ||
} | ||
|
||
$this->adapters = $adapters; | ||
} | ||
|
||
/** | ||
* {@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,41 @@ | ||
<?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; | ||
|
||
/** | ||
* @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.'); | ||
} | ||
|
||
re 3D15 turn new ChainAdapter(array(new ArrayAdapter(), new ApcuAdapter(__CLASS__))); | ||
} | ||
} |
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