[Cache] Optimize Chain adapter
This commit is contained in:
parent
ebdcd16bdd
commit
68d9ceabb2
@ -12,9 +12,10 @@
|
|||||||
namespace Symfony\Component\Cache\Adapter;
|
namespace Symfony\Component\Cache\Adapter;
|
||||||
|
|
||||||
use Psr\Cache\CacheItemPoolInterface;
|
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>
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
*/
|
*/
|
||||||
|
@ -13,27 +13,30 @@ namespace Symfony\Component\Cache\Adapter;
|
|||||||
|
|
||||||
use Psr\Cache\CacheItemInterface;
|
use Psr\Cache\CacheItemInterface;
|
||||||
use Psr\Cache\CacheItemPoolInterface;
|
use Psr\Cache\CacheItemPoolInterface;
|
||||||
|
use Symfony\Component\Cache\CacheItem;
|
||||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chains adapters together.
|
* Chains several adapters together.
|
||||||
*
|
*
|
||||||
* Saves, deletes and clears all registered adapter.
|
* Cached items are fetched from the first adapter having them in its data store.
|
||||||
* Gets data from the first chained adapter having it in cache.
|
* They are saved and deleted in all adapters at once.
|
||||||
*
|
*
|
||||||
* @author Kévin Dunglas <dunglas@gmail.com>
|
* @author Kévin Dunglas <dunglas@gmail.com>
|
||||||
*/
|
*/
|
||||||
class ChainAdapter implements AdapterInterface
|
class ChainAdapter implements AdapterInterface
|
||||||
{
|
{
|
||||||
private $adapters = array();
|
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)) {
|
if (!$adapters) {
|
||||||
throw new InvalidArgumentException('At least two adapters must be chained.');
|
throw new InvalidArgumentException('At least one adapter must be specified.');
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($adapters as $adapter) {
|
foreach ($adapters as $adapter) {
|
||||||
@ -47,6 +50,21 @@ class ChainAdapter implements AdapterInterface
|
|||||||
$this->adapters[] = new ProxyAdapter($adapter);
|
$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)
|
public function getItem($key)
|
||||||
{
|
{
|
||||||
foreach ($this->adapters as $adapter) {
|
$saveUp = $this->saveUp;
|
||||||
|
|
||||||
|
foreach ($this->adapters as $i => $adapter) {
|
||||||
$item = $adapter->getItem($key);
|
$item = $adapter->getItem($key);
|
||||||
|
|
||||||
if ($item->isHit()) {
|
if ($item->isHit()) {
|
||||||
|
while (0 <= --$i) {
|
||||||
|
$saveUp($this->adapters[$i], $item);
|
||||||
|
}
|
||||||
|
|
||||||
return $item;
|
return $item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,12 +94,36 @@ class ChainAdapter implements AdapterInterface
|
|||||||
*/
|
*/
|
||||||
public function getItems(array $keys = array())
|
public function getItems(array $keys = array())
|
||||||
{
|
{
|
||||||
$items = array();
|
return $this->generateItems($this->adapters[0]->getItems($keys), 0);
|
||||||
foreach ($keys as $key) {
|
|
||||||
$items[$key] = $this->getItem($key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $items;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
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()
|
public function createCachePool()
|
||||||
{
|
{
|
||||||
if (defined('HHVM_VERSION')) {
|
if (defined('HHVM_VERSION')) {
|
||||||
@ -37,22 +31,24 @@ class ChainAdapterTest extends CachePoolTest
|
|||||||
$this->markTestSkipped('APCu extension is required.');
|
$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
|
* @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException
|
||||||
|
* @expectedExceptionMessage At least one adapter must be specified.
|
||||||
*/
|
*/
|
||||||
public function testLessThanTwoAdapterException()
|
public function testEmptyAdaptersException()
|
||||||
{
|
{
|
||||||
new ChainAdapter(array());
|
new ChainAdapter(array());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException
|
* @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException
|
||||||
|
* @expectedExceptionMessage The class "stdClass" does not implement
|
||||||
*/
|
*/
|
||||||
public function testInvalidAdapterException()
|
public function testInvalidAdapterException()
|
||||||
{
|
{
|
||||||
new ChainAdapter(array(new \stdClass(), new \stdClass()));
|
new ChainAdapter(array(new \stdClass()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user