diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index 0cf42f7addb71..21d1ed2c87e70 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Cache\Adapter; use Psr\Cache\CacheItemInterface; -use Psr\Cache\CacheItemPoolInterface; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; use Symfony\Component\Cache\CacheItem; @@ -21,7 +20,7 @@ /** * @author Nicolas Grekas */ -abstract class AbstractAdapter implements CacheItemPoolInterface, LoggerAwareInterface +abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface { use LoggerAwareTrait; diff --git a/src/Symfony/Component/Cache/Adapter/AdapterInterface.php b/src/Symfony/Component/Cache/Adapter/AdapterInterface.php new file mode 100644 index 0000000000000..1179d16348330 --- /dev/null +++ b/src/Symfony/Component/Cache/Adapter/AdapterInterface.php @@ -0,0 +1,23 @@ + + * + * 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 {@see \Symfony\Component\Cache\CacheItem} instances. + * + * @author Kévin Dunglas + */ +interface AdapterInterface extends CacheItemPoolInterface +{ +} diff --git a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php index 64d0a4696d521..49c5accb29515 100644 --- a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Cache\Adapter; use Psr\Cache\CacheItemInterface; -use Psr\Cache\CacheItemPoolInterface; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; use Symfony\Component\Cache\CacheItem; @@ -21,7 +20,7 @@ /** * @author Nicolas Grekas */ -class ArrayAdapter implements CacheItemPoolInterface, LoggerAwareInterface +class ArrayAdapter implements AdapterInterface, LoggerAwareInterface { use LoggerAwareTrait; diff --git a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php new file mode 100644 index 0000000000000..8ebc02a100a2d --- /dev/null +++ b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php @@ -0,0 +1,178 @@ + + * + * 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 Psr\Cache\CacheItemPoolInterface; +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 + */ +class ChainAdapter implements AdapterInterface +{ + private $adapters = array(); + + /** + * @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 CacheItemPoolInterface) { + throw new InvalidArgumentException(sprintf('The class "%s" does not implement the "%s" interface.', get_class($adapter), CacheItemPoolInterface::class)); + } + + if ($adapter instanceof AdapterInterface) { + $this->adapters[] = $adapter; + } else { + $this->adapters[] = new ProxyAdapter($adapter); + } + } + } + + /** + * {@inheritdoc} + */ + public function getItem($key) + { + foreach ($this->adapters as $adapter) { + $item = $adapter->getItem($key); + + if ($item->isHit()) { + return $item; + } + } + + return $item; + } + + /** + * {@inheritdoc} + */ + public function getItems(array $keys = array()) + { + $items = array(); + foreach ($keys as $key) { + $items[$key] = $this->getItem($key); + } + + 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; + } +} diff --git a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php index 9e77ff4248a1b..8289e49536162 100644 --- a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php @@ -18,7 +18,7 @@ /** * @author Nicolas Grekas */ -class ProxyAdapter implements CacheItemPoolInterface +class ProxyAdapter implements AdapterInterface { private $pool; private $createCacheItem; diff --git a/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php new file mode 100644 index 0000000000000..97cb6bd24b80f --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Adapter/ChainAdapterTest.php @@ -0,0 +1,58 @@ + + * + * 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; +use Symfony\Component\Cache\Tests\Fixtures\ExternalAdapter; + +/** + * @author Kévin Dunglas + */ +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')) { + $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.'); + } + + return new ChainAdapter(array(new ArrayAdapter(), new ExternalAdapter(), new ApcuAdapter(__CLASS__))); + } + + /** + * @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException + */ + public function testLessThanTwoAdapterException() + { + new ChainAdapter(array()); + } + + /** + * @expectedException \Symfony\Component\Cache\Exception\InvalidArgumentException + */ + public function testInvalidAdapterException() + { + new ChainAdapter(array(new \stdClass(), new \stdClass())); + } +} diff --git a/src/Symfony/Component/Cache/Tests/Fixtures/ExternalAdapter.php b/src/Symfony/Component/Cache/Tests/Fixtures/ExternalAdapter.php new file mode 100644 index 0000000000000..493906ea0cccc --- /dev/null +++ b/src/Symfony/Component/Cache/Tests/Fixtures/ExternalAdapter.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Tests\Fixtures; + +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\Adapter\ArrayAdapter; + +/** + * Adapter not implementing the {@see \Symfony\Component\Cache\Adapter\AdapterInterface}. + * + * @author Kévin Dunglas + */ +class ExternalAdapter implements CacheItemPoolInterface +{ + private $cache; + + public function __construct() + { + $this->cache = new ArrayAdapter(); + } + + public function getItem($key) + { + return $this->cache->getItem($key); + } + + public function getItems(array $keys = array()) + { + return $this->cache->getItems($keys); + } + + public function hasItem($key) + { + return $this->cache->hasItem($key); + } + + public function clear() + { + return $this->cache->clear(); + } + + public function deleteItem($key) + { + return $this->cache->deleteItem($key); + } + + public function deleteItems(array $keys) + { + return $this->cache->deleteItems($keys); + } + + public function save(CacheItemInterface $item) + { + return $this->cache->save($item); + } + + public function saveDeferred(CacheItemInterface $item) + { + return $this->cache->saveDeferred($item); + } + + public function commit() + { + return $this->cache->commit(); + } +}