8000 feature #18215 [Cache] Add a Chain adapter (dunglas, nicolas-grekas) · symfony/symfony@ae86e5b · GitHub
[go: up one dir, main page]

Skip to content

Commit ae86e5b

Browse files
committed
feature #18215 [Cache] Add a Chain adapter (dunglas, nicolas-grekas)
This PR was merged into the 3.1-dev branch. Discussion ---------- [Cache] Add a Chain adapter | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | no | Fixed tickets | #17556 | License | MIT | Doc PR | - Made in coordination with @dunglas Commits ------- 68d9cea [Cache] Optimize Chain adapter ebdcd16 [Cache] Add a Chain adapter
2 parents fa01e84 + 68d9cea commit ae86e5b

File tree

7 files changed

+383
-5
lines changed

7 files changed

+383
-5
lines changed

src/Symfony/Component/Cache/Adapter/AbstractAdapter.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\Component\Cache\Adapter;
1313

1414
use Psr\Cache\CacheItemInterface;
15-
use Psr\Cache\CacheItemPoolInterface;
1615
use Psr\Log\LoggerAwareInterface;
1716
use Psr\Log\LoggerAwareTrait;
1817
use Symfony\Component\Cache\CacheItem;
@@ -21,7 +20,7 @@
2120
/**
2221
* @author Nicolas Grekas <p@tchwork.com>
2322
*/
24-
abstract class AbstractAdapter implements CacheItemPoolInterface, LoggerAwareInterface
23+
abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface
2524
{
2625
use LoggerAwareTrait;
2726

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Cache\Adapter;
13+
14+
use Psr\Cache\CacheItemPoolInterface;
15+
use Symfony\Component\Cache\CacheItem;
16+
17+
/**
18+
* Interface for adapters managing instances of Symfony's {@see CacheItem}.
19+
*
20+
* @author Kévin Dunglas <dunglas@gmail.com>
21+
*/
22+
interface AdapterInterface extends CacheItemPoolInterface
23+
{
24+
}

src/Symfony/Component/Cache/Adapter/ArrayAdapter.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\Component\Cache\Adapter;
1313

1414
use Psr\Cache\CacheItemInterface;
15-
use Psr\Cache\CacheItemPoolInterface;
1615
use Psr\Log\LoggerAwareInterface;
1716
use Psr\Log\LoggerAwareTrait;
1817
use Symfony\Component\Cache\CacheItem;
@@ -21,7 +20,7 @@
2120
/**
2221
* @author Nicolas Grekas <p@tchwork.com>
2322
*/
24-
class ArrayAdapter implements CacheItemPoolInterface, LoggerAwareInterface
23+
class ArrayAdapter implements AdapterInterface, LoggerAwareInterface
2524
{
2625
use LoggerAwareTrait;
2726

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Cache\Adapter;
13+
14+
use Psr\Cache\CacheItemInterface;
15+
use Psr\Cache\CacheItemPoolInterface;
16+
use Symfony\Component\Cache\CacheItem;
17+
use Symfony\Component\Cache\Exception\InvalidArgumentException;
18+
19+
/**
20+
* Chains several adapters together.
21+
*
22+
* Cached items are fetched from the first adapter having them in its data store.
23+
* They are saved and deleted in all adapters at once.
24+
*
25+
* @author Kévin Dunglas <dunglas@gmail.com>
26+
*/
27+
class ChainAdapter implements AdapterInterface
28+
{
29+
private $adapters = array();
30+
private $saveUp;
31+
32+
/**
33+
* @param CacheItemPoolInterface[] $adapters The ordered list of adapters used to fetch cached items.
34+
* @param int $maxLifetime The max lifetime of items propagated from lower adapters to upper ones.
35+
*/
36+
public function __construct(array $adapters, $maxLifetime = 0)
37+
{
38+
if (!$adapters) {
39+
throw new InvalidArgumentException('At least one adapter must be specified.');
40+
}
41+
42+
foreach ($adapters as $adapter) {
43+
if (!$adapter instanceof CacheItemPoolInterface) {
44+
throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', get_class($adapter), CacheItemPoolInterface::class));
45+
}
46+
47+
if ($adapter instanceof AdapterInterface) {
48+
$this->adapters[] = $adapter;
49+
} else {
50+
$this->adapters[] = new ProxyAdapter($adapter);
51+
}
52+
}
53+
54+
$this->saveUp = \Closure::bind(
55+
function ($adapter, $item) use ($maxLifetime) {
56+
$origDefaultLifetime = $item->defaultLifetime;
57+
58+
if (0 < $maxLifetime && ($origDefaultLifetime <= 0 || $maxLifetime < $origDefaultLifetime)) {
59+
$item->defaultLifetime = $maxLifetime;
60+
}
61+
62+
$adapter->save($item);
63+
$item->defaultLifetime = $origDefaultLifetime;
64+
},
65+
$this,
66+
CacheItem::class
67+
);
68+
}
69+
70+
/**
71+
* {@inheritdoc}
72+
*/
73+
public function getItem($key)
74+
{
75+
$saveUp = $this->saveUp;
76+
77+
foreach ($this->adapters as $i => $adapter) {
78+
$item = $adapter->getItem($key);
79+
80+
if ($item->isHit()) {
81+
while (0 <= --$i) {
82+
$saveUp($this->adapters[$i], $item);
83+
}
84+
85+
return $item;
86+
}
87+
}
88+
89+
return $item;
90+
}
91+
92+
/**
93+
* {@inheritdoc}
94+
*/
95+
public function getItems(array $keys = array())
96+
{
97+
return $this->generateItems($this->adapters[0]->getItems($keys), 0);
98+
}
99+
100+
private function generateItems($items, $adapterIndex)
101+
{
102+
$missing = array();
103+
$nextAdapterIndex = $adapterIndex + 1;
104+
$nextAdapter = isset($this->adapters[$nextAdapterIndex]) ? $this->adapters[$nextAdapterIndex] : null;
105+
106+
foreach ($items as $k => $item) {
107+
if (!$nextAdapter || $item->isHit()) {
108+
yield $k => $item;
109+
} else {
110+
$missing[] = $k;
111+
}
112+
}
113+
114+
if ($missing) {
115+
$saveUp = $this->saveUp;
116+
$adapter = $this->adapters[$adapterIndex];
117+
$items = $this->generateItems($nextAdapter->getItems($missing), $nextAdapterIndex);
118+
119+
foreach ($items as $k => $item) {
120+
if ($item->isHit()) {
121+
$saveUp($adapter, $item);
122+
}
123+
124+
yield $k => $item;
125+
}
126+
}
127+
}
128+
129+
/**
130+
* {@inheritdoc}
131+
*/
132+
public function hasItem($key)
133+
{
134+
foreach ($this->adapters as $adapter) {
135+
if ($adapter->hasItem($key)) {
136+
return true;
137+
}
138+
}
139+
140+
return false;
141+
}
142+
143+
/**
144+
* {@inheritdoc}
145+
*/
146+
public function clear()
147+
{
148+
$cleared = true;
149+
150+
foreach ($this->adapters as $adapter) {
151+
$cleared = $adapter->clear() && $cleared;
152+
}
153+
154+
return $cleared;
155+
}
156+
157+
/**
158+
* {@inheritdoc}
159+
*/
160+
public function deleteItem($key)
161+
{
162+
$deleted = true;
163+
164+
foreach ($this->adapters as $adapter) {
165+
$deleted = $adapter->deleteItem($key) && $deleted;
166+
}
167+
168+
return $deleted;
169+
}
170+
171+
/**
172+
* {@inheritdoc}
173+
*/
174+
public function deleteItems(array $keys)
175+
{
176+
$deleted = true;
177+
178+
foreach ($this->adapters as $adapter) {
179+
$deleted = $adapter->deleteItems($keys) && $deleted;
180+
}
181+
182+
return $deleted;
183+
}
184+
185+
/**
186+
* {@inheritdoc}
187+
*/
188+
public function save(CacheItemInterface $item)
189+
{
190+
$saved = true;
191+
192+
foreach ($this->adapters as $adapter) {
193+
$saved = $adapter->save($item) && $saved;
194+
}
195+
196+
return $saved;
197+
}
198+
199+
/**
200+
* {@inheritdoc}
201+
*/
202+
public function saveDeferred(CacheItemInterface $item)
203+
{
204+
$saved = true;
205+
206+
foreach ($this->adapters as $adapter) {
207+
$saved = $adapter->saveDeferred($item) && $saved;
208+
}
209+
210+
return $saved;
211+
}
212+
213+
/**
214+
* {@inheritdoc}
215+
*/
216+
public function commit()
217+
{
218+
$committed = true;
219+
220+
foreach ($this->adapters as $adapter) {
221+
$committed = $adapter->commit() && $committed;
222+
}
223+
224+
return $committed;
225+
}
226+
}

src/Symfony/Component/Cache/Adapter/ProxyAdapter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
/**
2020
* @author Nicolas Grekas <p@tchwork.com>
2121
*/
22-
class ProxyAdapter implements CacheItemPoolInterface
22+
class ProxyAdapter implements AdapterInterface
2323
{
2424
private $pool;
2525
private $namespace;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Cache\Tests\Adapter;
13+
14+
use Cache\IntegrationTests\CachePoolTest;
15+
use Symfony\Component\Cache\Adapter\ApcuAdapter;
16+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
17+
use Symfony\Component\Cache\Adapter\ChainAdapter;
18+
use Symfony\Component\Cache\Tests\Fixtures\ExternalAdapter;
19+
20+
/**
21+
* @author Kévin Dunglas <dunglas@gmail.com>
22+
*/
23+
class ChainAdapterTest extends CachePoolTest
24+
{
25+
public function createCachePool()
26+
{
27+
if (defined('HHVM_VERSION')) {
28+
$this->skippedTests['testDeferredSaveWithoutCommit'] = 'Fails on HHVM';
29+
}
30+
if (!function_exists('apcu_fetch') || !ini_get('apc.enabled') || ('cli' === PHP_SAPI && !ini_get('apc.enable_cli'))) {
31+
$this->markTestSkipped('APCu extension is required.');
32+
}
33+
34+
return new ChainAdapter(array(new ArrayAdapter(), new ExternalAdapter(), new ApcuAdapter()));
35+
}
36+
37+
/**
38+
* @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException
39+
* @expectedExceptionMessage At least one adapter must be specified.
40+
*/
41+
public function testEmptyAdaptersException()
42+
{
43+
new ChainAdapter(array());
44+
}
45+
46+
/**
47+
* @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException
48+
* @expectedExceptionMessage The class "stdClass" does not implement
49+
*/
50+
public function testInvalidAdapterException()
51+
{
52+
new ChainAdapter(array(new \stdClass()));
53+
}
54+
}

0 commit comments

Comments
 (0)
0