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

Skip to content

Commit 2f7f91e

Browse files
[Cache] serialize tags separately from values in AbstractTagAwareAdapter
1 parent 6e7f325 commit 2f7f91e

File tree

7 files changed

+130
-13
lines changed

7 files changed

+130
-13
lines changed

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

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Psr\Log\LoggerAwareInterface;
1515
use Symfony\Component\Cache\CacheItem;
1616
use Symfony\Component\Cache\Exception\InvalidArgumentException;
17+
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
1718
use Symfony\Component\Cache\ResettableInterface;
1819
use Symfony\Component\Cache\Traits\AbstractAdapterTrait;
1920
use Symfony\Component\Cache\Traits\ContractsTrait;
@@ -37,14 +38,17 @@ abstract class AbstractTagAwareAdapter implements TagAwareAdapterInterface, TagA
3738

3839
private const TAGS_PREFIX = "\0tags\0";
3940

40-
protected function __construct(string $namespace = '', int $defaultLifetime = 0)
41+
private $marshaller;
42+
43+
protected function __construct(string $namespace, int $defaultLifetime, MarshallerInterface $marshaller)
4144
{
45+
$this->marshaller = $marshaller;
4246
$this->namespace = '' === $namespace ? '' : CacheItem::validateKey($namespace).':';
4347
if (null !== $this->maxIdLength && \strlen($namespace) > $this->maxIdLength - 24) {
4448
throw new InvalidArgumentException(sprintf('Namespace must be %d chars max, %d given ("%s")', $this->maxIdLength - 24, \strlen($namespace), $namespace));
4549
}
4650
$this->createCacheItem = \Closure::bind(
47-
static function ($key, $value, $isHit) use ($defaultLifetime) {
51+
static function ($key, $value, $isHit) use ($defaultLifetime, $marshaller) {
4852
$item = new CacheItem();
4953
$item->key = $key;
5054
$item->defaultLifetime = $defaultLifetime;
@@ -53,6 +57,10 @@ static function ($key, $value, $isHit) use ($defaultLifetime) {
5357
if (!\is_array($value) || !\array_key_exists('value', $value)) {
5458
return $item;
5559
}
60+
if (\is_string($value['tags'] ?? null)) {
61+
$value['value'] = $marshaller->unmarshall($value['value']);
62+
$value['tags'] = '' === $value['tags'] ? [] : $marshaller->unmarshall($value['tags']);
63+
}
5664
$item->isHit = $isHit;
5765
// Extract value, tags and meta data from the cache value
5866
$item->value = $value['value'];
@@ -72,7 +80,7 @@ static function ($key, $value, $isHit) use ($defaultLifetime) {
7280
$getId = \Closure::fromCallable([$this, 'getId']);
7381
$tagPrefix = self::TAGS_PREFIX;
7482
$this->mergeByLifetime = \Closure::bind(
75-
static function ($deferred, &$expiredIds) use ($getId, $tagPrefix) {
83+
static function ($deferred, &$expiredIds) use ($getId, $tagPrefix, $marshaller) {
7684
$byLifetime = [];
7785
$now = microtime(true);
7886
$expiredIds = [];
@@ -93,6 +101,17 @@ static function ($deferred, &$expiredIds) use ($getId, $tagPrefix) {
93101
$value = ['value' => $item->value, 'tags' => []];
94102
}
95103

104+
$newTags = $value['tags'];
105+
$value = $marshaller->marshall($value, $failed);
106+
107+
if ([] === $newTags) {
108+
$value['tags'] = '';
109+
}
110+
111+
if ($failed) {
112+
$value['fail'] = $item->value;
113+
}
114+
96115
if ($metadata) {
97116
// For compactness, expiry and creation duration are packed, using magic numbers as separators
98117
$value['meta'] = pack('VN', (int) (0.1 + $metadata[self::METADATA_EXPIRY] - self::METADATA_EXPIRY_OFFSET), $metadata[self::METADATA_CTIME]);
@@ -101,10 +120,10 @@ static function ($deferred, &$expiredIds) use ($getId, $tagPrefix) {
101120
// Extract tag changes, these should be removed from values in doSave()
102121
$value['tag-operations'] = ['add' => [], 'remove' => []];
103122
$oldTags = $item->metadata[CacheItem::METADATA_TAGS] ?? [];
104-
foreach (array_diff($value['tags'], $oldTags) as $addedTag) {
123+
foreach (array_diff($newTags, $oldTags) as $addedTag) {
105124
$value['tag-operations']['add'][] = $getId($tagPrefix.$addedTag);
106125
}
107-
foreach (array_diff($oldTags, $value['tags']) as $removedTag) {
126+
foreach (array_diff($oldTags, $newTags) as $removedTag) {
108127
$value['tag-operations']['remove'][] = $getId($tagPrefix.$removedTag);
109128
}
110129

@@ -149,6 +168,20 @@ abstract protected function doDelete(array $ids, array $tagData = []): bool;
149168
*/
150169
abstract protected function doInvalidate(array $tagIds): bool;
151170

171+
/**
172+
* Returns the tags bound to the provided ids.
173+
*/
174+
protected function doFetchTags(array $ids): iterable
175+
{
176+
foreach ($this->doFetch($ids) as $id => $value) {
177+
if (\is_string($value['tags'] ?? null)) {
178+
$value['tags'] = '' === $value['tags'] ? [] : $this->marshaller->unmarshall($value['tags']);
179+
}
180+
181+
yield $id => $value['tags'] ?? [];
182+
}
183+
}
184+
152185
/**
153186
* {@inheritdoc}
154187
*
@@ -233,8 +266,8 @@ public function deleteItems(array $keys)
233266
}
234267

235268
try {
236-
foreach ($this->doFetch($ids) as $id => $value) {
237-
foreach ($value['tags'] ?? [] as $tag) {
269+
foreach ($this->doFetchTags($ids) as $id => $tags) {
270+
foreach ($tags as $tag) {
238271
$tagData[$this->getId(self::TAGS_PREFIX.$tag)][] = $id;
239272
}
240273
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
1515
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
16+
use Symfony\Component\Cache\Marshaller\TagAwareMarshaller;
1617
use Symfony\Component\Cache\PruneableInterface;
1718
use Symfony\Component\Cache\Traits\FilesystemTrait;
1819

@@ -37,8 +38,8 @@ class FilesystemTagAwareAdapter extends AbstractTagAwareAdapter implements Prune
3738

3839
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
3940
{
40-
$this->marshaller = $marshaller ?? new DefaultMarshaller();
41-
parent::__construct('', $defaultLifetime);
41+
$this->marshaller = new TagAwareMarshaller(new DefaultMarshaller());
42+
parent::__construct('', $defaultLifetime, $marshaller ?? new DefaultMarshaller());
4243
$this->init($namespace, $directory);
4344
}
4445

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
use Predis\Connection\Aggregate\PredisCluster;
1616
use Predis\Response\Status;
1717
use Symfony\Component\Cache\Exception\InvalidArgumentException;
18+
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
1819
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
20+
use Symfony\Component\Cache\Marshaller\TagAwareMarshaller;
1921
use Symfony\Component\Cache\Traits\RedisTrait;
2022

2123
/**
@@ -67,7 +69,9 @@ public function __construct($redisClient, string $namespace = '', int $defaultLi
6769
throw new InvalidArgumentException(sprintf('Unsupported Predis cluster connection: only "%s" is, "%s" given.', PredisCluster::class, \get_class($redisClient->getConnection())));
6870
}
6971

70-
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
72+
parent::__construct($namespace, $defaultLifetime, $marshaller ?? new DefaultMarshaller());
73+
74+
$this->init($redisClient, $namespace, $defaultLifetime, new TagAwareMarshaller(new DefaultMarshaller()), false);
7175
}
7276

7377
/**

src/Symfony/Component/Cache/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ CHANGELOG
88
* added argument `$prefix` to `AdapterInterface::clear()`
99
* improved `RedisTagAwareAdapter` to support Redis server >= 2.8 and up to 4B items per tag
1010
* [BC BREAK] `RedisTagAwareAdapter` is not compatible with `RedisCluster` from `Predis` anymore, use `phpredis` instead
11+
* [BC BREAK] `AbstractTagAwareAdapter`'s contructor now mandates being called with 3 arguments
1112

1213
4.3.0
1314
-----
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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\Marshaller;
13+
14+
/**
15+
* A marshaller optimized for data structures generated by AbstractTagAwareAdapter.
16+
*
17+
* @author Nicolas Grekas <[email protected]>
18+
*
19+
* @internal
20+
*/
21+
class TagAwareMarshaller implements MarshallerInterface
22+
{
23+
private $marshaller;
24+
25+
public function __construct(MarshallerInterface $marshaller)
26+
{
27+
$this->marshaller = $marshaller;
28+
}
29+
30+
/**
31+
* {@inheritdoc}
32+
*/
33+
public function marshall(array $values, ?array &$failed): array
34+
{
35+
$failed = $notSerialized = $serialized = [];
36+
37+
foreach ($values as $id => $value) {
38+
if (\is_array($value) && \is_string($value['tags'] ?? null) && \is_string($value['value'] ?? null) && \count($value) === 2 + (\is_string($value['meta'] ?? null) && 8 === \strlen($value['meta']))) {
39+
// if the value is an array with keys "tags", "value" and "meta", use a compact serialization format
40+
// magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F allow detecting this format quickly in unmarshall()
41+
$serialized[$id] = "\x9D".($value['meta'] ?? "\0\0\0\0\0\0\0\0").pack('N', \strlen($value['tags'])).$value['tags'].$value['value'];
42+
$serialized[$id][9] = "\x5F";
43+
} else {
44+
// other arbitratry values are serialized using the decorated marshaller below
45+
$notSerialized[$id] = $value;
46+
}
47+
}
48+
49+
return $notSerialized ? $serialized + $this->marshaller->marshall($notSerialized, $failed) : $serialized;
50+
}
51+
52+
/**
53+
* {@inheritdoc}
54+
*/
55+
public function unmarshall(string $value)
56+
{
57+
// detect the compact format used in marshall() using magic numbers in the form 9D-..-..-..-..-00-..-..-..-5F
58+
if (13 >= \strlen($value) || "\x9D" !== $value[0] || "\0" !== $value[5] || "\x5F" !== $value[9]) {
59+
return $this->marshaller->unmarshall($value);
60+
}
61+
62+
// data consists of value, tags and metadata which we need to unpack
63+
$meta = substr($value, 1, 12);
64+
$meta[8] = "\0";
65+
$tagLen = unpack('Nlen', $meta, 8)['len'];
66+
$meta = substr($meta, 0, 8);
67+
68+
return [
69+
'value' => substr($value, 13 + $tagLen),
70+
'tags' => substr($value, 13, $tagLen),
71+
'meta' => "\0\0\0\0\0\0\0\0" === $meta ? null : $meta,
72+
];
73+
}
74+
}

src/Symfony/Component/Cache/Traits/AbstractAdapterTrait.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,13 @@ public function getItem($key)
5151
foreach ($this->doFetch([$id]) as $value) {
5252
$isHit = true;
5353
}
54+
55+
return $f($key, $value, $isHit);
5456
} catch (\Exception $e) {
5557
CacheItem::log($this->logger, 'Failed to fetch key "{key}": '.$e->getMessage(), ['key' => $key, 'exception' => $e]);
5658
}
5759

58-
return $f($key, $value, $isHit);
60+
return $f($key, null, false);
5961
}
6062

6163
/**

src/Symfony/Component/Cache/Traits/RedisTrait.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@ trait RedisTrait
4848
/**
4949
* @param \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface $redisClient
5050
*/
51-
private function init($redisClient, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller)
51+
private function init($redisClient, string $namespace, int $defaultLifetime, ?MarshallerInterface $marshaller, bool $callConstructor = true)
5252
{
53-
parent::__construct($namespace, $defaultLifetime);
53+
if ($callConstructor) {
54+
parent::__construct($namespace, $defaultLifetime);
55+
}
5456

5557
if (preg_match('#[^-+_.A-Za-z0-9]#', $namespace, $match)) {
5658
throw new InvalidArgumentException(sprintf('RedisAdapter namespace contains "%s" but only characters in [-+_.A-Za-z0-9] are allowed.', $match[0]));

0 commit comments

Comments
 (0)