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

Skip to content

Commit d692648

Browse files
[Cache] Add hierachical tags based invalidation
1 parent 5ca5666 commit d692648

10 files changed

+546
-22
lines changed

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

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,25 @@ function ($key, $value, $isHit) use ($defaultLifetime) {
4545
CacheItem::class
4646
);
4747
$this->mergeByLifetime = \Closure::bind(
48-
function ($deferred, $namespace, &$expiredIds) {
48+
function ($deferred, $namespace, &$expiredIds, &$tags) {
4949
$byLifetime = array();
5050
$now = time();
5151
$expiredIds = array();
52+
$tags = array();
5253

5354
foreach ($deferred as $key => $item) {
55+
$id = $namespace.$key;
56+
5457
if (null === $item->expiry) {
55-
$byLifetime[0][$namespace.$key] = $item->value;
58+
$byLifetime[0][$id] = $item->value;
5659
} elseif ($item->expiry > $now) {
57-
$byLifetime[$item->expiry - $now][$namespace.$key] = $item->value;
60+
$byLifetime[$item->expiry - $now][$id] = $item->value;
5861
} else {
59-
$expiredIds[] = $namespace.$key;
62+
$expiredIds[] = $id;
63+
continue;
64+
}
65+
foreach ($item->tags as $tag) {
66+
$tags[$tag][$id] = $id;
6067
}
6168
}
6269

@@ -113,6 +120,23 @@ abstract protected function doDelete(array $ids);
113120
*/
114121
abstract protected function doSave(array $values, $lifetime);
115122

123+
/**
124+
* Adds hierarchical tags to a set of cache keys.
125+
*
126+
* @param array $tags The tags as keys, the set of corresponding identifiers as values.
127+
*
128+
* @return bool True if the tags were successfully stored, false otherwise.
129+
*/
130+
protected function doTag(array $tags)
131+
{
132+
if ($this instanceof TagInvalidationInterface) {
133+
throw new \LogicException(sprintf('Class "%s" must overwrite the AbstractAdapter::doTag() method to implement TagInvalidationInterface', get_class($this)));
134+
}
135+
CacheItem::log($this->logger, 'Failed to commit cache tags: {adapter} does not implement TagInvalidationInterface', array('adapter' => get_class($this)));
136+
137+
return false;
138+
}
139+
116140
/**
117141
* {@inheritdoc}
118142
*/
@@ -279,7 +303,7 @@ public function commit()
279303
{
280304
$ok = true;
281305
$byLifetime = $this->mergeByLifetime;
282-
$byLifetime = $byLifetime($this->deferred, $this->namespace, $expiredIds);
306+
$byLifetime = $byLifetime($this->deferred, $this->namespace, $expiredIds, $tags);
283307
$retry = $this->deferred = array();
284308

285309
if ($expiredIds) {
@@ -321,6 +345,22 @@ public function commit()
321345
$ok = false;
322346
$type = is_object($v) ? get_class($v) : gettype($v);
323347
CacheItem::log($this->logger, 'Failed to save key "{key}" ({type})', array('key' => substr($id, strlen($this->namespace)), 'type' => $type, 'exception' => $e instanceof \Exception ? $e : null));
348+
349+
foreach ($tags as $tag => $ids) {
350+
unset($tags[$tag][$id]);
351+
}
352+
}
353+
}
354+
355+
if ($tags) {
356+
try {
357+
$ok = $this->doTag($tags) && $ok;
358+
} catch (\Exception $e) {
359+
if (!$this instanceof TagInvalidationInterface) {
360+
throw $e;
361+
}
362+
$ok = false;
363+
CacheItem::log($this->logger, 'Failed to commit cache tags', array('exception' => $e));
324364
}
325365
}
326366

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

Lines changed: 120 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@
1111

1212
namespace Symfony\Component\Cache\Adapter;
1313

14+
use Symfony\Component\Cache\CacheItem;
1415
use Symfony\Component\Cache\Exception\InvalidArgumentException;
1516

1617
/**
1718
* @author Nicolas Grekas <[email protected]>
1819
*/
19-
class FilesystemAdapter extends AbstractAdapter
20+
class FilesystemAdapter extends AbstractAdapter implements TagInvalidationInterface
2021
{
2122
private $directory;
2223

@@ -50,6 +51,23 @@ public function __construct($namespace = '', $defaultLifetime = 0, $directory =
5051
$this->directory = $dir;
5152
}
5253

54+
/**
55+
* {@inheritdoc}
56+
*/
57+
public function invalidate($tags)
58+
{
59+
$ok = true;
60+
61+
foreach (CacheItem::normalizeTags($tags) as $tag) {
62+
foreach ($this->getInvalidatedIds($tag) as $id) {
63+
$file = $this->getFile($id, false);
64+
$ok = (!file_exists($file) || @unlink($file) || !file_exists($file)) && $ok;
65+
}
66+
}
67+
68+
return $ok;
69+
}
70+
5371
/**
5472
* {@inheritdoc}
5573
*/
@@ -59,7 +77,7 @@ protected function doFetch(array $ids)
5977
$now = time();
6078

6179
foreach ($ids as $id) {
62-
$file = $this->getFile($id);
80+
$file = $this->getFile($id, false);
6381
if (!$h = @fopen($file, 'rb')) {
6482
continue;
6583
}
@@ -89,7 +107,7 @@ protected function doFetch(array $ids)
89107
*/
90108
protected function doHave($id)
91109
{
92-
$file = $this->getFile($id);
110+
$file = $this->getFile($id, false);
93111

94112
return file_exists($file) && (@filemtime($file) > time() || $this->doFetch(array($id)));
95113
}
@@ -116,7 +134,7 @@ protected function doDelete(array $ids)
116134
$ok = true;
117135

118136
foreach ($ids as $id) {
119-
$file = $this->getFile($id);
137+
$file = $this->getFile($id, false);
120138
$ok = (!file_exists($file) || @unlink($file) || !file_exists($file)) && $ok;
121139
}
122140

@@ -130,28 +148,120 @@ protected function doSave(array $values, $lifetime)
130148
{
131149
$ok = true;
132150
$expiresAt = $lifetime ? time() + $lifetime : PHP_INT_MAX;
151+
$tags = array();
133152

134153
foreach ($values as $id => $value) {
135-
$file = $this->getFile($id);
136-
$dir = dirname($file);
137-
if (!file_exists($dir)) {
138-
@mkdir($dir, 0777, true);
154+
if (file_exists($file = $this->getFile($id.':tag', false))) {
155+
$tags = file($file, FILE_IGNORE_NEW_LINES);
156+
@unlink($file);
139157
}
158+
$file = $this->getFile($id, true);
140159
$value = $expiresAt."\n".rawurlencode($id)."\n".serialize($value);
141160
if (false !== @file_put_contents($file, $value, LOCK_EX)) {
142161
@touch($file, $expiresAt);
143162
} else {
144163
$ok = false;
145164
}
165+
if ($tags) {
166+
$this->removeTags($id, $tags);
167+
$tags = array();
168+
}
169+
}
170+
171+
return $ok;
172+
}
173+
174+
/**
175+
* {@inheritdoc}
176+
*/
177+
protected function doTag(array $tags)
178+
{
179+
$ok = true;
180+
$byIds = array();
181+
182+
foreach ($tags as $tag => $ids) {
183+
$file = $this->getFile($tag, true);
184+
$h = fopen($file, 'ab');
185+
186+
foreach ($ids as $id) {
187+
$ok = fwrite($h, rawurlencode($id)."\n") && $ok;
188+
$byIds[$id][] = $tag;
189+
}
190+
fclose($h);
191+
}
192+
foreach ($byIds as $id => $tags) {
193+
$file = $this->getFile($id.':tag', true);
194+
$h = fopen($file, 'ab');
195+
196+
foreach ($tags as $tag) {
197+
fwrite($h, rawurlencode($tag)."\n");
198+
}
199+
fclose($h);
200+
}
201+
while (0 < $r = strrpos($tag, '/')) {
202+
$parent = substr($tag, 0, $r);
203+
$ok = file_put_contents($this->getFile($parent, true), rawurlencode($tag)."\n", FILE_APPEND) && $ok;
204+
$tag = $parent;
146205
}
147206

148207
return $ok;
149208
}
150209

151-
private function getFile($id)
210+
private function getInvalidatedIds($tag)
211+
{
212+
$file = $this->getFile($tag, false);
213+
214+
if ($h = @fopen($file, 'rb')) {
215+
while (false !== $id = fgets($h)) {
216+
if ('!' === $id[0]) {
217+
continue;
218+
}
219+
$id = rawurldecode(substr($id, 0, -1));
220+
221+
if ('/' === $id[0]) {
222+
foreach ($this->getInvalidatedIds($id) as $id) {
223+
yield $id;
224+
}
225+
} else {
226+
yield $id;
227+
}
228+
}
229+
230+
fclose($h);
231+
@unlink($file);
232+
}
233+
}
234+
235+
private function removeTags($id, $tags)
236+
{
237+
$idLine = rawurlencode($id)."\n";
238+
$idSeek = -strlen($idLine);
239+
240+
foreach ($tags as $tag) {
241+
if (!file_exists($file = $this->getFile(rawurldecode($tag), false))) {
242+
continue;
243+
}
244+
$h = fopen($file, 'r+b');
245+
246+
while (false !== $line = fgets($h)) {
247+
if ($line === $idLine) {
248+
fseek($h, $idSeek, SEEK_CUR);
249+
fwrite($h, '!');
250+
}
251+
}
252+
fclose($h);
253+
}
254+
}
255+
256+
private function getFile($id, $mkdir)
152257
{
153258
$hash = str_replace('/', '-', base64_encode(md5($id, true)));
259+
$dir = $this->directory.$hash[0].DIRECTORY_SEPARATOR.$hash[1].DIRECTORY_SEPARATOR;
260+
261+
if ($mkdir && !file_exists($dir)) {
262+
@mkdir($dir, 0777, true);
263+
}
154264

155-
return $this->directory.$hash[0].DIRECTORY_SEPARATOR.$hash[1].DIRECTORY_SEPARATOR.substr($hash, 2, -2);
265+
return $dir.substr($hash, 2, -2);
156266
}
157267
}

0 commit comments

Comments
 (0)