Permalink
Browse files

feature #26929 [Cache] Add [Taggable]CacheInterface, the easiest way …

…to use a cache (nicolas-grekas)

This PR was merged into the 4.2-dev branch.

Discussion
----------

[Cache] Add [Taggable]CacheInterface, the easiest way to use a cache

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #25903
| License       | MIT
| Doc PR        | -

This feature is a no-brainer, yet it provides a wonderful DX when using a cache:

by type-hinting the new `CacheInterface` or  `TaggableCacheInterface`, you get access to:

```php
public function get(string $key, callable $callback);
```

`$callback` is called when `$key` is not found in the cache pool.
It is given one arguments: a `CacheItemInterface $item` (a `CacheItem` for a `TaggableCacheInterface`), and should return the corresponding value.

```php
$value = $cache->get($key, function (CacheItemInterface $item) {
    $item->expiresAfter(3600);
    return $this->computeValue();
});
```

or for tags, on a `TaggableCacheInterface $cache`:
```php
$value = $cache->get($key, function (CacheItem $item) {
    $item->tag('foo_tag');
    return $this->computeValue();
});
```

Plain simple, I love it, why didn't we have the idea earlier, isn't it ?! :)

Commits
-------

589ff69 [Cache] Add [Taggable]CacheInterface, the easiest way to use a cache
  • Loading branch information...
nicolas-grekas committed May 21, 2018
2 parents 021531b + 589ff69 commit bd6769e39101cb7d7489d080ff9bb8b56f54640c
@@ -15,6 +15,10 @@
<argument type="service" id="cache.app" />
</service>
<service id="cache.app.taggable" class="Symfony\Component\Cache\Adapter\TagAwareAdapter">
<argument type="service" id="cache.app" />
</service>
<service id="cache.system" parent="cache.adapter.system" public="true">
<tag name="cache.pool" />
</service>
@@ -122,7 +126,9 @@
<service id="cache.global_clearer" parent="cache.default_clearer" public="true" />
<service id="cache.app_clearer" alias="cache.default_clearer" public="true" />
<service id="Psr\Cache\CacheItemPoolInterface" alias="cache.app" />
<service id="Symfony\Component\Cache\TaggableCacheInterface" alias="cache.app.taggable" />
<service id="Psr\SimpleCache\CacheInterface" alias="cache.app.simple" />
<service id="Symfony\Component\Cache\Adapter\AdapterInterface" alias="cache.app" />
<service id="Symfony\Component\Cache\CacheInterface" alias="cache.app" />
</services>
</container>
@@ -15,17 +15,20 @@
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 <p@tchwork.com>
*/
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;
@@ -13,16 +13,19 @@
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 <p@tchwork.com>
*/
class ArrayAdapter implements AdapterInterface, LoggerAwareInterface, ResettableInterface
class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
{
use ArrayTrait;
use GetTrait;
private $createCacheItem;
@@ -13,10 +13,12 @@
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 @@
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
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 @@ function ($sourceItem, $item) use ($defaultLifetime) {
$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 @@ function ($sourceItem, $item) use ($defaultLifetime) {
);
}
/**
* {@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}
*/
@@ -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 <galopintitouan@gmail.com>
*/
class NullAdapter implements AdapterInterface
class NullAdapter implements AdapterInterface, CacheInterface
{
use GetTrait;
private $createCacheItem;
public function __construct()
@@ -13,10 +13,12 @@
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 @@
* @author Titouan Galopin <galopintitouan@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class PhpArrayAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
{
use PhpArrayTrait;
use GetTrait;
private $createCacheItem;
@@ -77,6 +80,31 @@ public static function create($file, CacheItemPoolInterface $fallbackPool)
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}
*/
@@ -13,17 +13,20 @@
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 <p@tchwork.com>
*/
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 @@ function ($key, $innerItem) use ($defaultLifetime, $poolHash) {
);
}
/**
* {@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}
*/
@@ -16,16 +16,19 @@
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 <p@tchwork.com>
*/
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 @@ function ($key, $value, CacheItem $protoItem) {
);
$this->setCacheItemTags = \Closure::bind(
function (CacheItem $item, $key, array &$itemTags) {
$item->isTaggable = true;
if (!$item->isHit) {
return $item;
}
@@ -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 @@
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
class TraceableAdapter implements AdapterInterface, PruneableInterface, ResettableInterface
class TraceableAdapter implements AdapterInterface, CacheInterface, PruneableInterface, ResettableInterface
{
protected $pool;
private $calls = array();
@@ -32,6 +34,38 @@ public function __construct(AdapterInterface $pool)
$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}
*/
@@ -11,10 +11,12 @@
namespace Symfony\Component\Cache\Adapter;
use Symfony\Component\Cache\TaggableCacheInterface;
/**
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class TraceableTagAwareAdapter extends TraceableAdapter implements TagAwareAdapterInterface
class TraceableTagAwareAdapter extends TraceableAdapter implements TaggableCacheInterface, TagAwareAdapterInterface
{
public function __construct(TagAwareAdapterInterface $pool)
{
@@ -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
-----
Oops, something went wrong.

0 comments on commit bd6769e

Please sign in to comment.