From 589ff697f4a09b6aa391389cc64d5504b4ba1eb1 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 14 Apr 2018 19:21:41 -0500 Subject: [PATCH] [Cache] Add [Taggable]CacheInterface, the easiest way to use a cache --- .../Resources/config/cache.xml | 6 +++ .../Cache/Adapter/AbstractAdapter.php | 5 ++- .../Component/Cache/Adapter/ArrayAdapter.php | 5 ++- .../Component/Cache/Adapter/ChainAdapter.php | 35 ++++++++++++++- .../Component/Cache/Adapter/NullAdapter.php | 6 ++- .../Cache/Adapter/PhpArrayAdapter.php | 30 ++++++++++++- .../Component/Cache/Adapter/ProxyAdapter.php | 19 +++++++- .../Cache/Adapter/TagAwareAdapter.php | 6 ++- .../Cache/Adapter/TraceableAdapter.php | 36 +++++++++++++++- .../Adapter/TraceableTagAwareAdapter.php | 4 +- src/Symfony/Component/Cache/CHANGELOG.md | 6 +++ .../Component/Cache/CacheInterface.php | 37 ++++++++++++++++ src/Symfony/Component/Cache/CacheItem.php | 7 ++- .../DataCollector/CacheDataCollector.php | 10 ++++- .../Cache/Exception/LogicException.php | 19 ++++++++ .../Cache/TaggableCacheInterface.php | 35 +++++++++++++++ .../Cache/Tests/Adapter/AdapterTestCase.php | 21 +++++++++ .../Tests/Adapter/PhpArrayAdapterTest.php | 1 + .../Component/Cache/Tests/CacheItemTest.php | 21 +++++++++ .../Component/Cache/Traits/GetTrait.php | 43 +++++++++++++++++++ 20 files changed, 341 insertions(+), 11 deletions(-) create mode 100644 src/Symfony/Component/Cache/CacheInterface.php create mode 100644 src/Symfony/Component/Cache/Exception/LogicException.php create mode 100644 src/Symfony/Component/Cache/TaggableCacheInterface.php create mode 100644 src/Symfony/Component/Cache/Traits/GetTrait.php diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml index f7162adb1c..d0e596ab83 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml @@ -15,6 +15,10 @@ + + + + @@ -122,7 +126,9 @@ + + diff --git a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php index 3afc982089..1e246b8790 100644 --- a/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/AbstractAdapter.php @@ -15,17 +15,20 @@ use Psr\Cache\CacheItemInterface; use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; +use Symfony\Component\Cache\CacheInterface; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\ResettableInterface; use Symfony\Component\Cache\Traits\AbstractTrait; +use Symfony\Component\Cache\Traits\GetTrait; /** * @author Nicolas Grekas */ -abstract class AbstractAdapter implements AdapterInterface, LoggerAwareInterface, ResettableInterface +abstract class AbstractAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface { use AbstractTrait; + use GetTrait; private static $apcuSupported; private static $phpFilesSupported; diff --git a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php index fee7ed6d90..17f2beaf0f 100644 --- a/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ArrayAdapter.php @@ -13,16 +13,19 @@ namespace Symfony\Component\Cache\Adapter; use Psr\Cache\CacheItemInterface; use Psr\Log\LoggerAwareInterface; +use Symfony\Component\Cache\CacheInterface; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\ResettableInterface; use Symfony\Component\Cache\Traits\ArrayTrait; +use Symfony\Component\Cache\Traits\GetTrait; /** * @author Nicolas Grekas */ -class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, ResettableInterface +class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface { use ArrayTrait; + use GetTrait; private $createCacheItem; diff --git a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php index 98b0cc2469..ea0af87d9f 100644 --- a/src/Symfony/Component/Cache/Adapter/ChainAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ChainAdapter.php @@ -13,10 +13,12 @@ namespace Symfony\Component\Cache\Adapter; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheInterface; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\GetTrait; /** * Chains several adapters together. @@ -26,8 +28,10 @@ use Symfony\Component\Cache\ResettableInterface; * * @author Kévin Dunglas */ -class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableInterface +class ChainAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface { + use GetTrait; + private $adapters = array(); private $adapterCount; private $syncItem; @@ -61,6 +65,8 @@ class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableIn $item->expiry = $sourceItem->expiry; $item->isHit = $sourceItem->isHit; + $sourceItem->isTaggable = false; + if (0 < $sourceItem->defaultLifetime && $sourceItem->defaultLifetime < $defaultLifetime) { $defaultLifetime = $sourceItem->defaultLifetime; } @@ -75,6 +81,33 @@ class ChainAdapter implements AdapterInterface, PruneableInterface, ResettableIn ); } + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback) + { + $lastItem = null; + $i = 0; + $wrap = function (CacheItem $item = null) use ($key, $callback, &$wrap, &$i, &$lastItem) { + $adapter = $this->adapters[$i]; + if (isset($this->adapters[++$i])) { + $callback = $wrap; + } + if ($adapter instanceof CacheInterface) { + $value = $adapter->get($key, $callback); + } else { + $value = $this->doGet($adapter, $key, $callback); + } + if (null !== $item) { + ($this->syncItem)($lastItem = $lastItem ?? $item, $item); + } + + return $value; + }; + + return $wrap(); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Cache/Adapter/NullAdapter.php b/src/Symfony/Component/Cache/Adapter/NullAdapter.php index f58f81e5b8..44929f9e76 100644 --- a/src/Symfony/Component/Cache/Adapter/NullAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/NullAdapter.php @@ -12,13 +12,17 @@ namespace Symfony\Component\Cache\Adapter; use Psr\Cache\CacheItemInterface; +use Symfony\Component\Cache\CacheInterface; use Symfony\Component\Cache\CacheItem; +use Symfony\Component\Cache\Traits\GetTrait; /** * @author Titouan Galopin */ -class NullAdapter implements AdapterInterface +class NullAdapter implements AdapterInterface, CacheInterface { + use GetTrait; + private $createCacheItem; public function __construct() diff --git a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php index ca5ef743d2..bcd322fede 100644 --- a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php @@ -13,10 +13,12 @@ namespace Symfony\Component\Cache\Adapter; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheInterface; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\Exception\InvalidArgumentException; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\GetTrait; use Symfony\Component\Cache\Traits\PhpArrayTrait; /** @@ -26,9 +28,10 @@ use Symfony\Component\Cache\Traits\PhpArrayTrait; * @author Titouan Galopin * @author Nicolas Grekas */ -class PhpArrayAdapter implements AdapterInterface, PruneableInterface, ResettableInterface +class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface { use PhpArrayTrait; + use GetTrait; private $createCacheItem; @@ -77,6 +80,31 @@ class PhpArrayAdapter implements AdapterInterface, PruneableInterface, Resettabl return $fallbackPool; } + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback) + { + if (null === $this->values) { + $this->initialize(); + } + if (null === $value = $this->values[$key] ?? null) { + if ($this->pool instanceof CacheInterface) { + return $this->pool->get($key, $callback); + } + + return $this->doGet($this->pool, $key, $callback); + } + if ('N;' === $value) { + return null; + } + if (\is_string($value) && isset($value[2]) && ':' === $value[1]) { + return unserialize($value); + } + + return $value; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php index da286dbf17..b9981f5e64 100644 --- a/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/ProxyAdapter.php @@ -13,17 +13,20 @@ namespace Symfony\Component\Cache\Adapter; use Psr\Cache\CacheItemInterface; use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheInterface; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\Traits\GetTrait; use Symfony\Component\Cache\Traits\ProxyTrait; /** * @author Nicolas Grekas */ -class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableInterface +class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface { use ProxyTrait; + use GetTrait; private $namespace; private $namespaceLen; @@ -54,6 +57,20 @@ class ProxyAdapter implements AdapterInterface, PruneableInterface, ResettableIn ); } + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback) + { + if (!$this->pool instanceof CacheInterface) { + return $this->doGet($this->pool, $key, $callback); + } + + return $this->pool->get($this->getId($key), function ($innerItem) use ($key, $callback) { + return $callback(($this->createCacheItem)($key, $innerItem)); + }); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php index 62f815e017..257e404e1c 100644 --- a/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TagAwareAdapter.php @@ -16,16 +16,19 @@ use Psr\Cache\InvalidArgumentException; use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; +use Symfony\Component\Cache\TaggableCacheInterface; +use Symfony\Component\Cache\Traits\GetTrait; use Symfony\Component\Cache\Traits\ProxyTrait; /** * @author Nicolas Grekas */ -class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, ResettableInterface +class TagAwareAdapter implements TagAwareAdapterInterface, TaggableCacheInterface, PruneableInterface, ResettableInterface { const TAGS_PREFIX = "\0tags\0"; use ProxyTrait; + use GetTrait; private $deferred = array(); private $createCacheItem; @@ -58,6 +61,7 @@ class TagAwareAdapter implements TagAwareAdapterInterface, PruneableInterface, R ); $this->setCacheItemTags = \Closure::bind( function (CacheItem $item, $key, array &$itemTags) { + $item->isTaggable = true; if (!$item->isHit) { return $item; } diff --git a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php index 98d0e52693..a0df682d92 100644 --- a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php @@ -12,6 +12,8 @@ namespace Symfony\Component\Cache\Adapter; use Psr\Cache\CacheItemInterface; +use Symfony\Component\Cache\CacheInterface; +use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\PruneableInterface; use Symfony\Component\Cache\ResettableInterface; @@ -22,7 +24,7 @@ use Symfony\Component\Cache\ResettableInterface; * @author Tobias Nyholm * @author Nicolas Grekas */ -class TraceableAdapter implements AdapterInterface, PruneableInterface, ResettableInterface +class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface { protected $pool; private $calls = array(); @@ -32,6 +34,38 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab $this->pool = $pool; } + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback) + { + if (!$this->pool instanceof CacheInterface) { + throw new \BadMethodCallException(sprintf('Cannot call "%s::get()": this class doesn\'t implement "%s".', get_class($this->pool), CacheInterface::class)); + } + + $isHit = true; + $callback = function (CacheItem $item) use ($callback, &$isHit) { + $isHit = $item->isHit(); + + return $callback($item); + }; + + $event = $this->start(__FUNCTION__); + try { + $value = $this->pool->get($key, $callback); + $event->result[$key] = \is_object($value) ? \get_class($value) : gettype($value); + } finally { + $event->end = microtime(true); + } + if ($isHit) { + ++$event->hits; + } else { + ++$event->misses; + } + + return $value; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Cache/Adapter/TraceableTagAwareAdapter.php b/src/Symfony/Component/Cache/Adapter/TraceableTagAwareAdapter.php index de68955d8e..2fda8b3602 100644 --- a/src/Symfony/Component/Cache/Adapter/TraceableTagAwareAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TraceableTagAwareAdapter.php @@ -11,10 +11,12 @@ namespace Symfony\Component\Cache\Adapter; +use Symfony\Component\Cache\TaggableCacheInterface; + /** * @author Robin Chalas */ -class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface +class TraceableTagAwareAdapter extends TraceableAdapter implements TaggableCacheInterface, TagAwareAdapterInterface { public function __construct(TagAwareAdapterInterface $pool) { diff --git a/src/Symfony/Component/Cache/CHANGELOG.md b/src/Symfony/Component/Cache/CHANGELOG.md index 11c1b9364e..d21b2cbda4 100644 --- a/src/Symfony/Component/Cache/CHANGELOG.md +++ b/src/Symfony/Component/Cache/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +4.2.0 +----- + + * added `CacheInterface` and `TaggableCacheInterface` + * throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool + 3.4.0 ----- diff --git a/src/Symfony/Component/Cache/CacheInterface.php b/src/Symfony/Component/Cache/CacheInterface.php new file mode 100644 index 0000000000..c5c877ddbd --- /dev/null +++ b/src/Symfony/Component/Cache/CacheInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +use Psr\Cache\CacheItemInterface; + +/** + * Gets and stores items from a cache. + * + * On cache misses, a callback is called that should return the missing value. + * It is given two arguments: + * - the missing cache key + * - the corresponding PSR-6 CacheItemInterface object, + * allowing time-based expiration control. + * + * If you need tag-based invalidation, use TaggableCacheInterface instead. + * + * @author Nicolas Grekas + */ +interface CacheInterface +{ + /** + * @param callable(CacheItemInterface):mixed $callback Should return the computed value for the given key/item + * + * @return mixed The value corresponding to the provided key + */ + public function get(string $key, callable $callback); +} diff --git a/src/Symfony/Component/Cache/CacheItem.php b/src/Symfony/Component/Cache/CacheItem.php index cecaa126d9..82ad9df682 100644 --- a/src/Symfony/Component/Cache/CacheItem.php +++ b/src/Symfony/Component/Cache/CacheItem.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Cache; use Psr\Cache\CacheItemInterface; use Psr\Log\LoggerInterface; use Symfony\Component\Cache\Exception\InvalidArgumentException; +use Symfony\Component\Cache\Exception\LogicException; /** * @author Nicolas Grekas @@ -29,6 +30,7 @@ final class CacheItem implements CacheItemInterface protected $prevTags = array(); protected $innerItem; protected $poolHash; + protected $isTaggable = false; /** * {@inheritdoc} @@ -109,7 +111,10 @@ final class CacheItem implements CacheItemInterface */ public function tag($tags) { - if (!\is_array($tags)) { + if (!$this->isTaggable) { + throw new LogicException(sprintf('Cache item "%s" comes from a non tag-aware pool: you cannot tag it.', $this->key)); + } + if (!\is_iterable($tags)) { $tags = array($tags); } foreach ($tags as $tag) { diff --git a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php index 91763e5a9f..5f29bfe5f2 100644 --- a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php +++ b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php @@ -121,7 +121,15 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter foreach ($calls as $call) { ++$statistics[$name]['calls']; $statistics[$name]['time'] += $call->end - $call->start; - if ('getItem' === $call->name) { + if ('get' === $call->name) { + ++$statistics[$name]['reads']; + if ($call->hits) { + ++$statistics[$name]['hits']; + } else { + ++$statistics[$name]['misses']; + ++$statistics[$name]['writes']; + } + } elseif ('getItem' === $call->name) { ++$statistics[$name]['reads']; if ($call->hits) { ++$statistics[$name]['hits']; diff --git a/src/Symfony/Component/Cache/Exception/LogicException.php b/src/Symfony/Component/Cache/Exception/LogicException.php new file mode 100644 index 0000000000..042f73e6a5 --- /dev/null +++ b/src/Symfony/Component/Cache/Exception/LogicException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Exception; + +use Psr\Cache\InvalidArgumentException as Psr6CacheInterface; +use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInterface; + +class LogicException extends \LogicException implements Psr6CacheInterface, SimpleCacheInterface +{ +} diff --git a/src/Symfony/Component/Cache/TaggableCacheInterface.php b/src/Symfony/Component/Cache/TaggableCacheInterface.php new file mode 100644 index 0000000000..c112e72586 --- /dev/null +++ b/src/Symfony/Component/Cache/TaggableCacheInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache; + +/** + * Gets and stores items from a tag-aware cache. + * + * On cache misses, a callback is called that should return the missing value. + * It is given two arguments: + * - the missing cache key + * - the corresponding Symfony CacheItem object, + * allowing time-based *and* tags-based expiration control + * + * If you don't need tags-based invalidation, use CacheInterface instead. + * + * @author Nicolas Grekas + */ +interface TaggableCacheInterface extends CacheInterface +{ + /** + * @param callable(CacheItem):mixed $callback Should return the computed value for the given key/item + * + * @return mixed The value corresponding to the provided key + */ + public function get(string $key, callable $callback); +} diff --git a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php index 018d149467..3c96b731cc 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/AdapterTestCase.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Cache\Tests\Adapter; use Cache\IntegrationTests\CachePoolTest; use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Cache\CacheItem; use Symfony\Component\Cache\PruneableInterface; abstract class AdapterTestCase extends CachePoolTest @@ -26,6 +27,26 @@ abstract class AdapterTestCase extends CachePoolTest } } + public function testGet() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $cache = $this->createCachePool(); + + $value = mt_rand(); + + $this->assertSame($value, $cache->get('foo', function (CacheItem $item) use ($value) { + $this->assertSame('foo', $item->getKey()); + + return $value; + })); + + $item = $cache->getItem('foo'); + $this->assertSame($value, $item->get()); + } + public function testDefaultLifeTime() { if (isset($this->skippedTests[__FUNCTION__])) { diff --git a/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php b/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php index 14b61263c5..8630b52cf3 100644 --- a/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php +++ b/src/Symfony/Component/Cache/Tests/Adapter/PhpArrayAdapterTest.php @@ -21,6 +21,7 @@ use Symfony\Component\Cache\Adapter\PhpArrayAdapter; class PhpArrayAdapterTest extends AdapterTestCase { protected $skippedTests = array( + 'testGet' => 'PhpArrayAdapter is read-only.', 'testBasicUsage' => 'PhpArrayAdapter is read-only.', 'testBasicUsageWithLongKey' => 'PhpArrayAdapter is read-only.', 'testClear' => 'PhpArrayAdapter is read-only.', diff --git a/src/Symfony/Component/Cache/Tests/CacheItemTest.php b/src/Symfony/Component/Cache/Tests/CacheItemTest.php index daca925fd5..3a0ea098ad 100644 --- a/src/Symfony/Component/Cache/Tests/CacheItemTest.php +++ b/src/Symfony/Component/Cache/Tests/CacheItemTest.php @@ -55,6 +55,9 @@ class CacheItemTest extends TestCase public function testTag() { $item = new CacheItem(); + $r = new \ReflectionProperty($item, 'isTaggable'); + $r->setAccessible(true); + $r->setValue($item, true); $this->assertSame($item, $item->tag('foo')); $this->assertSame($item, $item->tag(array('bar', 'baz'))); @@ -72,6 +75,24 @@ class CacheItemTest extends TestCase public function testInvalidTag($tag) { $item = new CacheItem(); + $r = new \ReflectionProperty($item, 'isTaggable'); + $r->setAccessible(true); + $r->setValue($item, true); + $item->tag($tag); } + + /** + * @expectedException \Symfony\Component\Cache\Exception\LogicException + * @expectedExceptionMessage Cache item "foo" comes from a non tag-aware pool: you cannot tag it. + */ + public function testNonTaggableItem() + { + $item = new CacheItem(); + $r = new \ReflectionProperty($item, 'key'); + $r->setAccessible(true); + $r->setValue($item, 'foo'); + + $item->tag(array()); + } } diff --git a/src/Symfony/Component/Cache/Traits/GetTrait.php b/src/Symfony/Component/Cache/Traits/GetTrait.php new file mode 100644 index 0000000000..d2a5f92da2 --- /dev/null +++ b/src/Symfony/Component/Cache/Traits/GetTrait.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Cache\Traits; + +use Psr\Cache\CacheItemPoolInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +trait GetTrait +{ + /** + * {@inheritdoc} + */ + public function get(string $key, callable $callback) + { + return $this->doGet($this, $key, $callback); + } + + private function doGet(CacheItemPoolInterface $pool, string $key, callable $callback) + { + $item = $pool->getItem($key); + + if ($item->isHit()) { + return $item->get(); + } + + $pool->save($item->set($value = $callback($item))); + + return $value; + } +}