[Cache] Optimize Chain adapter
This commit is contained in:
parent
ebdcd16bdd
commit
68d9ceabb2
@ -12,9 +12,10 @@
|
||||
namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
|
||||
/**
|
||||
* Marker interface for adapters managing {@see \Symfony\Component\Cache\CacheItem} instances.
|
||||
* Interface for adapters managing instances of Symfony's {@see CacheItem}.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
|
@ -13,27 +13,30 @@ namespace Symfony\Component\Cache\Adapter;
|
||||
|
||||
use Psr\Cache\CacheItemInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Symfony\Component\Cache\CacheItem;
|
||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Chains adapters together.
|
||||
* Chains several adapters together.
|
||||
*
|
||||
* Saves, deletes and clears all registered adapter.
|
||||
* Gets data from the first chained adapter having it in cache.
|
||||
* Cached items are fetched from the first adapter having them in its data store.
|
||||
* They are saved and deleted in all adapters at once.
|
||||
*
|
||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||
*/
|
||||
class ChainAdapter implements AdapterInterface
|
||||
{
|
||||
private $adapters = array();
|
||||
private $saveUp;
|
||||
|
||||
/**
|
||||
* @param AdapterInterface[] $adapters
|
||||
* @param CacheItemPoolInterface[] $adapters The ordered list of adapters used to fetch cached items.
|
||||
* @param int $maxLifetime The max lifetime of items propagated from lower adapters to upper ones.
|
||||
*/
|
||||
public function __construct(array $adapters)
|
||||
public function __construct(array $adapters, $maxLifetime = 0)
|
||||
{
|
||||
if (2 > count($adapters)) {
|
||||
throw new InvalidArgumentException('At least two adapters must be chained.');
|
||||
if (!$adapters) {
|
||||
throw new InvalidArgumentException('At least one adapter must be specified.');
|
||||
}
|
||||
|
||||
foreach ($adapters as $adapter) {
|
||||
@ -47,6 +50,21 @@ class ChainAdapter implements AdapterInterface
|
||||
$this->adapters[] = new ProxyAdapter($adapter);
|
||||
}
|
||||
}
|
||||
|
||||
$this->saveUp = \Closure::bind(
|
||||
function ($adapter, $item) use ($maxLifetime) {
|
||||
$origDefaultLifetime = $item->defaultLifetime;
|
||||
|
||||
if (0 < $maxLifetime && ($origDefaultLifetime <= 0 || $maxLifetime < $origDefaultLifetime)) {
|
||||
$item->defaultLifetime = $maxLifetime;
|
||||
}
|
||||
|
||||
$adapter->save($item);
|
||||
$item->defaultLifetime = $origDefaultLifetime;
|
||||
},
|
||||
$this,
|
||||
CacheItem::class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,10 +72,16 @@ class ChainAdapter implements AdapterInterface
|
||||
*/
|
||||
public function getItem($key)
|
||||
{
|
||||
foreach ($this->adapters as $adapter) {
|
||||
$saveUp = $this->saveUp;
|
||||
|
||||
foreach ($this->adapters as $i => $adapter) {
|
||||
$item = $adapter->getItem($key);
|
||||
|
||||
if ($item->isHit()) {
|
||||
while (0 <= --$i) {
|
||||
$saveUp($this->adapters[$i], $item);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
@ -70,12 +94,36 @@ class ChainAdapter implements AdapterInterface
|
||||
*/
|
||||
public function getItems(array $keys = array())
|
||||
{
|
||||
$items = array();
|
||||
foreach ($keys as $key) {
|
||||
$items[$key] = $this->getItem($key);
|
||||
return $this->generateItems($this->adapters[0]->getItems($keys), 0);
|
||||
}
|
||||
|
||||
private function generateItems($items, $adapterIndex)
|
||||
{
|
||||
$missing = array();
|
||||
$nextAdapterIndex = $adapterIndex + 1;
|
||||
$nextAdapter = isset($this->adapters[$nextAdapterIndex]) ? $this->adapters[$nextAdapterIndex] : null;
|
||||
|
||||
foreach ($items as $k => $item) {
|
||||
if (!$nextAdapter || $item->isHit()) {
|
||||
yield $k => $item;
|
||||
} else {
|
||||
$missing[] = $k;
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
if ($missing) {
|
||||
$saveUp = $this->saveUp;
|
||||
$adapter = $this->adapters[$adapterIndex];
|
||||
$items = $this->generateItems($nextAdapter->getItems($missing), $nextAdapterIndex);
|
||||
|
||||
foreach ($items as $k => $item) {
|
||||
if ($item->isHit()) {
|
||||
$saveUp($adapter, $item);
|
||||
}
|
||||
|
||||
yield $k => $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,12 +22,6 @@ use Symfony\Component\Cache\Tests\Fixtures\ExternalAdapter;
|
||||
*/
|
||||
class ChainAdapterTest extends CachePoolTest
|
||||
{
|
||||
protected $skippedTests = array(
|
||||
'testDeferredSaveWithoutCommit' => 'Assumes a shared cache which ArrayAdapter is not.',
|
||||
'testSaveWithoutExpire' => 'Assumes a shared cache which ArrayAdapter is not.',
|
||||
'testDeferredExpired' => 'Failing for now, needs to be fixed.',
|
||||
);
|
||||
|
||||
public function createCachePool()
|
||||
{
|
||||
if (defined('HHVM_VERSION')) {
|
||||
@ -37,22 +31,24 @@ class ChainAdapterTest extends CachePoolTest
|
||||
$this->markTestSkipped('APCu extension is required.');
|
||||
}
|
||||
|
||||
return new ChainAdapter(array(new ArrayAdapter(), new ExternalAdapter(), new ApcuAdapter(__CLASS__)));
|
||||
return new ChainAdapter(array(new ArrayAdapter(), new ExternalAdapter(), new ApcuAdapter()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException
|
||||
* @expectedExceptionMessage At least one adapter must be specified.
|
||||
*/
|
||||
public function testLessThanTwoAdapterException()
|
||||
public function testEmptyAdaptersException()
|
||||
{
|
||||
new ChainAdapter(array());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException
|
||||
* @expectedExceptionMessage The class "stdClass" does not implement
|
||||
*/
|
||||
public function testInvalidAdapterException()
|
||||
{
|
||||
new ChainAdapter(array(new \stdClass(), new \stdClass()));
|
||||
new ChainAdapter(array(new \stdClass()));
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user