8000 [Cache] Add a Chain adapter by nicolas-grekas · Pull Request #18215 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Cache] Add a Chain adapter #18215

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

Merged
merged 2 commits into from
Mar 27, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
[Cache] Optimize Chain adapter
  • Loading branch information
nicolas-grekas committed Mar 17, 2016
commit 68d9ceabb26f90d27832a75b17e95894205ace5a
3 changes: 2 additions & 1 deletion src/Symfony/Component/Cache/Adapter/AdapterInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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>
*/
Expand Down
72 changes: 60 additions & 12 deletions src/Symfony/Component/Cache/Adapter/ChainAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,30 @@

use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;

/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PHPdoc of this class could also be reworded a bit. It's confusing.

10000
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

* 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dots at the end of @param lines should be removed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*/
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) {
Expand All @@ -47,17 +50,38 @@ public function __construct(array $adapters)
$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
);
}

/**
* {@inheritdoc}
*/
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicolas-grekas I don't understand why you save the item with a new lifetime for a fetch operation. Can you explain it to me plz ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because there is no way to know the remaining lifetime of the item in its original pool...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thx

}

return $item;
}
}
Expand All @@ -70,12 +94,36 @@ public function getItem($key)
*/
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;
}
}
}

/**
Expand Down
14 changes: 5 additions & 9 deletions src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,6 @@
*/
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')) {
Expand All @@ -37,22 +31,24 @@ public function createCachePool()
$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()));
}
}
0