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

Skip to content

Commit 9c328c4

Browse files
[Cache] Add MarshallerInterface allowing to change the serializer, providing a default one that automatically uses igbinary when available
1 parent 24babca commit 9c328c4

27 files changed

+358
-73
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.5.0 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
@@ -24,6 +24,7 @@
2424
use Symfony\Component\Cache\Adapter\AdapterInterface;
2525
use Symfony\Component\Cache\Adapter\ArrayAdapter;
2626
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
27+
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
2728
use Symfony\Component\Cache\ResettableInterface;
2829
use Symfony\Component\Config\FileLocator;
2930
use Symfony\Component\Config\Loader\LoaderInterface;
@@ -1539,6 +1540,10 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
15391540

15401541
private function registerCacheConfiguration(array $config, ContainerBuilder $container)
15411542
{
1543+
if (!class_exists(DefaultMarshaller::class)) {
1544+
$container->removeDefinition('cache.default_marshaller');
1545+
}
1546+
15421547
$version = new Parameter('container.build_id');
15431548
$container->getDefinition('cache.adapter.apcu')->replaceArgument(2, $version);
15441549
$container->getDefinition('cache.adapter.filesystem')->replaceArgument(2, $config['directory']);

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

Lines changed: 7 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,10 @@
118121
</call>
119122
</service>
120123

124+
<service id="cache.default_marshaller" class="Symfony\Component\Cache\Marshaller\DefaultMarshaller">
125+
<argument>null</argument> <!-- use igbinary_serialize() when available -->
126+
</service>
127+
121128
<service id="cache.default_clearer" class="Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer">
122129
<argument type="collection" />
123130
</service>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ function ($deferred, $namespace, &$expiredIds) use ($getId) {
8181
if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
8282
unset($metadata[CacheItem::METADATA_TAGS]);
8383
}
84-
// For compactness, expiry and creation duration are packed in the key of a array, using magic numbers as separators
84+
// For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
8585
$byLifetime[$ttl][$getId($key)] = $metadata ? array("\x9D".pack('VN', (int) $metadata[CacheItem::METADATA_EXPIRY] - CacheItem::METADATA_EXPIRY_OFFSET, $metadata[CacheItem::METADATA_CTIME])."\x5F" => $item->value) : $item->value;
8686
}
8787

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/ProxyAdapter.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,15 @@ function ($key, $innerItem) use ($defaultLifetime, $poolHash) {
6969
);
7070
$this->setInnerItem = \Closure::bind(
7171
/**
72-
* @param array $item A CacheItem cast to (array); accessing protected properties requires adding the \0*\0" PHP prefix
72+
* @param array $item A CacheItem cast to (array); accessing protected properties requires adding the "\0*\0" PHP prefix
7373
*/
7474
function (CacheItemInterface $innerItem, array $item) {
7575
// Tags are stored separately, no need to account for them when considering this item's newly set metadata
7676
if (isset(($metadata = $item["\0*\0newMetadata"])[CacheItem::METADATA_TAGS])) {
7777
unset($metadata[CacheItem::METADATA_TAGS]);
7878
}
7979
if ($metadata) {
80-
// For compactness, expiry and creation duration are packed in the key of a array, using magic numbers as separators
80+
// For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
8181
$item["\0*\0value"] = array("\x9D".pack('VN', (int) $metadata[CacheItem::METADATA_EXPIRY] - CacheItem::METADATA_EXPIRY_OFFSET, $metadata[CacheItem::METADATA_CTIME])."\x5F" => $item["\0*\0value"]);
8282
}
8383
$innerItem->set($item["\0*\0value"]);

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
1315

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

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
}

0 commit comments

Comments
 (0)