Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8cc7f94

Browse files
[Cache] Add tags based invalidation + RedisTaggedAdapter
1 parent 8fac3c0 commit 8cc7f94

11 files changed

+596
-7
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,13 @@ protected function doSave(array $values, $lifetime)
249249
return $failed;
250250
}
251251

252-
private function execute($command, $id, array $args, $redis = null)
252+
protected function execute($command, $id, array $args = array(), $redis = null)
253253
{
254254
array_unshift($args, $id);
255255
call_user_func_array(array($redis ?: $this->redis, $command), $args);
256256
}
257257

258-
private function pipeline(\Closure $callback)
258+
protected function pipeline(\Closure $callback)
259259
{
260260
$redis = $this->redis;
261261

@@ -267,7 +267,7 @@ private function pipeline(\Closure $callback)
267267
});
268268
} elseif ($redis instanceof \RedisArray) {
269269
$connections = array();
270-
$callback(function ($command, $id, $args) use (&$connections) {
270+
$callback(function ($command, $id, $args = array()) use (&$connections) {
271271
if (!isset($connections[$h = $this->redis->_target($id)])) {
272272
$connections[$h] = $this->redis->_instance($h);
273273
$connections[$h]->multi(\Redis::PIPELINE);
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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 Symfony\Component\Cache\CacheItem;
15+
16+
/**
17+
* @author Nicolas Grekas <[email protected]>
18+
*/
19+
class RedisTaggedAdapter extends RedisAdapter implements TagsInvalidatingAdapterInterface
20+
{
21+
use TagsInvalidatingAdapterTrait;
22+
23+
private $redis;
24+
private $namespace;
25+
26+
/**
27+
* @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient
28+
*/
29+
public function __construct($redisClient, $namespace = '', $defaultLifetime = 0, AdapterInterface $adapter = null)
30+
{
31+
parent::__construct($redisClient, $namespace, $defaultLifetime);
32+
$this->redis = $redisClient;
33+
$this->namespace = $namespace;
34+
$this->adapter = $adapter;
35+
$this->init($defaultLifetime);
36+
}
37+
38+
/**
39+
* {@inheritdoc}
40+
*/
41+
public function hasItem($key)
42+
{
43+
if ($this->deferred) {
44+
$this->commit();
45+
}
46+
if (null === $this->adapter ? !parent::hasItem($key) : !$this->adapter->hasItem($key)) {
47+
return false;
48+
}
49+
50+
$tags = array();
51+
52+
foreach ($this->redis->hGetAll($this->namespace.$key.':tags') as $tag => $version) {
53+
$tags[$this->namespace.'tag:'.$tag][$version] = $key;
54+
}
55+
if ($tags) {
56+
foreach ($this->redis->mGet(array_keys($tags)) as $tag => $version) {
57+
unset($tags[$tag][(int) $version]);
58+
}
59+
}
60+
61+
return !$tags;
62+
}
63+
64+
/**
65+
* {@inheritdoc}
66+
*/
67+
public function getItems(array $keys = array())
68+
{
69+
if ($this->deferred) {
70+
$this->commit();
71+
}
72+
$tags = $invalids = array();
73+
74+
foreach ($keys as $i => $key) {
75+
CacheItem::validateKey($key);
76+
77+
foreach ($this->redis->hGetAll($this->namespace.$key.':tags') as $tag => $version) {
78+
$tags[$this->namespace.'tag:'.$tag][$version][$i] = $key;
79+
}
80+
}
81+
if ($tags) {
82+
$i = 0;
83+
$versions = $this->redis->mGet(array_keys($tags));
84+
85+
foreach ($tags as $tag => $version) {
86+
$version = $versions[$i++];
87+
unset($tags[$tag][(int) $version]);
88+
89+
foreach ($tags[$tag] as $version) {
90+
foreach ($version as $j => $key) {
91+
$invalids[] = $key;
92+
unset($keys[$j]);
93+
}
94+
}
95+
}
96+
}
97+
$items = null === $this->adapter ? parent::getItems($keys) : $this->adapter->getItems($keys);
98+
99+
return $this->generateItems($items, $invalids);
100+
}
101+
102+
/**
103+
* {@inheritdoc}
104+
*/
105+
public function invalidateTags($tags)
106+
{
107+
if (!is_array($tags)) {
108+
$tags = array($tags);
109+
}
110+
$this->pipeline(function ($pipe) use ($tags) {
111+
foreach ($tags as $tag) {
112+
CacheItem::validateKey($tag);
113+
$pipe('incr', $this->namespace.'tag:'.$tag);
114+
}
115+
});
116+
117+
return true;
118+
}
119+
120+
private function doSaveTags(array $tagsByKey)
121+
{
122+
$tagVersions = array();
123+
124+
foreach ($tagsByKey as $key => $tags) {
125+
foreach ($tags as $tag) {
126+
$tagVersions[$tag] = $this->namespace.'tag:'.$tag;
127+
}
128+
}
129+
130+
if ($tagVersions) {
131+
$tagVersions = array_combine(array_keys($tagVersions), $this->redis->mGet($tagVersions));
132+
$tagVersions = array_map('intval', $tagVersions);
133+
}
134+
135+
$this->pipeline(function ($pipe) use ($tagsByKey, $tagVersions) {
136+
foreach ($tagsByKey as $key => $tags) {
137+
$pipe('del', $this->namespace.$key.':tags');
138+
if ($tags) {
139+
foreach (array_intersect_key($tagVersions, $tags) as $tag => $version) {
140+
$pipe('hSet', $this->namespace.$key.':tags', array($tag, $version));
141+
}
142+
}
143+
}
144+
});
145+
146+
return true;
147+
}
148+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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 Psr\Cache\InvalidArgumentException;
16+
17+
/**
18+
* Interface for invalidating cached items using tags.
19+
*
20+
* @author Nicolas Grekas <[email protected]>
21+
*/
22+
interface TagsInvalidatingAdapterInterface extends CacheItemPoolInterface
23+
{
24+
/**
25+
* Invalidates cached items using tags.
26+
*
27+
* @param string|string[] $tags A tag or an array of tags to invalidate.
28+
*
29+
* @return bool True on success.
30+
*
31+
* @throws InvalidArgumentException When $tags is not valid.
32+
*/
33+
public function invalidateTags($tags);
34+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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 Symfony\Component\Cache\CacheItem;
16+
17+
/**
18+
* @author Nicolas Grekas <[email protected]>
19+
*/
20+
trait TagsInvalidatingAdapterTrait
21+
{
22+
private $adapter;
23+
private $deferred = array();
24+
private $createCacheItem;
25+
private $getTagsByKey;
26+
27+
/**
28+
* {@inheritdoc}
29+
*/
30+
public function getItem($key)
31+
{
32+
foreach ($this->getItems(array($key)) as $item) {
33+
}
34+
35+
return $item;
36+
}
37+
38+
/**
39+
* {@inheritdoc}
40+
*/
41+
public function clear()
42+
{
43+
$this->deferred = array();
44+
$cleared = parent::clear();
45+
46+
return null === $this->adapter ? $cleared : $this->adapter->clear();
47+
}
48+
49+
/**
50+
* {@inheritdoc}
51+
*/
52+
public function deleteItem($key)
53+
{
54+
return null === $this->adapter ? parent::deleteItem($key) : $this->adapter->deleteItem($key);
55+
}
56+
57+
/**
58+
* {@inheritdoc}
59+
*/
60+
public function deleteItems(array $keys)
61+
{
62+
return null === $this->adapter ? parent::deleteItems($keys) : $this->adapter->deleteItems($keys);
63+
}
64+
65+
/**
66+
* {@inheritdoc}
67+
*/
68+
public function save(CacheItemInterface $item)
69+
{
70+
if (!$item instanceof CacheItem) {
71+
return false;
72+
}
73+
if ($this->deferred) {
74+
$this->commit();
75+
}
76+
$this->deferred[$item->getKey()] = $item;
77+
78+
return $this->commit();
79+
}
80+
81+
/**
82+
* {@inheritdoc}
83+
*/
84+
public function saveDeferred(CacheItemInterface $item)
85+
{
86+
if (!$item instanceof CacheItem) {
87+
return false;
88+
}
89+
$this->deferred[$item->getKey()] = $item;
90+
91+
return true;
92+
}
93+
94+
/**
95+
* {@inheritdoc}
96+
*/
97+
public function commit()
98+
{
99+
$ok = true;
100+
101+
if ($this->deferred) {
102+
foreach ($this->deferred as $key => $item) {
103+
if (null === $this->adapter ? !parent::saveDeferred($item) : !$this->adapter->saveDeferred($item)) {
104+
unset($this->deferred[$key]);
105+
$ok = false;
106+
}
107+
}
108+
$f = $this->getTagsByKey;
109+
$ok = $this->doSaveTags($f($this->deferred)) && $ok;
110+
$this->deferred = array();
111+
}
112+
113+
return (null === $this->adapter ? parent::commit() : $this->adapter->commit()) && $ok;
114+
}
115+
116+
public function __destruct()
117+
{
118+
if ($this->deferred) {
119+
$this->commit();
120+
}
121+
}
122+
123+
private function init($defaultLifetime)
124+
{
125+
$this->createCacheItem = \Closure::bind(
126+
function ($key) use ($defaultLifetime) {
127+
$item = new CacheItem();
128+
$item->key = $key;
129+
$item->isHit = false;
130+
$item->defaultLifetime = $defaultLifetime;
131+
132+
return $item;
133+
},
134+
null,
135+
CacheItem::class
136+
);
137+
$this->getTagsByKey = \Closure::bind(
138+
function ($deferred) {
139+
$tagsByKey = array();
140+
foreach ($deferred as $key => $item) {
141+
$tagsByKey[$key] = $item->tags;
142+
}
143+
144+
return $tagsByKey;
145+
},
146+
null,
147+
CacheItem::class
148+
);
149+
}
150+
151+
private function generateItems($items, $invalids)
152+
{
153+
foreach ($items as $key => $item) {
154+
yield $key => $item;
155+
}
156+
157+
$f = $this->createCacheItem;
158+
159+
foreach ($invalids as $key) {
160+
yield $key => $f($key);
161+
}
162+
}
163+
}

0 commit comments

Comments
 (0)