[Cache] make PhpMarshaller handle hard references

This commit is contained in:
Nicolas Grekas 2018-08-10 10:45:13 +02:00
parent f96753b9ab
commit bc5d208584
30 changed files with 544 additions and 549 deletions

View File

@ -16,7 +16,6 @@ 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;
@ -35,7 +34,6 @@ 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
@ -106,9 +104,6 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
if ($value instanceof \Closure) { if ($value instanceof \Closure) {
return $value(); return $value();
} }
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
return ($this->marshaller ?? $this->marshaller = new DefaultMarshaller())->unmarshall($value);
}
} catch (\Throwable $e) { } catch (\Throwable $e) {
unset($this->keys[$key]); unset($this->keys[$key]);
goto get_from_pool; goto get_from_pool;
@ -144,13 +139,6 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
$value = null; $value = null;
$isHit = false; $isHit = false;
} }
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
$value = unserialize($value);
} catch (\Throwable $e) {
$value = null;
$isHit = false;
}
} }
$f = $this->createCacheItem; $f = $this->createCacheItem;
@ -284,12 +272,6 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
} catch (\Throwable $e) { } catch (\Throwable $e) {
yield $key => $f($key, null, false); yield $key => $f($key, null, false);
} }
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
yield $key => $f($key, $this->unserializeValue($value), true);
} catch (\Throwable $e) {
yield $key => $f($key, null, false);
}
} else { } else {
yield $key => $f($key, $value, true); yield $key => $f($key, $value, true);
} }

View File

@ -12,8 +12,10 @@
namespace Symfony\Component\Cache\Marshaller; namespace Symfony\Component\Cache\Marshaller;
use Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator; use Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator;
use Symfony\Component\Cache\Marshaller\PhpMarshaller\Marshaller;
use Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference; use Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference;
use Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry; use Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry;
use Symfony\Component\Cache\Marshaller\PhpMarshaller\Values;
/** /**
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
@ -27,14 +29,31 @@ use Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry;
*/ */
class PhpMarshaller class PhpMarshaller
{ {
public static function marshall($value, int &$objectsCount) public static function marshall($value, bool &$isStaticValue = null): string
{ {
if (!\is_object($value) && !\is_array($value)) { $isStaticValue = true;
return $value;
if (!\is_object($value) && !(\is_array($value) && $value) && !$value instanceof \__PHP_Incomplete_Class && !\is_resource($value)) {
return var_export($value, true);
} }
$objectsPool = new \SplObjectStorage(); $objectsPool = new \SplObjectStorage();
$value = array($value); $refsPool = array();
$objectsCount = self::doMarshall($value, $objectsPool); $objectsCount = 0;
try {
$value = Marshaller::marshall(array($value), $objectsPool, $refsPool, $objectsCount, $isStaticValue)[0];
} finally {
$references = array();
foreach ($refsPool as $i => $v) {
$v[0] = $v[1];
$references[1 + $i] = $v[2];
}
}
if ($isStaticValue) {
return var_export($value, true);
}
$classes = array(); $classes = array();
$values = array(); $values = array();
@ -46,6 +65,7 @@ class PhpMarshaller
} }
} }
ksort($wakeups); ksort($wakeups);
$properties = array(); $properties = array();
foreach ($values as $i => $vars) { foreach ($values as $i => $vars) {
foreach ($vars as $class => $values) { foreach ($vars as $class => $values) {
@ -54,131 +74,14 @@ class PhpMarshaller
} }
} }
} }
if (!$classes) {
return $value[0];
}
return new Configurator(new Registry($classes), $properties, $value[0], $wakeups); $value = new Configurator($classes ? new Registry($classes) : null, $references ? new Values($references) : null, $properties, $value, $wakeups);
} $value = var_export($value, true);
public static function optimize(string $exportedValue) $regexp = sprintf("{%s::__set_state\(array\(\s++'id' => %%s(\d+),\s++\)\)}", preg_quote(Reference::class));
{ $value = preg_replace(sprintf($regexp, ''), Registry::class.'::$objects[$1]', $value);
return preg_replace(sprintf("{%s::__set_state\(array\(\s++'0' => (\d+),\s++\)\)}", preg_quote(Reference::class)), Registry::class.'::$objects[$1]', $exportedValue); $value = preg_replace(sprintf($regexp, '-'), '&'.Registry::class.'::$references[$1]', $value);
}
private static function doMarshall(array &$array, \SplObjectStorage $objectsPool): int return $value;
{
$objectsCount = 0;
foreach ($array as &$value) {
if (\is_array($value) && $value) {
$objectsCount += self::doMarshall($value, $objectsPool);
}
if (!\is_object($value)) {
continue;
}
if (isset($objectsPool[$value])) {
++$objectsCount;
$value = new Reference($objectsPool[$value][0]);
continue;
}
$class = \get_class($value);
$properties = array();
$sleep = null;
$arrayValue = (array) $value;
$proto = (Registry::$reflectors[$class] ?? Registry::getClassReflector($class))->newInstanceWithoutConstructor();
if ($value instanceof \ArrayIterator || $value instanceof \ArrayObject) {
// ArrayIterator and ArrayObject need special care because their "flags"
// option changes the behavior of the (array) casting operator.
$reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject';
$reflector = Registry::$reflectors[$reflector] ?? Registry::getClassReflector($reflector);
$properties = array(
$arrayValue,
$reflector->getMethod('getFlags')->invoke($value),
$value instanceof \ArrayObject ? $reflector->getMethod('getIteratorClass')->invoke($value) : 'ArrayIterator',
);
$reflector = $reflector->getMethod('setFlags');
$reflector->invoke($proto, \ArrayObject::STD_PROP_LIST);
if ($properties[1] & \ArrayObject::STD_PROP_LIST) {
$reflector->invoke($value, 0);
$properties[0] = (array) $value;
} else {
$reflector->invoke($value, \ArrayObject::STD_PROP_LIST);
$arrayValue = (array) $value;
}
$reflector->invoke($value, $properties[1]);
if (array(array(), 0, 'ArrayIterator') === $properties) {
$properties = array();
} else {
if ('ArrayIterator' === $properties[2]) {
unset($properties[2]);
}
$properties = array($reflector->class => array("\0" => $properties));
}
} elseif ($value instanceof \SplObjectStorage) {
foreach (clone $value as $v) {
$properties[] = $v;
$properties[] = $value[$v];
}
$properties = array('SplObjectStorage' => array("\0" => $properties));
} elseif ($value instanceof \Serializable) {
++$objectsCount;
$objectsPool[$value] = array($id = \count($objectsPool), serialize($value), array(), 0);
$value = new Reference($id);
continue;
}
if (\method_exists($class, '__sleep')) {
if (!\is_array($sleep = $value->__sleep())) {
trigger_error('serialize(): __sleep should return an array only containing the names of instance-variables to serialize', E_USER_NOTICE);
$value = null;
continue;
}
$sleep = array_flip($sleep);
}
$proto = (array) $proto;
foreach ($arrayValue as $name => $v) {
$k = (string) $name;
if ('' === $k || "\0" !== $k[0]) {
$c = $class;
} elseif ('*' === $k[1]) {
$c = $class;
$k = substr($k, 3);
} else {
$i = strpos($k, "\0", 2);
$c = substr($k, 1, $i - 1);
$k = substr($k, 1 + $i);
}
if (null === $sleep) {
$properties[$c][$k] = $v;
} elseif (isset($sleep[$k]) && $c === $class) {
$properties[$c][$k] = $v;
unset($sleep[$k]);
}
if (\array_key_exists($name, $proto) && $proto[$name] === $v) {
unset($properties[$c][$k]);
}
}
if ($sleep) {
foreach ($sleep as $k => $v) {
trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $k), E_USER_NOTICE);
}
}
$objectsPool[$value] = array($id = \count($objectsPool));
$objectsCount += 1 + self::doMarshall($properties, $objectsPool);
$objectsPool[$value] = array($id, $class, $properties, \method_exists($class, '__wakeup') ? $objectsCount : 0);
$value = new Reference($id);
}
return $objectsCount;
} }
} }

View File

@ -20,19 +20,20 @@ class Configurator
{ {
public static $configurators = array(); public static $configurators = array();
public function __construct(Registry $registry, array $properties, $value, array $wakeups) public function __construct(?Registry $registry, ?Values $values, array $properties, $value, array $wakeups)
{ {
$this->{0} = $registry; $this->{0} = $registry;
$this->{1} = $properties; $this->{1} = $values;
$this->{2} = $value; $this->{2} = $properties;
$this->{3} = $wakeups; $this->{3} = $value;
$this->{4} = $wakeups;
} }
public static function __set_state($state) public static function __set_state($state)
{ {
$objects = Registry::$objects; $objects = Registry::$objects;
Registry::$objects = \array_pop(Registry::$stack); list(Registry::$objects, Registry::$references) = \array_pop(Registry::$stack);
list(, $properties, $value, $wakeups) = $state; list(, , $properties, $value, $wakeups) = $state;
foreach ($properties as $class => $vars) { foreach ($properties as $class => $vars) {
(self::$configurators[$class] ?? self::getConfigurator($class))($vars, $objects); (self::$configurators[$class] ?? self::getConfigurator($class))($vars, $objects);

View File

@ -0,0 +1,212 @@
<?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\PhpMarshaller;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class Marshaller
{
/**
* Prepares an array of values for PhpMarshaller.
*
* For performance this method is public and has no type-hints.
*
* @param array &$values
* @param \SplObjectStorage $objectsPool
* @param array &$refsPool
* @param int &$objectsCount
* @param bool &$valuesAreStatic
*
* @return int
*
* @throws \Exception When a value cannot be serialized
*
* @internal
*/
public static function marshall($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic)
{
$refs = $values;
foreach ($values as $k => $value) {
if (\is_resource($value)) {
throw new \Exception(sprintf("Serialization of '%s' resource is not allowed", \get_resource_type($value)));
}
$refs[$k] = $objectsPool;
if ($isRef = !$valueIsStatic = $values[$k] !== $objectsPool) {
$values[$k] = &$value; // Break hard references to make $values completely
unset($value); // independent from the original structure
$refs[$k] = $value = $values[$k];
if ($value instanceof Reference && 0 > $value->id) {
$valuesAreStatic = false;
continue;
}
$refsPool[] = array(&$refs[$k], $value, &$value);
$refs[$k] = $values[$k] = new Reference(-\count($refsPool));
}
if (\is_array($value)) {
if ($value) {
$value = self::marshall($value, $objectsPool, $refsPool, $objectsCount, $valueIsStatic);
}
goto handle_value;
} elseif (!\is_object($value) && !$value instanceof \__PHP_Incomplete_Class) {
goto handle_value;
}
$valueIsStatic = false;
if (isset($objectsPool[$value])) {
++$objectsCount;
$value = new Reference($objectsPool[$value][0]);
goto handle_value;
}
$class = \get_class($value);
$properties = array();
$sleep = null;
$arrayValue = (array) $value;
if (!isset(Registry::$prototypes[$class])) {
// Might throw Exception("Serialization of '...' is not allowed")
Registry::getClassReflector($class);
serialize(Registry::$prototypes[$class]);
}
$proto = Registry::$prototypes[$class];
if ($value instanceof \ArrayIterator || $value instanceof \ArrayObject) {
// ArrayIterator and ArrayObject need special care because their "flags"
// option changes the behavior of the (array) casting operator.
$proto = Registry::$cloneable[$class] ? clone Registry::$prototypes[$class] : Registry::$reflectors[$class]->newInstanceWithoutConstructor();
$properties = self::getArrayObjectProperties($value, $arrayValue, $proto);
} elseif ($value instanceof \SplObjectStorage) {
// By implementing Serializable, SplObjectStorage breaks internal references,
// let's deal with it on our own.
foreach (clone $value as $v) {
$properties[] = $v;
$properties[] = $value[$v];
}
$properties = array('SplObjectStorage' => array("\0" => $properties));
} elseif ($value instanceof \Serializable || $value instanceof \__PHP_Incomplete_Class) {
++$objectsCount;
$objectsPool[$value] = array($id = \count($objectsPool), serialize($value), array(), 0);
$value = new Reference($id);
goto handle_value;
}
if (\method_exists($class, '__sleep')) {
if (!\is_array($sleep = $value->__sleep())) {
trigger_error('serialize(): __sleep should return an array only containing the names of instance-variables to serialize', E_USER_NOTICE);
$value = null;
goto handle_value;
}
$sleep = array_flip($sleep);
}
$proto = (array) $proto;
foreach ($arrayValue as $name => $v) {
$n = (string) $name;
if ('' === $n || "\0" !== $n[0]) {
$c = $class;
} elseif ('*' === $n[1]) {
$c = $class;
$n = substr($n, 3);
} else {
$i = strpos($n, "\0", 2);
$c = substr($n, 1, $i - 1);
$n = substr($n, 1 + $i);
}
if (null === $sleep) {
$properties[$c][$n] = $v;
} elseif (isset($sleep[$n]) && $c === $class) {
$properties[$c][$n] = $v;
unset($sleep[$n]);
}
if (\array_key_exists($name, $proto) && $proto[$name] === $v) {
unset($properties[$c][$n]);
}
}
if ($sleep) {
foreach ($sleep as $n => $v) {
trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $n), E_USER_NOTICE);
}
}
$objectsPool[$value] = array($id = \count($objectsPool));
$properties = self::marshall($properties, $objectsPool, $refsPool, $objectsCount, $valueIsStatic);
++$objectsCount;
$objectsPool[$value] = array($id, $class, $properties, \method_exists($class, '__wakeup') ? $objectsCount : 0);
$value = new Reference($id);
handle_value:
if ($isRef) {
unset($value); // Break the hard reference created above
} elseif (!$valueIsStatic) {
$values[$k] = $value;
}
$valuesAreStatic = $valueIsStatic && $valuesAreStatic;
}
return $values;
}
/**
* Extracts the state of an ArrayIterator or ArrayObject instance.
*
* For performance this method is public and has no type-hints.
*
* @param \ArrayIterator|\ArrayObject $value
* @param array &$arrayValue
* @param object $proto
*
* @return array
*
* @internal
*/
public static function getArrayObjectProperties($value, &$arrayValue, $proto)
{
$reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject';
$reflector = Registry::$reflectors[$reflector] ?? Registry::getClassReflector($reflector);
$properties = array(
$arrayValue,
$reflector->getMethod('getFlags')->invoke($value),
$value instanceof \ArrayObject ? $reflector->getMethod('getIteratorClass')->invoke($value) : 'ArrayIterator',
);
$reflector = $reflector->getMethod('setFlags');
$reflector->invoke($proto, \ArrayObject::STD_PROP_LIST);
if ($properties[1] & \ArrayObject::STD_PROP_LIST) {
$reflector->invoke($value, 0);
$properties[0] = (array) $value;
} else {
$reflector->invoke($value, \ArrayObject::STD_PROP_LIST);
$arrayValue = (array) $value;
}
$reflector->invoke($value, $properties[1]);
if (array(array(), 0, 'ArrayIterator') === $properties) {
$properties = array();
} else {
if ('ArrayIterator' === $properties[2]) {
unset($properties[2]);
}
$properties = array($reflector->class => array("\0" => $properties));
}
return $properties;
}
}

View File

@ -18,13 +18,10 @@ namespace Symfony\Component\Cache\Marshaller\PhpMarshaller;
*/ */
class Reference class Reference
{ {
public $id;
public function __construct(int $id) public function __construct(int $id)
{ {
$this->{0} = $id; $this->id = $id;
}
public static function __set_state($state)
{
return Registry::$objects[$state[0]];
} }
} }

View File

@ -20,8 +20,11 @@ class Registry
{ {
public static $stack = array(); public static $stack = array();
public static $objects = array(); public static $objects = array();
public static $references = array();
public static $reflectors = array(); public static $reflectors = array();
public static $prototypes = array(); public static $prototypes = array();
public static $cloneable = array();
public static $instantiableWithoutConstructor = array();
public function __construct(array $classes) public function __construct(array $classes)
{ {
@ -32,15 +35,33 @@ class Registry
public static function __set_state($classes) public static function __set_state($classes)
{ {
self::$stack[] = self::$objects; $unserializeCallback = null;
self::$stack[] = array(self::$objects, self::$references);
self::$objects = $classes; self::$objects = $classes;
foreach (self::$objects as &$class) { self::$references = array();
if (isset(self::$prototypes[$class])) { try {
$class = clone self::$prototypes[$class]; foreach (self::$objects as &$class) {
} elseif (':' === ($class[1] ?? null)) { if (':' === ($class[1] ?? null)) {
$class = \unserialize($class); if (null === $unserializeCallback) {
} else { $unserializeCallback = ini_set('unserialize_callback_func', __CLASS__.'::getClassReflector');
$class = (self::$reflectors[$class] ?? self::getClassReflector($class))->newInstanceWithoutConstructor(); }
$class = \unserialize($class);
continue;
}
$r = self::$reflectors[$class] ?? self::getClassReflector($class);
if (self::$cloneable[$class]) {
$class = clone self::$prototypes[$class];
} else {
$class = self::$instantiableWithoutConstructor[$class] ? $r->newInstanceWithoutConstructor() : $r->newInstance();
}
}
} catch (\Throwable $e) {
list(self::$objects, self::$references) = \array_pop(self::$stack);
throw $e;
} finally {
if (null !== $unserializeCallback) {
ini_set('unserialize_callback_func', $unserializeCallback);
} }
} }
} }
@ -49,8 +70,38 @@ class Registry
{ {
$reflector = new \ReflectionClass($class); $reflector = new \ReflectionClass($class);
if (!$reflector->hasMethod('__clone')) { if (self::$instantiableWithoutConstructor[$class] = !$reflector->isFinal() || !$reflector->isInternal()) {
self::$prototypes[$class] = $reflector->newInstanceWithoutConstructor(); $proto = $reflector->newInstanceWithoutConstructor();
} else {
try {
$proto = $reflector->newInstance();
} catch (\Throwable $e) {
throw new \Exception(sprintf("Serialization of '%s' is not allowed", $class), 0, $e);
}
}
if ($proto instanceof \Reflector || $proto instanceof \ReflectionGenerator || $proto instanceof \ReflectionType) {
if (!$proto instanceof \Serializable && !\method_exists($proto, '__wakeup')) {
throw new \Exception(sprintf("Serialization of '%s' is not allowed", $class));
}
}
self::$prototypes[$class] = $proto;
self::$cloneable[$class] = !$reflector->hasMethod('__clone');
if ($proto instanceof \Throwable) {
static $trace;
if (null === $trace) {
$trace = array(
new \ReflectionProperty(\Error::class, 'trace'),
new \ReflectionProperty(\Exception::class, 'trace'),
);
$trace[0]->setAccessible(true);
$trace[1]->setAccessible(true);
}
$trace[$proto instanceof \Exception]->setValue($proto, array());
} }
return self::$reflectors[$class] = $reflector; return self::$reflectors[$class] = $reflector;

View File

@ -0,0 +1,34 @@
<?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\PhpMarshaller;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class Values
{
public function __construct(array $values)
{
foreach ($values as $i => $v) {
$this->$i = $v;
}
}
public static function __set_state($values)
{
foreach ($values as $i => $v) {
Registry::$references[$i] = $v;
}
}
}

View File

@ -13,7 +13,6 @@ 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\PruneableInterface; use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface; use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\PhpArrayTrait; use Symfony\Component\Cache\Traits\PhpArrayTrait;
@ -29,8 +28,6 @@ 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
@ -84,13 +81,6 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
return $default; return $default;
} }
} }
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
return ($this->marshaller ?? $this->marshaller = new DefaultMarshaller())->unmarshall($value);
} catch (\Throwable $e) {
return $default;
}
}
return $value; return $value;
} }
@ -243,12 +233,6 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
} catch (\Throwable $e) { } catch (\Throwable $e) {
yield $key => $default; yield $key => $default;
} }
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
yield $key => unserialize($value);
} catch (\Throwable $e) {
yield $key => $default;
}
} else { } else {
yield $key => $value; yield $key => $value;
} }

View File

@ -3,7 +3,8 @@
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'ArrayIterator', '0' => 'ArrayIterator',
)), )),
'1' => '1' => NULL,
'2' =>
array ( array (
'ArrayIterator' => 'ArrayIterator' =>
array ( array (
@ -20,11 +21,9 @@
), ),
), ),
), ),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' => '3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
array ( array (
), ),
)); ));

View File

@ -1,28 +0,0 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyArrayObject',
)),
'1' =>
array (
'ArrayObject' =>
array (
'' . "\0" . '' =>
array (
0 =>
array (
0 =>
array (
0 => 234,
),
1 => 1,
),
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'3' =>
array (
),
));

View File

@ -3,7 +3,8 @@
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyArrayObject', '0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyArrayObject',
)), )),
'1' => '1' => NULL,
'2' =>
array ( array (
'ArrayObject' => 'ArrayObject' =>
array ( array (
@ -20,11 +21,9 @@
), ),
), ),
), ),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' => '3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
array ( array (
), ),
)); ));

View File

@ -1,36 +0,0 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'ArrayObject',
'1' => 'ArrayObject',
)),
'1' =>
array (
'ArrayObject' =>
array (
'' . "\0" . '' =>
array (
0 =>
array (
0 =>
array (
0 => 1,
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
),
1 => 0,
),
),
'foo' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'3' =>
array (
),
));

View File

@ -4,7 +4,8 @@
'0' => 'ArrayObject', '0' => 'ArrayObject',
'1' => 'ArrayObject', '1' => 'ArrayObject',
)), )),
'1' => '1' => NULL,
'2' =>
array ( array (
'ArrayObject' => 'ArrayObject' =>
array ( array (
@ -16,9 +17,7 @@
array ( array (
0 => 1, 0 => 1,
1 => 1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'0' => 0,
)),
), ),
1 => 0, 1 => 0,
), ),
@ -26,17 +25,13 @@
'foo' => 'foo' =>
array ( array (
0 => 0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
'0' => 1,
)),
), ),
), ),
), ),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' => '3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
array ( array (
), ),
)); ));

View File

@ -1,20 +0,0 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyCloneable',
'1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyNotCloneable',
)),
'1' =>
array (
),
'2' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
),
'3' =>
array (
),
));

View File

@ -4,21 +4,18 @@
'0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyCloneable', '0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyCloneable',
'1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyNotCloneable', '1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyNotCloneable',
)), )),
'1' => '1' => NULL,
array (
),
'2' => '2' =>
array ( array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 1,
)),
), ),
'3' => '3' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
),
'4' =>
array ( array (
), ),
)); ));

View File

@ -1,30 +0,0 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'DateTime',
)),
'1' =>
array (
'DateTime' =>
array (
'date' =>
array (
0 => '1970-01-01 00:00:00.000000',
),
'timezone_type' =>
array (
0 => 1,
),
'timezone' =>
array (
0 => '+00:00',
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'3' =>
array (
1 => 0,
),
));

View File

@ -3,7 +3,8 @@
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'DateTime', '0' => 'DateTime',
)), )),
'1' => '1' => NULL,
'2' =>
array ( array (
'DateTime' => 'DateTime' =>
array ( array (
@ -21,11 +22,9 @@
), ),
), ),
), ),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' => '3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
array ( array (
1 => 0, 1 => 0,
), ),

View File

@ -0,0 +1,22 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' => NULL,
'1' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Values::__set_state(array(
'1' =>
array (
0 =>
&Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$references[1],
),
)),
'2' =>
array (
),
'3' =>
array (
0 =>
&Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$references[1],
),
'4' =>
array (
),
));

View File

@ -1,19 +1,26 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array( <?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' => '0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'C:55:"Symfony\\Component\\Cache\\Tests\\Marshaller\\MySerializable":3:{123}', '0' => 'stdClass',
)), )),
'1' => '1' =>
array ( Symfony\Component\Cache\Marshaller\PhpMarshaller\Values::__set_state(array(
), '1' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
)),
'2' => '2' =>
array ( array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
), ),
'3' => '3' =>
array (
0 =>
&Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$references[1],
1 =>
&Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$references[1],
2 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
),
'4' =>
array ( array (
), ),
)); ));

View File

@ -1,28 +1,15 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array( <?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' => '0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'ArrayIterator', '0' => 'O:20:"SomeNotExistingClass":0:{}',
)), )),
'1' => '1' => NULL,
array (
'ArrayIterator' =>
array (
'' . "\0" . '' =>
array (
0 =>
array (
0 =>
array (
0 => 123,
),
1 => 1,
),
),
),
),
'2' => '2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0], array (
),
'3' => '3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
array ( array (
), ),
)); ));

View File

@ -1,39 +0,0 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateValue',
'1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateChildValue',
)),
'1' =>
array (
'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateValue' =>
array (
'prot' =>
array (
0 => 123,
),
'priv' =>
array (
0 => 234,
1 => 234,
),
),
'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateChildValue' =>
array (
'prot' =>
array (
1 => 123,
),
),
),
'2' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
),
'3' =>
array (
),
));

View File

@ -4,7 +4,8 @@
'0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateValue', '0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateValue',
'1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateChildValue', '1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateChildValue',
)), )),
'1' => '1' => NULL,
'2' =>
array ( array (
'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateValue' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateValue' =>
array ( array (
@ -26,18 +27,14 @@
), ),
), ),
), ),
'2' => '3' =>
array ( array (
0 => 0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'0' => 0,
)),
1 => 1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
'0' => 1,
)),
), ),
'3' => '4' =>
array ( array (
), ),
)); ));

View File

@ -3,21 +3,18 @@
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array( Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'C:55:"Symfony\\Component\\Cache\\Tests\\Marshaller\\MySerializable":3:{123}', '0' => 'C:55:"Symfony\\Component\\Cache\\Tests\\Marshaller\\MySerializable":3:{123}',
)), )),
'1' => '1' => NULL,
array (
),
'2' => '2' =>
array ( array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
), ),
'3' => '3' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
),
'4' =>
array ( array (
), ),
)); ));

View File

@ -1,27 +0,0 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'SplObjectStorage',
'1' => 'stdClass',
)),
'1' =>
array (
'SplObjectStorage' =>
array (
'' . "\0" . '' =>
array (
0 =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
1 => 345,
),
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'3' =>
array (
),
));

View File

@ -4,7 +4,8 @@
'0' => 'SplObjectStorage', '0' => 'SplObjectStorage',
'1' => 'stdClass', '1' => 'stdClass',
)), )),
'1' => '1' => NULL,
'2' =>
array ( array (
'SplObjectStorage' => 'SplObjectStorage' =>
array ( array (
@ -13,19 +14,15 @@
0 => 0 =>
array ( array (
0 => 0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
'0' => 1,
)),
1 => 345, 1 => 345,
), ),
), ),
), ),
), ),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' => '3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
array ( array (
), ),
)); ));

View File

@ -1,30 +0,0 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup',
'1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup',
)),
'1' =>
array (
'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup' =>
array (
'sub' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
1 => 123,
),
'baz' =>
array (
1 => 123,
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'3' =>
array (
1 => 1,
2 => 0,
),
));

View File

@ -4,16 +4,15 @@
'0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup', '0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup',
'1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup', '1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup',
)), )),
'1' => '1' => NULL,
'2' =>
array ( array (
'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup' =>
array ( array (
'sub' => 'sub' =>
array ( array (
0 => 0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array( Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
'0' => 1,
)),
1 => 123, 1 => 123,
), ),
'baz' => 'baz' =>
@ -22,11 +21,9 @@
), ),
), ),
), ),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' => '3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
array ( array (
1 => 1, 1 => 1,
2 => 0, 2 => 0,

View File

@ -13,35 +13,82 @@ namespace Symfony\Component\Cache\Tests\Marshaller;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Marshaller\PhpMarshaller; use Symfony\Component\Cache\Marshaller\PhpMarshaller;
use Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait; use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
class DoctrineProviderTest extends TestCase class DoctrineProviderTest extends TestCase
{ {
use VarDumperTestTrait; use VarDumperTestTrait;
/**
* @expectedException \ReflectionException
* @expectedExceptionMessage Class SomeNotExistingClass does not exist
*/
public function testPhpIncompleteClassesAreForbidden()
{
$unserializeCallback = ini_set('unserialize_callback_func', 'var_dump');
try {
Registry::__set_state(array('O:20:"SomeNotExistingClass":0:{}'));
} finally {
$this->assertSame('var_dump', ini_set('unserialize_callback_func', $unserializeCallback));
}
}
/**
* @dataProvider provideFailingSerialization
* @expectedException \Exception
* @expectedExceptionMessageRegexp Serialization of '.*' is not allowed
*/
public function testFailingSerialization($value)
{
$expectedDump = $this->getDump($value);
try {
PhpMarshaller::marshall($value);
} finally {
$this->assertDumpEquals(rtrim($expectedDump), $value);
}
}
public function provideFailingSerialization()
{
yield array(hash_init('md5'));
yield array(new \ReflectionClass('stdClass'));
yield array((new \ReflectionFunction(function (): int {}))->getReturnType());
yield array(new \ReflectionGenerator((function () { yield 123; })()));
yield array(function () {});
yield array(function () { yield 123; });
yield array(new \SplFileInfo(__FILE__));
yield array($h = fopen(__FILE__, 'r'));
yield array(array($h));
$a = array(null, $h);
$a[0] = &$a;
yield array($a);
}
/** /**
* @dataProvider provideMarshall * @dataProvider provideMarshall
*/ */
public function testMarshall(string $testName, $value, int $expectedObjectsCount) public function testMarshall(string $testName, $value, bool $staticValueExpected = false)
{ {
$objectsCount = 0; $serializedValue = serialize($value);
$marshalledValue = PhpMarshaller::marshall($value, $objectsCount); $isStaticValue = true;
$marshalledValue = PhpMarshaller::marshall($value, $isStaticValue);
$this->assertSame($expectedObjectsCount, $objectsCount); $this->assertSame($staticValueExpected, $isStaticValue);
$this->assertSame($serializedValue, serialize($value));
$dump = '<?php return '.var_export($marshalledValue, true).";\n"; $dump = '<?php return '.$marshalledValue.";\n";
$fixtureFile = __DIR__.'/Fixtures/'.$testName.'.php'; $fixtureFile = __DIR__.'/Fixtures/'.$testName.'.php';
$this->assertStringEqualsFile($fixtureFile, $dump); $this->assertStringEqualsFile($fixtureFile, $dump);
if ($objectsCount) { if ('incomplete-class' === $testName) {
$marshalledValue = include $fixtureFile; return;
$this->assertDumpEquals($value, $marshalledValue); }
$marshalledValue = include $fixtureFile;
$dump = PhpMarshaller::optimize($dump); if (!$isStaticValue) {
$fixtureFile = __DIR__.'/Fixtures/'.$testName.'.optimized.php';
$this->assertStringEqualsFile($fixtureFile, $dump);
$marshalledValue = include $fixtureFile;
$this->assertDumpEquals($value, $marshalledValue); $this->assertDumpEquals($value, $marshalledValue);
} else { } else {
$this->assertSame($value, $marshalledValue); $this->assertSame($value, $marshalledValue);
@ -50,23 +97,23 @@ class DoctrineProviderTest extends TestCase
public function provideMarshall() public function provideMarshall()
{ {
yield array('bool', true, 0); yield array('bool', true, true);
yield array('simple-array', array(123, array('abc')), 0); yield array('simple-array', array(123, array('abc')), true);
yield array('datetime', \DateTime::createFromFormat('U', 0), 1); yield array('datetime', \DateTime::createFromFormat('U', 0));
$value = new \ArrayObject(); $value = new \ArrayObject();
$value[0] = 1; $value[0] = 1;
$value->foo = new \ArrayObject(); $value->foo = new \ArrayObject();
$value[1] = $value; $value[1] = $value;
yield array('array-object', $value, 3); yield array('array-object', $value);
yield array('array-iterator', new \ArrayIterator(array(123), 1), 1); yield array('array-iterator', new \ArrayIterator(array(123), 1));
yield array('array-object-custom', new MyArrayObject(array(234)), 1); yield array('array-object-custom', new MyArrayObject(array(234)));
$value = new MySerializable(); $value = new MySerializable();
yield array('serializable', array($value, $value), 2); yield array('serializable', array($value, $value));
$value = new MyWakeup(); $value = new MyWakeup();
$value->sub = new MyWakeup(); $value->sub = new MyWakeup();
@ -74,16 +121,29 @@ class DoctrineProviderTest extends TestCase
$value->sub->bis = 123; $value->sub->bis = 123;
$value->sub->baz = 123; $value->sub->baz = 123;
yield array('wakeup', $value, 2); yield array('wakeup', $value);
yield array('clone', array(new MyCloneable(), new MyNotCloneable()), 2); yield array('clone', array(new MyCloneable(), new MyNotCloneable()));
yield array('private', array(new MyPrivateValue(123, 234), new MyPrivateChildValue(123, 234)), 2); yield array('private', array(new MyPrivateValue(123, 234), new MyPrivateChildValue(123, 234)));
$value = new \SplObjectStorage(); $value = new \SplObjectStorage();
$value[new \stdClass()] = 345; $value[new \stdClass()] = 345;
yield array('spl-object-storage', $value, 2); yield array('spl-object-storage', $value);
yield array('incomplete-class', unserialize('O:20:"SomeNotExistingClass":0:{}'));
$value = array((object) array());
$value[1] = &$value[0];
$value[2] = $value[0];
yield array('hard-references', $value);
$value = array();
$value[0] = &$value;
yield array('hard-references-recursive', $value);
} }
} }

View File

@ -70,33 +70,29 @@ EOF;
foreach ($values as $key => $value) { foreach ($values as $key => $value) {
CacheItem::validateKey(\is_int($key) ? (string) $key : $key); CacheItem::validateKey(\is_int($key) ? (string) $key : $key);
$objectsCount = 0; $isStaticValue = true;
if (null === $value) { if (null === $value) {
$value = 'N;'; $value = "'N;'";
} elseif (\is_object($value) || \is_array($value)) { } elseif (\is_object($value) || \is_array($value)) {
try { try {
$e = null; $value = PhpMarshaller::marshall($value, $isStaticValue);
$serialized = serialize($value);
} catch (\Exception $e) { } catch (\Exception $e) {
}
if (null !== $e || false === $serialized) {
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e); throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e);
} }
// Keep value serialized if it contains any internal references
$value = false !== strpos($serialized, ';R:') ? $serialized : PhpMarshaller::marshall($value, $objectsCount);
} elseif (\is_string($value)) { } elseif (\is_string($value)) {
// Wrap strings if they could be confused with serialized objects or arrays // Wrap "N;" in a closure to not confuse it with an encoded `null`
if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) { if ('N;' === $value) {
++$objectsCount; $isStaticValue = false;
} }
$value = var_export($value, true);
} elseif (!\is_scalar($value)) { } elseif (!\is_scalar($value)) {
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \gettype($value))); throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \gettype($value)));
} else {
$value = var_export($value, true);
} }
$value = var_export($value, true); if (!$isStaticValue) {
if ($objectsCount) {
$value = PhpMarshaller::optimize($value);
$value = "static function () {\nreturn {$value};\n}"; $value = "static function () {\nreturn {$value};\n}";
} }
$hash = hash('md5', $value); $hash = hash('md5', $value);

View File

@ -13,7 +13,6 @@ 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;
/** /**
@ -30,7 +29,6 @@ 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();
@ -92,8 +90,6 @@ trait PhpFilesTrait
$values[$id] = null; $values[$id] = null;
} elseif ($value instanceof \Closure) { } elseif ($value instanceof \Closure) {
$values[$id] = $value(); $values[$id] = $value();
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
$values[$id] = ($this->marshaller ?? $this->marshaller = new DefaultMarshaller())->unmarshall($value);
} else { } else {
$values[$id] = $value; $values[$id] = $value;
} }
@ -165,32 +161,28 @@ trait PhpFilesTrait
foreach ($values as $key => $value) { foreach ($values as $key => $value) {
unset($this->values[$key]); unset($this->values[$key]);
$objectsCount = 0; $isStaticValue = true;
if (null === $value) { if (null === $value) {
$value = 'N;'; $value = "'N;'";
} elseif (\is_object($value) || \is_array($value)) { } elseif (\is_object($value) || \is_array($value)) {
try { try {
$e = null; $value = PhpMarshaller::marshall($value, $isStaticValue);
$serialized = serialize($value);
} catch (\Exception $e) { } catch (\Exception $e) {
}
if (null !== $e || false === $serialized) {
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e); throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e);
} }
// Keep value serialized if it contains any internal references
$value = false !== strpos($serialized, ';R:') ? $serialized : PhpMarshaller::marshall($value, $objectsCount);
} elseif (\is_string($value)) { } elseif (\is_string($value)) {
// Wrap strings if they could be confused with serialized objects or arrays // Wrap "N;" in a closure to not confuse it with an encoded `null`
if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) { if ('N;' === $value) {
++$objectsCount; $isStaticValue = false;
} }
$value = var_export($value, true);
} elseif (!\is_scalar($value)) { } elseif (!\is_scalar($value)) {
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \gettype($value))); throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \gettype($value)));
} else {
$value = var_export($value, true);
} }
$value = var_export($value, true); if (!$isStaticValue) {
if ($objectsCount) {
$value = PhpMarshaller::optimize($value);
$value = "static function () {\n\nreturn {$value};\n\n}"; $value = "static function () {\n\nreturn {$value};\n\n}";
} }