8000 [Cache] Add `MarshallerInterface` allowing to change the serializer, … · symfony/symfony@523556c · GitHub
[go: up one dir, main page]

Skip to content

Commit 523556c

Browse files
[Cache] Add MarshallerInterface allowing to change the serializer, providing a default one that automatically uses igbinary when available
1 parent 72bf72a commit 523556c

24 files changed

+344
-69
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ before_install:
151151
tfold ext.libsodium tpecl libsodium sodium.so $INI
152152
tfold ext.mongodb tpecl mongodb-1.4.0RC1 mongodb.so $INI
153153
tfold ext.amqp tpecl amqp-1.9.3 amqp.so $INI
154+
tfold ext.igbinary tpecl igbinary-2.0.6 igbinary.so $INI
154155
fi
155156
156157
- |

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Symfony\Component\Cache\Adapter\AdapterInterface;
2424
use Symfony\Component\Cache\Adapter\ArrayAdapter;
2525
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
26+
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
2627
use Symfony\Component\Cache\ResettableInterface;
2728
use Symfony\Component\Config\FileLocator;
2829
use Symfony\Component\Config\Loader\LoaderInterface;
@@ -1536,6 +1537,10 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
15361537

15371538
private function registerCacheConfiguration(array $config, ContainerBuilder $container)
15381539
{
1540+
if (!class_exists(DefaultMarshaller::class)) {
1541+
$container->removeDefinition('cache.default_marshaller');
1542+
}
1543+
15391544
$version = new Parameter('container.build_id');
15401545
$container->getDefinition('cache.adapter.apcu')->replaceArgument(2, $version);
15411546
$container->getDefinition('cache.adapter.filesystem')->replaceArgument(2, $config['directory']);

src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
<argument /> <!-- namespace -->
7676
<argument>0</argument> <!-- default lifetime -->
7777
<argument>%kernel.cache_dir%/pools</argument>
78+
<argument type="service" id="cache.default_marshaller" on-invalid="ignore" />
7879
<call method="setLogger">
7980
<argument type="service" id="logger" on-invalid="ignore" />
8081
</call>
@@ -93,6 +94,7 @@
9394
<argument /> <!-- Redis connection service -->
9495
<argument /> <!-- namespace -->
9596
<argument>0</argument> <!-- default lifetime -->
97+
<argument type="service" id="cache.default_marshaller" on-invalid="ignore" />
9698
<call method="setLogger">
9799
<argument type="service" id="logger" on-invalid="ignore" />
98100
</call>
@@ -104,6 +106,7 @@
104106
<argument /> <!-- Memcached connection service -->
105107
<argument /> <!-- namespace -->
106108
<argument>0</argument> <!-- default lifetime -->
109+
<argument type="service" id="cache.default_marshaller" on-invalid="ignore" />
107110
<call method="setLogger">
108111
<argument type="service" id="logger" on-invalid="ignore" />
109112
</call>
@@ -118,6 +121,8 @@
118121
</call>
119122
</service>
120123

124+
<service id="cache.default_marshaller" class="Symfony\Component\Cache\Marshaller\DefaultMarshaller" />
125+
121126
<service id="cache.default_clearer" class="Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer">
122127
<argument type="collection" />
123128
</service>

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@
1111

1212
namespace Symfony\Component\Cache\Adapter;
1313

14+
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
15+
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
1416
use Symfony\Component\Cache\PruneableInterface;
1517
use Symfony\Component\Cache\Traits\FilesystemTrait;
1618

1719
class FilesystemAdapter extends AbstractAdapter implements PruneableInterface
1820
{
1921
use FilesystemTrait;
2022

21-
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null)
23+
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
2224
{
25+
$this->marshaller = $marshaller ?? new DefaultMarshaller();
2326
parent::__construct('', $defaultLifetime);
2427
$this->init($namespace, $directory);
2528
}

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

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

1212
namespace Symfony\Component\Cache\Adapter;
1313

14+
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
1415
use Symfony\Component\Cache\Traits\MemcachedTrait;
1516

1617
class MemcachedAdapter extends AbstractAdapter
@@ -29,8 +30,8 @@ class MemcachedAdapter extends AbstractAdapter
2930
*
3031
* Using a MemcachedAdapter as a pure items store is fine.
3132
*/
32-
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0)
33+
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
3334
{
34-
$this->init($client, $namespace, $defaultLifetime);
35+
$this->init($client, $namespace, $defaultLifetime, $marshaller);
3536
}
3637
}

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

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

1414
use Doctrine\DBAL\Connection;
1515
use Symfony\Component\Cache\Exception\InvalidArgumentException;
16+
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
1617
use Symfony\Component\Cache\PruneableInterface;
1718
use Symfony\Component\Cache\Traits\PdoTrait;
1819

@@ -43,8 +44,8 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface
4344
* @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
4445
* @throws InvalidArgumentException When namespace contains invalid characters
4546
*/
46-
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = array())
47+
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = array(), MarshallerInterface $marshaller = null)
4748
{
48-
$this->init($connOrDsn, $namespace, $defaultLifetime, $options);
49+
$this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
4950
}
5051
}

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\Cache\CacheInterface;
1717
use Symfony\Component\Cache\CacheItem;
1818
use Symfony\Component\Cache\Exception\InvalidArgumentException;
19+
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
1920
use Symfony\Component\Cache\PruneableInterface;
2021
use Symfony\Component\Cache\ResettableInterface;
2122
use Symfony\Component\Cache\Traits\GetTrait;
@@ -34,6 +35,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
3435
use GetTrait;
3536

3637
private $createCacheItem;
38+
private $marshaller;
3739

3840
/**
3941
* @param string $file The PHP file were values are cached
@@ -88,6 +90,7 @@ public function get(string $key, callable $callback, float $beta = null)
8890
$this->initialize();
8991
}
9092
if (!isset($this->keys[$key])) {
93+
get_from_pool:
9194
if ($this->pool instanceof CacheInterface) {
9295
return $this->pool->get($key, $callback, $beta);
9396
}
@@ -99,11 +102,16 @@ public function get(string $key, callable $callback, float $beta = null)
99102
if ('N;' === $value) {
100103
return null;
101104
}
102-
if ($value instanceof \Closure) {
103-
return $value();
104-
}
105-
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
106-
return unserialize($value);
105+
try {
106+
if ($value instanceof \Closure) {
107+
return $value();
108+
}
109+
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
110+
return ($this->marshaller ?? $this->marshaller = new DefaultMarshaller())->unmarshall($value);
111+
}
112+
} catch (\Throwable $e) {
113+
unset($this->keys[$key]);
114+
goto get_from_pool;
107115
}
108116

109117
return $value;
@@ -278,7 +286,7 @@ private function generateItems(array $keys): \Generator
278286
}
279287
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
280288
try {
281-
yield $key => $f($key, unserialize($value), true);
289+
yield $key => $f($key, $this->unserializeValue($value), true);
282290
} catch (\Throwable $e) {
283291
yield $key => $f($key, null, false);
284292
}

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

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

1212
namespace Symfony\Component\Cache\Adapter;
1313

14+
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
1415
use Symfony\Component\Cache\Traits\RedisTrait;
1516

1617
class RedisAdapter extends AbstractAdapter
@@ -22,8 +23,8 @@ class RedisAdapter extends AbstractAdapter
2223
* @param string $namespace The default namespace
2324
* @param int $defaultLifetime The default lifetime
2425
*/
25-
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0)
26+
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
2627
{
27-
$this->init($redisClient, $namespace, $defaultLifetime);
28+
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
2829
}
2930
}

src/Symfony/Component/Cache/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ CHANGELOG
44
4.2.0
55
-----
66

7+
* added `MarshallerInterface` and `DefaultMarshaller` to allow changing the serializer and provide one that automatically uses igbinary when available
78
* added `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache
89
* added sub-second expiry accuracy for backends that support it
910
* added support for phpredis 4 `compression` and `tcp_keepalive` options
1011
* throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool
1112
* deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead
1213
* deprecated the `AbstractAdapter::createSystemCache()` method
14+
* deprecated the `AbstractAdapter::unserialize()` and `AbstractCache::unserialize()` methods
< 10000 code>1315

1416
3.4.0
1517
-----
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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+
* Serializes/unserializes values using igbinary_serialize() if available, serialize() otherwise.
16+
*
17+
* @author Nicolas Grekas <p@tchwork.com>
18+
*/
19+
class DefaultMarshaller implements MarshallerInterface
20+
{
21+
private $allowIgbinarySerialize = true;
22+
23+
public function __construct(bool $allowIgbinarySerialize = true)
24+
{
25+
$this->allowIgbinarySerialize = $allowIgbinarySerialize;
26+
}
27+
28+
/**
29+
* {@inheritdoc}
30+
*/
31+
public function marshall(array $values, ?array &$failed): array
32+
{
33+
$serialized = $failed = array();
34+
35+
foreach ($values as $id => $value) {
36+
try {
37+
if ($this->allowIgbinarySerialize && \extension_loaded('igbinary')) {
38+
$serialized[$id] = igbinary_serialize($value);
39+
} else {
40+
$serialized[$id] = serialize($value);
41+
}
42+
} catch (\Exception $e) {
43+
$failed[] = $id;
44+
}
45+
}
46+
47+
return $serialized;
48+
}
49+
50+
/**
51+
* {@inheritdoc}
52+
*/
53+
public function unmarshall(string $value)
54+
{
55+
if ('b:0;' === $value) {
56+
return false;
57+
}
58+
if ('N;' === $value) {
59+
return null;
60+
}
61+
static $igbinaryNull;
62+
if ($value === ($igbinaryNull ?? $igbinaryNull = \extension_loaded('igbinary') ? igbinary_serialize(null) : false)) {
63+
return null;
64+
}
65+
$unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
66+
try {
67+
if (':' === ($value[1] ?? ':' 97AE ;)) {
68+
if (false !== $value = unserialize($value)) {
69+
return $value;
70+
}
71+
} elseif (false === $igbinaryNull) {
72+
throw new \RuntimeException('Failed to unserialize cached value, did you forget to install the "igbinary" extension?');
73+
} elseif (null !== $value = igbinary_unserialize($value)) {
74+
return $value;
75+
}
76+
77+
throw new \DomainException(error_get_last() ? error_get_last()['message'] : 'Failed to unserialize cached value');
78+
} catch (\Error $e) {
79+
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
80+
} finally {
81+
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
82+
}
83+
}
84+
85+
/**
86+
* @internal
87+
*/
88+
public static function handleUnserializeCallback($class)
89+
{
90+
throw new \DomainException('Class not found: '.$class);
91+
}
92+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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+
* Serializes/unserializes PHP values.
16+
*
17+
* Implementations of this interface MUST deal with errors carefuly.
18+
*
19+
* @author Nicolas Grekas <p@tchwork.com>
20+
*/
21+
interface MarshallerInterface
22+
{
23+
/**
24+
* Serializes a list of values.
25+
*
26+
* When serialization fails for a specific value, no exception should be
27+
* thrown. Instead, its key should be listed in $failed.
28+
*/
29+
public function marshall(array $values, ?array &$failed): array;
30+
31+
/**
32+
* Unserializes a single value and throws an exception if anything goes wrong.
33+
*
34+
* @return mixed
35+
*
36+
* @throws \Exception Whenever unserialization fails
37+
*/
38+
public function unmarshall(string $value);
39+
}

src/Symfony/Component/Cache/Simple/FilesystemCache.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@
1111

1212
namespace Symfony\Component\Cache\Simple;
1313

14+
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
15+
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
1416
use Symfony\Component\Cache\PruneableInterface;
1517
use Symfony\Component\Cache\Traits\FilesystemTrait;
1618

1719
class FilesystemCache extends AbstractCache implements PruneableInterface
1820
{
1921
use FilesystemTrait;
2022

21-
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null)
23+
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
2224
{
25+
$this->marshaller = $marshaller ?? new DefaultMarshaller();
2326
parent::__construct('', $defaultLifetime);
2427
$this->init($namespace, $directory);
2528
}

src/Symfony/Component/Cache/Simple/MemcachedCache.php

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

1212
namespace Symfony\Component\Cache\Simple;
1313

14+
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
1415
use Symfony\Component\Cache\Traits\MemcachedTrait;
1516

1617
class MemcachedCache extends AbstractCache
@@ -19,8 +20,8 @@ class MemcachedCache extends AbstractCache
1920

2021
protected $maxIdLength = 250;
2122

22-
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0)
23+
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
2324
{
24-
$this->init($client, $namespace, $defaultLifetime);
25+
$this->init($client, $namespace, $defaultLifetime, $marshaller);
2526
}
2627
}

src/Symfony/Component/Cache/Simple/PdoCache.php

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

1212
namespace Symfony\Component\Cache\Simple;
1313

14+
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
1415
use Symfony\Component\Cache\PruneableInterface;
1516
use Symfony\Component\Cache\Traits\PdoTrait;
1617

@@ -41,8 +42,8 @@ class PdoCache extends AbstractCache implements PruneableInterface
4142
* @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
4243
* @throws InvalidArgumentException When namespace contains invalid characters
4344
*/
44-
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = array())
45+
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = array(), MarshallerInterface $marshaller = null)
4546
{
46-
$this->init($connOrDsn, $namespace, $defaultLifetime, $options);
47+
$this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
4748
}
4849
}

0 commit comments

Comments
 (0)
0