[Cache] Add `MarshallerInterface` allowing to change the serializer, providing a default one that automatically uses igbinary when available
This commit is contained in:
parent
24babca889
commit
9c328c4894
|
@ -151,6 +151,7 @@ before_install:
|
||||||
tfold ext.libsodium tpecl libsodium sodium.so $INI
|
tfold ext.libsodium tpecl libsodium sodium.so $INI
|
||||||
tfold ext.mongodb tpecl mongodb-1.5.0 mongodb.so $INI
|
tfold ext.mongodb tpecl mongodb-1.5.0 mongodb.so $INI
|
||||||
tfold ext.amqp tpecl amqp-1.9.3 amqp.so $INI
|
tfold ext.amqp tpecl amqp-1.9.3 amqp.so $INI
|
||||||
|
tfold ext.igbinary tpecl igbinary-2.0.6 igbinary.so $INI
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- |
|
- |
|
||||||
|
|
|
@ -24,6 +24,7 @@ use Symfony\Component\Cache\Adapter\AbstractAdapter;
|
||||||
use Symfony\Component\Cache\Adapter\AdapterInterface;
|
use Symfony\Component\Cache\Adapter\AdapterInterface;
|
||||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||||
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
|
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
|
||||||
|
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||||
use Symfony\Component\Cache\ResettableInterface;
|
use Symfony\Component\Cache\ResettableInterface;
|
||||||
use Symfony\Component\Config\FileLocator;
|
use Symfony\Component\Config\FileLocator;
|
||||||
use Symfony\Component\Config\Loader\LoaderInterface;
|
use Symfony\Component\Config\Loader\LoaderInterface;
|
||||||
|
@ -1539,6 +1540,10 @@ class FrameworkExtension extends Extension
|
||||||
|
|
||||||
private function registerCacheConfiguration(array $config, ContainerBuilder $container)
|
private function registerCacheConfiguration(array $config, ContainerBuilder $container)
|
||||||
{
|
{
|
||||||
|
if (!class_exists(DefaultMarshaller::class)) {
|
||||||
|
$container->removeDefinition('cache.default_marshaller');
|
||||||
|
}
|
||||||
|
|
||||||
$version = new Parameter('container.build_id');
|
$version = new Parameter('container.build_id');
|
||||||
$container->getDefinition('cache.adapter.apcu')->replaceArgument(2, $version);
|
$container->getDefinition('cache.adapter.apcu')->replaceArgument(2, $version);
|
||||||
$container->getDefinition('cache.adapter.filesystem')->replaceArgument(2, $config['directory']);
|
$container->getDefinition('cache.adapter.filesystem')->replaceArgument(2, $config['directory']);
|
||||||
|
|
|
@ -75,6 +75,7 @@
|
||||||
<argument /> <!-- namespace -->
|
<argument /> <!-- namespace -->
|
||||||
<argument>0</argument> <!-- default lifetime -->
|
<argument>0</argument> <!-- default lifetime -->
|
||||||
<argument>%kernel.cache_dir%/pools</argument>
|
<argument>%kernel.cache_dir%/pools</argument>
|
||||||
|
<argument type="service" id="cache.default_marshaller" on-invalid="ignore" />
|
||||||
<call method="setLogger">
|
<call method="setLogger">
|
||||||
<argument type="service" id="logger" on-invalid="ignore" />
|
<argument type="service" id="logger" on-invalid="ignore" />
|
||||||
</call>
|
</call>
|
||||||
|
@ -93,6 +94,7 @@
|
||||||
<argument /> <!-- Redis connection service -->
|
<argument /> <!-- Redis connection service -->
|
||||||
<argument /> <!-- namespace -->
|
<argument /> <!-- namespace -->
|
||||||
<argument>0</argument> <!-- default lifetime -->
|
<argument>0</argument> <!-- default lifetime -->
|
||||||
|
<argument type="service" id="cache.default_marshaller" on-invalid="ignore" />
|
||||||
<call method="setLogger">
|
<call method="setLogger">
|
||||||
<argument type="service" id="logger" on-invalid="ignore" />
|
<argument type="service" id="logger" on-invalid="ignore" />
|
||||||
</call>
|
</call>
|
||||||
|
@ -104,6 +106,7 @@
|
||||||
<argument /> <!-- Memcached connection service -->
|
<argument /> <!-- Memcached connection service -->
|
||||||
<argument /> <!-- namespace -->
|
<argument /> <!-- namespace -->
|
||||||
<argument>0</argument> <!-- default lifetime -->
|
<argument>0</argument> <!-- default lifetime -->
|
||||||
|
<argument type="service" id="cache.default_marshaller" on-invalid="ignore" />
|
||||||
<call method="setLogger">
|
<call method="setLogger">
|
||||||
<argument type="service" id="logger" on-invalid="ignore" />
|
<argument type="service" id="logger" on-invalid="ignore" />
|
||||||
</call>
|
</call>
|
||||||
|
@ -118,6 +121,10 @@
|
||||||
</call>
|
</call>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="cache.default_marshaller" class="Symfony\Component\Cache\Marshaller\DefaultMarshaller">
|
||||||
|
<argument>null</argument> <!-- use igbinary_serialize() when available -->
|
||||||
|
</service>
|
||||||
|
|
||||||
<service id="cache.default_clearer" class="Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer">
|
<service id="cache.default_clearer" class="Symfony\Component\HttpKernel\CacheClearer\Psr6CacheClearer">
|
||||||
<argument type="collection" />
|
<argument type="collection" />
|
||||||
</service>
|
</service>
|
||||||
|
|
|
@ -81,7 +81,7 @@ abstract class AbstractAdapter implements AdapterInterface, CacheInterface, Logg
|
||||||
if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
|
if (isset(($metadata = $item->newMetadata)[CacheItem::METADATA_TAGS])) {
|
||||||
unset($metadata[CacheItem::METADATA_TAGS]);
|
unset($metadata[CacheItem::METADATA_TAGS]);
|
||||||
}
|
}
|
||||||
// For compactness, expiry and creation duration are packed in the key of a array, using magic numbers as separators
|
// For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
|
||||||
$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;
|
$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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
namespace Symfony\Component\Cache\Adapter;
|
namespace Symfony\Component\Cache\Adapter;
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||||
|
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||||
use Symfony\Component\Cache\PruneableInterface;
|
use Symfony\Component\Cache\PruneableInterface;
|
||||||
use Symfony\Component\Cache\Traits\FilesystemTrait;
|
use Symfony\Component\Cache\Traits\FilesystemTrait;
|
||||||
|
|
||||||
|
@ -18,8 +20,9 @@ class FilesystemAdapter extends AbstractAdapter implements PruneableInterface
|
||||||
{
|
{
|
||||||
use FilesystemTrait;
|
use FilesystemTrait;
|
||||||
|
|
||||||
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null)
|
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
|
||||||
{
|
{
|
||||||
|
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||||
parent::__construct('', $defaultLifetime);
|
parent::__construct('', $defaultLifetime);
|
||||||
$this->init($namespace, $directory);
|
$this->init($namespace, $directory);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
namespace Symfony\Component\Cache\Adapter;
|
namespace Symfony\Component\Cache\Adapter;
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||||
use Symfony\Component\Cache\Traits\MemcachedTrait;
|
use Symfony\Component\Cache\Traits\MemcachedTrait;
|
||||||
|
|
||||||
class MemcachedAdapter extends AbstractAdapter
|
class MemcachedAdapter extends AbstractAdapter
|
||||||
|
@ -29,8 +30,8 @@ class MemcachedAdapter extends AbstractAdapter
|
||||||
*
|
*
|
||||||
* Using a MemcachedAdapter as a pure items store is fine.
|
* Using a MemcachedAdapter as a pure items store is fine.
|
||||||
*/
|
*/
|
||||||
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0)
|
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
|
||||||
{
|
{
|
||||||
$this->init($client, $namespace, $defaultLifetime);
|
$this->init($client, $namespace, $defaultLifetime, $marshaller);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ namespace Symfony\Component\Cache\Adapter;
|
||||||
|
|
||||||
use Doctrine\DBAL\Connection;
|
use Doctrine\DBAL\Connection;
|
||||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||||
use Symfony\Component\Cache\PruneableInterface;
|
use Symfony\Component\Cache\PruneableInterface;
|
||||||
use Symfony\Component\Cache\Traits\PdoTrait;
|
use Symfony\Component\Cache\Traits\PdoTrait;
|
||||||
|
|
||||||
|
@ -43,8 +44,8 @@ class PdoAdapter extends AbstractAdapter implements PruneableInterface
|
||||||
* @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
|
* @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
|
||||||
* @throws InvalidArgumentException When namespace contains invalid characters
|
* @throws InvalidArgumentException When namespace contains invalid characters
|
||||||
*/
|
*/
|
||||||
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = array())
|
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = array(), MarshallerInterface $marshaller = null)
|
||||||
{
|
{
|
||||||
$this->init($connOrDsn, $namespace, $defaultLifetime, $options);
|
$this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ use Psr\Cache\CacheItemPoolInterface;
|
||||||
use Symfony\Component\Cache\CacheInterface;
|
use Symfony\Component\Cache\CacheInterface;
|
||||||
use Symfony\Component\Cache\CacheItem;
|
use Symfony\Component\Cache\CacheItem;
|
||||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||||
use Symfony\Component\Cache\PruneableInterface;
|
use Symfony\Component\Cache\PruneableInterface;
|
||||||
use Symfony\Component\Cache\ResettableInterface;
|
use Symfony\Component\Cache\ResettableInterface;
|
||||||
use Symfony\Component\Cache\Traits\GetTrait;
|
use Symfony\Component\Cache\Traits\GetTrait;
|
||||||
|
@ -34,6 +35,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
|
||||||
use GetTrait;
|
use GetTrait;
|
||||||
|
|
||||||
private $createCacheItem;
|
private $createCacheItem;
|
||||||
|
private $marshaller;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $file The PHP file were values are cached
|
* @param string $file The PHP file were values are cached
|
||||||
|
@ -88,6 +90,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
|
||||||
$this->initialize();
|
$this->initialize();
|
||||||
}
|
}
|
||||||
if (!isset($this->keys[$key])) {
|
if (!isset($this->keys[$key])) {
|
||||||
|
get_from_pool:
|
||||||
if ($this->pool instanceof CacheInterface) {
|
if ($this->pool instanceof CacheInterface) {
|
||||||
return $this->pool->get($key, $callback, $beta);
|
return $this->pool->get($key, $callback, $beta);
|
||||||
}
|
}
|
||||||
|
@ -99,11 +102,16 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
|
||||||
if ('N;' === $value) {
|
if ('N;' === $value) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
if ($value instanceof \Closure) {
|
if ($value instanceof \Closure) {
|
||||||
return $value();
|
return $value();
|
||||||
}
|
}
|
||||||
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
|
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
|
||||||
return unserialize($value);
|
return ($this->marshaller ?? $this->marshaller = new DefaultMarshaller())->unmarshall($value);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
unset($this->keys[$key]);
|
||||||
|
goto get_from_pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
|
@ -278,7 +286,7 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
|
||||||
}
|
}
|
||||||
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
|
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
|
||||||
try {
|
try {
|
||||||
yield $key => $f($key, unserialize($value), true);
|
yield $key => $f($key, $this->unserializeValue($value), true);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
yield $key => $f($key, null, false);
|
yield $key => $f($key, null, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
|
||||||
);
|
);
|
||||||
$this->setInnerItem = \Closure::bind(
|
$this->setInnerItem = \Closure::bind(
|
||||||
/**
|
/**
|
||||||
* @param array $item A CacheItem cast to (array); accessing protected properties requires adding the \0*\0" PHP prefix
|
* @param array $item A CacheItem cast to (array); accessing protected properties requires adding the "\0*\0" PHP prefix
|
||||||
*/
|
*/
|
||||||
function (CacheItemInterface $innerItem, array $item) {
|
function (CacheItemInterface $innerItem, array $item) {
|
||||||
// Tags are stored separately, no need to account for them when considering this item's newly set metadata
|
// Tags are stored separately, no need to account for them when considering this item's newly set metadata
|
||||||
|
@ -77,7 +77,7 @@ class ProxyAdapter implements AdapterInterface, CacheInterface, PruneableInterfa
|
||||||
unset($metadata[CacheItem::METADATA_TAGS]);
|
unset($metadata[CacheItem::METADATA_TAGS]);
|
||||||
}
|
}
|
||||||
if ($metadata) {
|
if ($metadata) {
|
||||||
// For compactness, expiry and creation duration are packed in the key of a array, using magic numbers as separators
|
// For compactness, expiry and creation duration are packed in the key of an array, using magic numbers as separators
|
||||||
$item["\0*\0value"] = array("\x9D".pack('VN', (int) $metadata[CacheItem::METADATA_EXPIRY] - CacheItem::METADATA_EXPIRY_OFFSET, $metadata[CacheItem::METADATA_CTIME])."\x5F" => $item["\0*\0value"]);
|
$item["\0*\0value"] = array("\x9D".pack('VN', (int) $metadata[CacheItem::METADATA_EXPIRY] - CacheItem::METADATA_EXPIRY_OFFSET, $metadata[CacheItem::METADATA_CTIME])."\x5F" => $item["\0*\0value"]);
|
||||||
}
|
}
|
||||||
$innerItem->set($item["\0*\0value"]);
|
$innerItem->set($item["\0*\0value"]);
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
namespace Symfony\Component\Cache\Adapter;
|
namespace Symfony\Component\Cache\Adapter;
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||||
use Symfony\Component\Cache\Traits\RedisTrait;
|
use Symfony\Component\Cache\Traits\RedisTrait;
|
||||||
|
|
||||||
class RedisAdapter extends AbstractAdapter
|
class RedisAdapter extends AbstractAdapter
|
||||||
|
@ -22,8 +23,8 @@ class RedisAdapter extends AbstractAdapter
|
||||||
* @param string $namespace The default namespace
|
* @param string $namespace The default namespace
|
||||||
* @param int $defaultLifetime The default lifetime
|
* @param int $defaultLifetime The default lifetime
|
||||||
*/
|
*/
|
||||||
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0)
|
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
|
||||||
{
|
{
|
||||||
$this->init($redisClient, $namespace, $defaultLifetime);
|
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,14 @@ CHANGELOG
|
||||||
4.2.0
|
4.2.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
* added `MarshallerInterface` and `DefaultMarshaller` to allow changing the serializer and provide one that automatically uses igbinary when available
|
||||||
* added `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache
|
* added `CacheInterface`, which provides stampede protection via probabilistic early expiration and should become the preferred way to use a cache
|
||||||
* added sub-second expiry accuracy for backends that support it
|
* added sub-second expiry accuracy for backends that support it
|
||||||
* added support for phpredis 4 `compression` and `tcp_keepalive` options
|
* added support for phpredis 4 `compression` and `tcp_keepalive` options
|
||||||
* throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool
|
* throw `LogicException` when `CacheItem::tag()` is called on an item coming from a non tag-aware pool
|
||||||
* deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead
|
* deprecated `CacheItem::getPreviousTags()`, use `CacheItem::getMetadata()` instead
|
||||||
* deprecated the `AbstractAdapter::createSystemCache()` method
|
* deprecated the `AbstractAdapter::createSystemCache()` method
|
||||||
|
* deprecated the `AbstractAdapter::unserialize()` and `AbstractCache::unserialize()` methods
|
||||||
|
|
||||||
3.4.0
|
3.4.0
|
||||||
-----
|
-----
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Cache\Marshaller;
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\Exception\CacheException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes/unserializes values using igbinary_serialize() if available, serialize() otherwise.
|
||||||
|
*
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
class DefaultMarshaller implements MarshallerInterface
|
||||||
|
{
|
||||||
|
private $useIgbinarySerialize = true;
|
||||||
|
|
||||||
|
public function __construct(bool $useIgbinarySerialize = null)
|
||||||
|
{
|
||||||
|
if (null === $useIgbinarySerialize) {
|
||||||
|
$useIgbinarySerialize = \extension_loaded('igbinary');
|
||||||
|
} elseif ($useIgbinarySerialize && !\extension_loaded('igbinary')) {
|
||||||
|
throw new CacheException('The "igbinary" PHP extension is not loaded.');
|
||||||
|
}
|
||||||
|
$this->useIgbinarySerialize = $useIgbinarySerialize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function marshall(array $values, ?array &$failed): array
|
||||||
|
{
|
||||||
|
$serialized = $failed = array();
|
||||||
|
|
||||||
|
foreach ($values as $id => $value) {
|
||||||
|
try {
|
||||||
|
if ($this->useIgbinarySerialize) {
|
||||||
|
$serialized[$id] = igbinary_serialize($value);
|
||||||
|
} else {
|
||||||
|
$serialized[$id] = serialize($value);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$failed[] = $id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $serialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function unmarshall(string $value)
|
||||||
|
{
|
||||||
|
if ('b:0;' === $value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ('N;' === $value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
static $igbinaryNull;
|
||||||
|
if ($value === ($igbinaryNull ?? $igbinaryNull = \extension_loaded('igbinary') ? igbinary_serialize(null) : false)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
|
||||||
|
try {
|
||||||
|
if (':' === ($value[1] ?? ':')) {
|
||||||
|
if (false !== $value = unserialize($value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
} elseif (false === $igbinaryNull) {
|
||||||
|
throw new \RuntimeException('Failed to unserialize cached value, did you forget to install the "igbinary" extension?');
|
||||||
|
} elseif (null !== $value = igbinary_unserialize($value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \DomainException(error_get_last() ? error_get_last()['message'] : 'Failed to unserialize cached value');
|
||||||
|
} catch (\Error $e) {
|
||||||
|
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
|
||||||
|
} finally {
|
||||||
|
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public static function handleUnserializeCallback($class)
|
||||||
|
{
|
||||||
|
throw new \DomainException('Class not found: '.$class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Cache\Marshaller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes/unserializes PHP values.
|
||||||
|
*
|
||||||
|
* Implementations of this interface MUST deal with errors carefuly. They MUST
|
||||||
|
* also deal with forward and backward compatibility at the storage format level.
|
||||||
|
*
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
interface MarshallerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Serializes a list of values.
|
||||||
|
*
|
||||||
|
* When serialization fails for a specific value, no exception should be
|
||||||
|
* thrown. Instead, its key should be listed in $failed.
|
||||||
|
*/
|
||||||
|
public function marshall(array $values, ?array &$failed): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unserializes a single value and throws an exception if anything goes wrong.
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*
|
||||||
|
* @throws \Exception Whenever unserialization fails
|
||||||
|
*/
|
||||||
|
public function unmarshall(string $value);
|
||||||
|
}
|
|
@ -11,6 +11,8 @@
|
||||||
|
|
||||||
namespace Symfony\Component\Cache\Simple;
|
namespace Symfony\Component\Cache\Simple;
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||||
|
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||||
use Symfony\Component\Cache\PruneableInterface;
|
use Symfony\Component\Cache\PruneableInterface;
|
||||||
use Symfony\Component\Cache\Traits\FilesystemTrait;
|
use Symfony\Component\Cache\Traits\FilesystemTrait;
|
||||||
|
|
||||||
|
@ -18,8 +20,9 @@ class FilesystemCache extends AbstractCache implements PruneableInterface
|
||||||
{
|
{
|
||||||
use FilesystemTrait;
|
use FilesystemTrait;
|
||||||
|
|
||||||
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null)
|
public function __construct(string $namespace = '', int $defaultLifetime = 0, string $directory = null, MarshallerInterface $marshaller = null)
|
||||||
{
|
{
|
||||||
|
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||||
parent::__construct('', $defaultLifetime);
|
parent::__construct('', $defaultLifetime);
|
||||||
$this->init($namespace, $directory);
|
$this->init($namespace, $directory);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
namespace Symfony\Component\Cache\Simple;
|
namespace Symfony\Component\Cache\Simple;
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||||
use Symfony\Component\Cache\Traits\MemcachedTrait;
|
use Symfony\Component\Cache\Traits\MemcachedTrait;
|
||||||
|
|
||||||
class MemcachedCache extends AbstractCache
|
class MemcachedCache extends AbstractCache
|
||||||
|
@ -19,8 +20,8 @@ class MemcachedCache extends AbstractCache
|
||||||
|
|
||||||
protected $maxIdLength = 250;
|
protected $maxIdLength = 250;
|
||||||
|
|
||||||
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0)
|
public function __construct(\Memcached $client, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
|
||||||
{
|
{
|
||||||
$this->init($client, $namespace, $defaultLifetime);
|
$this->init($client, $namespace, $defaultLifetime, $marshaller);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
namespace Symfony\Component\Cache\Simple;
|
namespace Symfony\Component\Cache\Simple;
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||||
use Symfony\Component\Cache\PruneableInterface;
|
use Symfony\Component\Cache\PruneableInterface;
|
||||||
use Symfony\Component\Cache\Traits\PdoTrait;
|
use Symfony\Component\Cache\Traits\PdoTrait;
|
||||||
|
|
||||||
|
@ -41,8 +42,8 @@ class PdoCache extends AbstractCache implements PruneableInterface
|
||||||
* @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
|
* @throws InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
|
||||||
* @throws InvalidArgumentException When namespace contains invalid characters
|
* @throws InvalidArgumentException When namespace contains invalid characters
|
||||||
*/
|
*/
|
||||||
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = array())
|
public function __construct($connOrDsn, string $namespace = '', int $defaultLifetime = 0, array $options = array(), MarshallerInterface $marshaller = null)
|
||||||
{
|
{
|
||||||
$this->init($connOrDsn, $namespace, $defaultLifetime, $options);
|
$this->init($connOrDsn, $namespace, $defaultLifetime, $options, $marshaller);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ namespace Symfony\Component\Cache\Simple;
|
||||||
|
|
||||||
use Psr\SimpleCache\CacheInterface;
|
use Psr\SimpleCache\CacheInterface;
|
||||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||||
use Symfony\Component\Cache\Traits\PhpArrayTrait;
|
use Symfony\Component\Cache\Traits\PhpArrayTrait;
|
||||||
use Symfony\Component\Cache\PruneableInterface;
|
use Symfony\Component\Cache\PruneableInterface;
|
||||||
use Symfony\Component\Cache\ResettableInterface;
|
use Symfony\Component\Cache\ResettableInterface;
|
||||||
|
@ -28,6 +29,8 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
|
||||||
{
|
{
|
||||||
use PhpArrayTrait;
|
use PhpArrayTrait;
|
||||||
|
|
||||||
|
private $marshaller;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $file The PHP file were values are cached
|
* @param string $file The PHP file were values are cached
|
||||||
* @param CacheInterface $fallbackPool A pool to fallback on when an item is not hit
|
* @param CacheInterface $fallbackPool A pool to fallback on when an item is not hit
|
||||||
|
@ -83,7 +86,7 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
|
||||||
}
|
}
|
||||||
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
|
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
|
||||||
try {
|
try {
|
||||||
return unserialize($value);
|
return ($this->marshaller ?? $this->marshaller = new DefaultMarshaller())->unmarshall($value);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return $default;
|
return $default;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
namespace Symfony\Component\Cache\Simple;
|
namespace Symfony\Component\Cache\Simple;
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||||
use Symfony\Component\Cache\Traits\RedisTrait;
|
use Symfony\Component\Cache\Traits\RedisTrait;
|
||||||
|
|
||||||
class RedisCache extends AbstractCache
|
class RedisCache extends AbstractCache
|
||||||
|
@ -22,8 +23,8 @@ class RedisCache extends AbstractCache
|
||||||
* @param string $namespace
|
* @param string $namespace
|
||||||
* @param int $defaultLifetime
|
* @param int $defaultLifetime
|
||||||
*/
|
*/
|
||||||
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0)
|
public function __construct($redisClient, string $namespace = '', int $defaultLifetime = 0, MarshallerInterface $marshaller = null)
|
||||||
{
|
{
|
||||||
$this->init($redisClient, $namespace, $defaultLifetime);
|
$this->init($redisClient, $namespace, $defaultLifetime, $marshaller);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ class MaxIdLengthAdapterTest extends TestCase
|
||||||
$reflectionProperty->setValue($cache, true);
|
$reflectionProperty->setValue($cache, true);
|
||||||
|
|
||||||
// Versioning enabled
|
// Versioning enabled
|
||||||
$this->assertEquals('--------------------------:1:------------', $reflectionMethod->invokeArgs($cache, array(str_repeat('-', 12))));
|
$this->assertEquals('--------------------------:1/------------', $reflectionMethod->invokeArgs($cache, array(str_repeat('-', 12))));
|
||||||
$this->assertLessThanOrEqual(50, strlen($reflectionMethod->invokeArgs($cache, array(str_repeat('-', 12)))));
|
$this->assertLessThanOrEqual(50, strlen($reflectionMethod->invokeArgs($cache, array(str_repeat('-', 12)))));
|
||||||
$this->assertLessThanOrEqual(50, strlen($reflectionMethod->invokeArgs($cache, array(str_repeat('-', 23)))));
|
$this->assertLessThanOrEqual(50, strlen($reflectionMethod->invokeArgs($cache, array(str_repeat('-', 23)))));
|
||||||
$this->assertLessThanOrEqual(50, strlen($reflectionMethod->invokeArgs($cache, array(str_repeat('-', 40)))));
|
$this->assertLessThanOrEqual(50, strlen($reflectionMethod->invokeArgs($cache, array(str_repeat('-', 40)))));
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Component\Cache\Tests\Marshaller;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||||
|
|
||||||
|
class DefaultMarshallerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testSerialize()
|
||||||
|
{
|
||||||
|
$marshaller = new DefaultMarshaller();
|
||||||
|
$values = array(
|
||||||
|
'a' => 123,
|
||||||
|
'b' => function () {},
|
||||||
|
);
|
||||||
|
|
||||||
|
$expected = array('a' => \extension_loaded('igbinary') ? igbinary_serialize(123) : serialize(123));
|
||||||
|
$this->assertSame($expected, $marshaller->marshall($values, $failed));
|
||||||
|
$this->assertSame(array('b'), $failed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNativeUnserialize()
|
||||||
|
{
|
||||||
|
$marshaller = new DefaultMarshaller();
|
||||||
|
$this->assertNull($marshaller->unmarshall(serialize(null)));
|
||||||
|
$this->assertFalse($marshaller->unmarshall(serialize(false)));
|
||||||
|
$this->assertSame('', $marshaller->unmarshall(serialize('')));
|
||||||
|
$this->assertSame(0, $marshaller->unmarshall(serialize(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires extension igbinary
|
||||||
|
*/
|
||||||
|
public function testIgbinaryUnserialize()
|
||||||
|
{
|
||||||
|
$marshaller = new DefaultMarshaller();
|
||||||
|
$this->assertNull($marshaller->unmarshall(igbinary_serialize(null)));
|
||||||
|
$this->assertFalse($marshaller->unmarshall(igbinary_serialize(false)));
|
||||||
|
$this->assertSame('', $marshaller->unmarshall(igbinary_serialize('')));
|
||||||
|
$this->assertSame(0, $marshaller->unmarshall(igbinary_serialize(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \DomainException
|
||||||
|
* @expectedExceptionMessage Class not found: NotExistingClass
|
||||||
|
*/
|
||||||
|
public function testNativeUnserializeNotFoundClass()
|
||||||
|
{
|
||||||
|
$marshaller = new DefaultMarshaller();
|
||||||
|
$marshaller->unmarshall('O:16:"NotExistingClass":0:{}');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires extension igbinary
|
||||||
|
* @expectedException \DomainException
|
||||||
|
* @expectedExceptionMessage Class not found: NotExistingClass
|
||||||
|
*/
|
||||||
|
public function testIgbinaryUnserializeNotFoundClass()
|
||||||
|
{
|
||||||
|
$marshaller = new DefaultMarshaller();
|
||||||
|
$marshaller->unmarshall(rawurldecode('%00%00%00%02%17%10NotExistingClass%14%00'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \DomainException
|
||||||
|
* @expectedExceptionMessage unserialize(): Error at offset 0 of 3 bytes
|
||||||
|
*/
|
||||||
|
public function testNativeUnserializeInvalid()
|
||||||
|
{
|
||||||
|
$marshaller = new DefaultMarshaller();
|
||||||
|
set_error_handler(function () { return false; });
|
||||||
|
try {
|
||||||
|
@$marshaller->unmarshall(':::');
|
||||||
|
} finally {
|
||||||
|
restore_error_handler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @requires extension igbinary
|
||||||
|
* @expectedException \DomainException
|
||||||
|
* @expectedExceptionMessage igbinary_unserialize_zval: unknown type '61', position 5
|
||||||
|
*/
|
||||||
|
public function testIgbinaryUnserializeInvalid()
|
||||||
|
{
|
||||||
|
$marshaller = new DefaultMarshaller();
|
||||||
|
set_error_handler(function () { return false; });
|
||||||
|
try {
|
||||||
|
@$marshaller->unmarshall(rawurldecode('%00%00%00%02abc'));
|
||||||
|
} finally {
|
||||||
|
restore_error_handler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -109,14 +109,14 @@ trait AbstractTrait
|
||||||
if ($cleared = $this->versioningIsEnabled) {
|
if ($cleared = $this->versioningIsEnabled) {
|
||||||
$namespaceVersion = 2;
|
$namespaceVersion = 2;
|
||||||
try {
|
try {
|
||||||
foreach ($this->doFetch(array('@'.$this->namespace)) as $v) {
|
foreach ($this->doFetch(array('/'.$this->namespace)) as $v) {
|
||||||
$namespaceVersion = 1 + (int) $v;
|
$namespaceVersion = 1 + (int) $v;
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
}
|
}
|
||||||
$namespaceVersion .= ':';
|
$namespaceVersion .= '/';
|
||||||
try {
|
try {
|
||||||
$cleared = $this->doSave(array('@'.$this->namespace => $namespaceVersion), 0);
|
$cleared = $this->doSave(array('/'.$this->namespace => $namespaceVersion), 0);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$cleared = false;
|
$cleared = false;
|
||||||
}
|
}
|
||||||
|
@ -222,9 +222,13 @@ trait AbstractTrait
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*
|
*
|
||||||
* @throws \Exception
|
* @throws \Exception
|
||||||
|
*
|
||||||
|
* @deprecated since Symfony 4.2, use DefaultMarshaller instead.
|
||||||
*/
|
*/
|
||||||
protected static function unserialize($value)
|
protected static function unserialize($value)
|
||||||
{
|
{
|
||||||
|
@trigger_error(sprintf('The "%s::unserialize()" method is deprecated since Symfony 4.2, use DefaultMarshaller instead.', __CLASS__), E_USER_DEPRECATED);
|
||||||
|
|
||||||
if ('b:0;' === $value) {
|
if ('b:0;' === $value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -245,9 +249,9 @@ trait AbstractTrait
|
||||||
{
|
{
|
||||||
if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
|
if ($this->versioningIsEnabled && '' === $this->namespaceVersion) {
|
||||||
$this->ids = array();
|
$this->ids = array();
|
||||||
$this->namespaceVersion = '1:';
|
$this->namespaceVersion = '1/';
|
||||||
try {
|
try {
|
||||||
foreach ($this->doFetch(array('@'.$this->namespace)) as $v) {
|
foreach ($this->doFetch(array('/'.$this->namespace)) as $v) {
|
||||||
$this->namespaceVersion = $v;
|
$this->namespaceVersion = $v;
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|
|
@ -51,6 +51,7 @@ trait ApcuTrait
|
||||||
*/
|
*/
|
||||||
protected function doFetch(array $ids)
|
protected function doFetch(array $ids)
|
||||||
{
|
{
|
||||||
|
$unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
|
||||||
try {
|
try {
|
||||||
$values = array();
|
$values = array();
|
||||||
foreach (apcu_fetch($ids, $ok) ?: array() as $k => $v) {
|
foreach (apcu_fetch($ids, $ok) ?: array() as $k => $v) {
|
||||||
|
@ -62,6 +63,8 @@ trait ApcuTrait
|
||||||
return $values;
|
return $values;
|
||||||
} catch (\Error $e) {
|
} catch (\Error $e) {
|
||||||
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
|
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
|
||||||
|
} finally {
|
||||||
|
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,8 @@ trait FilesystemTrait
|
||||||
{
|
{
|
||||||
use FilesystemCommonTrait;
|
use FilesystemCommonTrait;
|
||||||
|
|
||||||
|
private $marshaller;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
|
@ -68,7 +70,7 @@ trait FilesystemTrait
|
||||||
$value = stream_get_contents($h);
|
$value = stream_get_contents($h);
|
||||||
fclose($h);
|
fclose($h);
|
||||||
if ($i === $id) {
|
if ($i === $id) {
|
||||||
$values[$id] = parent::unserialize($value);
|
$values[$id] = $this->marshaller->unmarshall($value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,17 +93,19 @@ trait FilesystemTrait
|
||||||
*/
|
*/
|
||||||
protected function doSave(array $values, $lifetime)
|
protected function doSave(array $values, $lifetime)
|
||||||
{
|
{
|
||||||
$ok = true;
|
|
||||||
$expiresAt = $lifetime ? (time() + $lifetime) : 0;
|
$expiresAt = $lifetime ? (time() + $lifetime) : 0;
|
||||||
|
$values = $this->marshaller->marshall($values, $failed);
|
||||||
|
|
||||||
foreach ($values as $id => $value) {
|
foreach ($values as $id => $value) {
|
||||||
$ok = $this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".serialize($value), $expiresAt) && $ok;
|
if (!$this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".$value, $expiresAt)) {
|
||||||
|
$failed[] = $id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$ok && !is_writable($this->directory)) {
|
if ($failed && !is_writable($this->directory)) {
|
||||||
throw new CacheException(sprintf('Cache directory is not writable (%s)', $this->directory));
|
throw new CacheException(sprintf('Cache directory is not writable (%s)', $this->directory));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $ok;
|
return $failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@ namespace Symfony\Component\Cache\Traits;
|
||||||
|
|
||||||
use Symfony\Component\Cache\Exception\CacheException;
|
use Symfony\Component\Cache\Exception\CacheException;
|
||||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||||
|
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rob Frawley 2nd <rmf@src.run>
|
* @author Rob Frawley 2nd <rmf@src.run>
|
||||||
|
@ -29,6 +31,7 @@ trait MemcachedTrait
|
||||||
'serializer' => 'php',
|
'serializer' => 'php',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private $marshaller;
|
||||||
private $client;
|
private $client;
|
||||||
private $lazyClient;
|
private $lazyClient;
|
||||||
|
|
||||||
|
@ -37,7 +40,7 @@ trait MemcachedTrait
|
||||||
return extension_loaded('memcached') && version_compare(phpversion('memcached'), '2.2.0', '>=');
|
return extension_loaded('memcached') && version_compare(phpversion('memcached'), '2.2.0', '>=');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function init(\Memcached $client, $namespace, $defaultLifetime)
|
private function init(\Memcached $client, $namespace, $defaultLifetime, ?MarshallerInterface $marshaller)
|
||||||
{
|
{
|
||||||
if (!static::isSupported()) {
|
if (!static::isSupported()) {
|
||||||
throw new CacheException('Memcached >= 2.2.0 is required');
|
throw new CacheException('Memcached >= 2.2.0 is required');
|
||||||
|
@ -55,6 +58,7 @@ trait MemcachedTrait
|
||||||
|
|
||||||
parent::__construct($namespace, $defaultLifetime);
|
parent::__construct($namespace, $defaultLifetime);
|
||||||
$this->enableVersioning();
|
$this->enableVersioning();
|
||||||
|
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -194,6 +198,10 @@ trait MemcachedTrait
|
||||||
*/
|
*/
|
||||||
protected function doSave(array $values, $lifetime)
|
protected function doSave(array $values, $lifetime)
|
||||||
{
|
{
|
||||||
|
if (!$values = $this->marshaller->marshall($values, $failed)) {
|
||||||
|
return $failed;
|
||||||
|
}
|
||||||
|
|
||||||
if ($lifetime && $lifetime > 30 * 86400) {
|
if ($lifetime && $lifetime > 30 * 86400) {
|
||||||
$lifetime += time();
|
$lifetime += time();
|
||||||
}
|
}
|
||||||
|
@ -203,7 +211,7 @@ trait MemcachedTrait
|
||||||
$encodedValues[rawurlencode($key)] = $value;
|
$encodedValues[rawurlencode($key)] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime));
|
return $this->checkResultCode($this->getClient()->setMulti($encodedValues, $lifetime)) ? $failed : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -211,7 +219,6 @@ trait MemcachedTrait
|
||||||
*/
|
*/
|
||||||
protected function doFetch(array $ids)
|
protected function doFetch(array $ids)
|
||||||
{
|
{
|
||||||
$unserializeCallbackHandler = ini_set('unserialize_callback_func', __CLASS__.'::handleUnserializeCallback');
|
|
||||||
try {
|
try {
|
||||||
$encodedIds = array_map('rawurlencode', $ids);
|
$encodedIds = array_map('rawurlencode', $ids);
|
||||||
|
|
||||||
|
@ -219,14 +226,12 @@ trait MemcachedTrait
|
||||||
|
|
||||||
$result = array();
|
$result = array();
|
||||||
foreach ($encodedResult as $key => $value) {
|
foreach ($encodedResult as $key => $value) {
|
||||||
$result[rawurldecode($key)] = $value;
|
$result[rawurldecode($key)] = $this->marshaller->unmarshall($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
} catch (\Error $e) {
|
} catch (\Error $e) {
|
||||||
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
|
throw new \ErrorException($e->getMessage(), $e->getCode(), E_ERROR, $e->getFile(), $e->getLine());
|
||||||
} finally {
|
|
||||||
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,12 +16,15 @@ use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
|
||||||
use Doctrine\DBAL\DBALException;
|
use Doctrine\DBAL\DBALException;
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||||
|
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
trait PdoTrait
|
trait PdoTrait
|
||||||
{
|
{
|
||||||
|
private $marshaller;
|
||||||
private $conn;
|
private $conn;
|
||||||
private $dsn;
|
private $dsn;
|
||||||
private $driver;
|
private $driver;
|
||||||
|
@ -36,7 +39,7 @@ trait PdoTrait
|
||||||
private $connectionOptions = array();
|
private $connectionOptions = array();
|
||||||
private $namespace;
|
private $namespace;
|
||||||
|
|
||||||
private function init($connOrDsn, $namespace, $defaultLifetime, array $options)
|
private function init($connOrDsn, $namespace, $defaultLifetime, array $options, ?MarshallerInterface $marshaller)
|
||||||
{
|
{
|
||||||
if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) {
|
if (isset($namespace[0]) && preg_match('#[^-+.A-Za-z0-9]#', $namespace, $match)) {
|
||||||
throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0]));
|
throw new InvalidArgumentException(sprintf('Namespace contains "%s" but only characters in [-+.A-Za-z0-9] are allowed.', $match[0]));
|
||||||
|
@ -65,6 +68,7 @@ trait PdoTrait
|
||||||
$this->password = isset($options['db_password']) ? $options['db_password'] : $this->password;
|
$this->password = isset($options['db_password']) ? $options['db_password'] : $this->password;
|
||||||
$this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions;
|
$this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions;
|
||||||
$this->namespace = $namespace;
|
$this->namespace = $namespace;
|
||||||
|
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||||
|
|
||||||
parent::__construct($namespace, $defaultLifetime);
|
parent::__construct($namespace, $defaultLifetime);
|
||||||
}
|
}
|
||||||
|
@ -181,7 +185,7 @@ trait PdoTrait
|
||||||
if (null === $row[1]) {
|
if (null === $row[1]) {
|
||||||
$expired[] = $row[0];
|
$expired[] = $row[0];
|
||||||
} else {
|
} else {
|
||||||
yield $row[0] => parent::unserialize(is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]);
|
yield $row[0] => $this->marshaller->unmarshall(is_resource($row[1]) ? stream_get_contents($row[1]) : $row[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,18 +256,7 @@ trait PdoTrait
|
||||||
*/
|
*/
|
||||||
protected function doSave(array $values, $lifetime)
|
protected function doSave(array $values, $lifetime)
|
||||||
{
|
{
|
||||||
$serialized = array();
|
if (!$values = $this->marshaller->marshall($values, $failed)) {
|
||||||
$failed = array();
|
|
||||||
|
|
||||||
foreach ($values as $id => $value) {
|
|
||||||
try {
|
|
||||||
$serialized[$id] = serialize($value);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$failed[] = $id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$serialized) {
|
|
||||||
return $failed;
|
return $failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,7 +321,7 @@ trait PdoTrait
|
||||||
$insertStmt->bindValue(':time', $now, \PDO::PARAM_INT);
|
$insertStmt->bindValue(':time', $now, \PDO::PARAM_INT);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($serialized as $id => $data) {
|
foreach ($values as $id => $data) {
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
||||||
if (null === $driver && !$stmt->rowCount()) {
|
if (null === $driver && !$stmt->rowCount()) {
|
||||||
|
|
|
@ -13,6 +13,7 @@ namespace Symfony\Component\Cache\Traits;
|
||||||
|
|
||||||
use Symfony\Component\Cache\Exception\CacheException;
|
use Symfony\Component\Cache\Exception\CacheException;
|
||||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||||
use Symfony\Component\Cache\Marshaller\PhpMarshaller;
|
use Symfony\Component\Cache\Marshaller\PhpMarshaller;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,6 +30,7 @@ trait PhpFilesTrait
|
||||||
doDelete as private doCommonDelete;
|
doDelete as private doCommonDelete;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private $marshaller;
|
||||||
private $includeHandler;
|
private $includeHandler;
|
||||||
private $appendOnly;
|
private $appendOnly;
|
||||||
private $values = array();
|
private $values = array();
|
||||||
|
@ -91,7 +93,7 @@ trait PhpFilesTrait
|
||||||
} elseif ($value instanceof \Closure) {
|
} elseif ($value instanceof \Closure) {
|
||||||
$values[$id] = $value();
|
$values[$id] = $value();
|
||||||
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
|
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
|
||||||
$values[$id] = parent::unserialize($value);
|
$values[$id] = ($this->marshaller ?? $this->marshaller = new DefaultMarshaller())->unmarshall($value);
|
||||||
} else {
|
} else {
|
||||||
$values[$id] = $value;
|
$values[$id] = $value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ use Predis\Connection\Aggregate\RedisCluster;
|
||||||
use Predis\Response\Status;
|
use Predis\Response\Status;
|
||||||
use Symfony\Component\Cache\Exception\CacheException;
|
use Symfony\Component\Cache\Exception\CacheException;
|
||||||
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
use Symfony\Component\Cache\Exception\InvalidArgumentException;
|
||||||
|
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
|
||||||
|
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Aurimas Niekis <aurimas@niekis.lt>
|
* @author Aurimas Niekis <aurimas@niekis.lt>
|
||||||
|
@ -39,11 +41,12 @@ trait RedisTrait
|
||||||
'lazy' => false,
|
'lazy' => false,
|
||||||
);
|
);
|
||||||
private $redis;
|
private $redis;
|
||||||
|
private $marshaller;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient
|
* @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient
|
||||||
*/
|
*/
|
||||||
private function init($redisClient, $namespace = '', $defaultLifetime = 0)
|
private function init($redisClient, $namespace, $defaultLifetime, ?MarshallerInterface $marshaller)
|
||||||
{
|
{
|
||||||
parent::__construct($namespace, $defaultLifetime);
|
parent::__construct($namespace, $defaultLifetime);
|
||||||
|
|
||||||
|
@ -56,6 +59,7 @@ trait RedisTrait
|
||||||
throw new InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, is_object($redisClient) ? get_class($redisClient) : gettype($redisClient)));
|
throw new InvalidArgumentException(sprintf('%s() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\Client, %s given', __METHOD__, is_object($redisClient) ? get_class($redisClient) : gettype($redisClient)));
|
||||||
}
|
}
|
||||||
$this->redis = $redisClient;
|
$this->redis = $redisClient;
|
||||||
|
$this->marshaller = $marshaller ?? new DefaultMarshaller();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -186,7 +190,7 @@ trait RedisTrait
|
||||||
});
|
});
|
||||||
foreach ($values as $id => $v) {
|
foreach ($values as $id => $v) {
|
||||||
if ($v) {
|
if ($v) {
|
||||||
yield $id => parent::unserialize($v);
|
yield $id => $this->marshaller->unmarshall($v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -282,23 +286,12 @@ trait RedisTrait
|
||||||
*/
|
*/
|
||||||
protected function doSave(array $values, $lifetime)
|
protected function doSave(array $values, $lifetime)
|
||||||
{
|
{
|
||||||
$serialized = array();
|
if (!$values = $this->marshaller->marshall($values, $failed)) {
|
||||||
$failed = array();
|
|
||||||
|
|
||||||
foreach ($values as $id => $value) {
|
|
||||||
try {
|
|
||||||
$serialized[$id] = serialize($value);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$failed[] = $id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$serialized) {
|
|
||||||
return $failed;
|
return $failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
$results = $this->pipeline(function () use ($serialized, $lifetime) {
|
$results = $this->pipeline(function () use ($values, $lifetime) {
|
||||||
foreach ($serialized as $id => $value) {
|
foreach ($values as $id => $value) {
|
||||||
if (0 >= $lifetime) {
|
if (0 >= $lifetime) {
|
||||||
yield 'set' => array($id, $value);
|
yield 'set' => array($id, $value);
|
||||||
} else {
|
} else {
|
||||||
|
|
Reference in New Issue