[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\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\GetTrait;
@ -35,7 +34,6 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
use GetTrait;
private $createCacheItem;
private $marshaller;
/**
* @param string $file The PHP file were values are cached
@ -106,9 +104,6 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
if ($value instanceof \Closure) {
return $value();
}
if (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
return ($this->marshaller ?? $this->marshaller = new DefaultMarshaller())->unmarshall($value);
}
} catch (\Throwable $e) {
unset($this->keys[$key]);
goto get_from_pool;
@ -144,13 +139,6 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
$value = null;
$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;
@ -284,12 +272,6 @@ class PhpArrayAdapter implements AdapterInterface, CacheInterface, PruneableInte
} catch (\Throwable $e) {
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 {
yield $key => $f($key, $value, true);
}

View File

@ -12,8 +12,10 @@
namespace Symfony\Component\Cache\Marshaller;
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\Registry;
use Symfony\Component\Cache\Marshaller\PhpMarshaller\Values;
/**
* @author Nicolas Grekas <p@tchwork.com>
@ -27,14 +29,31 @@ use Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry;
*/
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)) {
return $value;
$isStaticValue = true;
if (!\is_object($value) && !(\is_array($value) && $value) && !$value instanceof \__PHP_Incomplete_Class && !\is_resource($value)) {
return var_export($value, true);
}
$objectsPool = new \SplObjectStorage();
$value = array($value);
$objectsCount = self::doMarshall($value, $objectsPool);
$refsPool = array();
$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();
$values = array();
@ -46,6 +65,7 @@ class PhpMarshaller
}
}
ksort($wakeups);
$properties = array();
foreach ($values as $i => $vars) {
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)
{
return preg_replace(sprintf("{%s::__set_state\(array\(\s++'0' => (\d+),\s++\)\)}", preg_quote(Reference::class)), Registry::class.'::$objects[$1]', $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);
$value = preg_replace(sprintf($regexp, '-'), '&'.Registry::class.'::$references[$1]', $value);
private static function doMarshall(array &$array, \SplObjectStorage $objectsPool): int
{
$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;
return $value;
}
}

View File

@ -20,19 +20,20 @@ class Configurator
{
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->{1} = $properties;
$this->{2} = $value;
$this->{3} = $wakeups;
$this->{1} = $values;
$this->{2} = $properties;
$this->{3} = $value;
$this->{4} = $wakeups;
}
public static function __set_state($state)
{
$objects = Registry::$objects;
Registry::$objects = \array_pop(Registry::$stack);
list(, $properties, $value, $wakeups) = $state;
list(Registry::$objects, Registry::$references) = \array_pop(Registry::$stack);
list(, , $properties, $value, $wakeups) = $state;
foreach ($properties as $class => $vars) {
(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
{
public $id;
public function __construct(int $id)
{
$this->{0} = $id;
}
public static function __set_state($state)
{
return Registry::$objects[$state[0]];
$this->id = $id;
}
}

View File

@ -20,8 +20,11 @@ class Registry
{
public static $stack = array();
public static $objects = array();
public static $references = array();
public static $reflectors = array();
public static $prototypes = array();
public static $cloneable = array();
public static $instantiableWithoutConstructor = array();
public function __construct(array $classes)
{
@ -32,15 +35,33 @@ class Registry
public static function __set_state($classes)
{
self::$stack[] = self::$objects;
$unserializeCallback = null;
self::$stack[] = array(self::$objects, self::$references);
self::$objects = $classes;
foreach (self::$objects as &$class) {
if (isset(self::$prototypes[$class])) {
$class = clone self::$prototypes[$class];
} elseif (':' === ($class[1] ?? null)) {
$class = \unserialize($class);
} else {
$class = (self::$reflectors[$class] ?? self::getClassReflector($class))->newInstanceWithoutConstructor();
self::$references = array();
try {
foreach (self::$objects as &$class) {
if (':' === ($class[1] ?? null)) {
if (null === $unserializeCallback) {
$unserializeCallback = ini_set('unserialize_callback_func', __CLASS__.'::getClassReflector');
}
$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);
if (!$reflector->hasMethod('__clone')) {
self::$prototypes[$class] = $reflector->newInstanceWithoutConstructor();
if (self::$instantiableWithoutConstructor[$class] = !$reflector->isFinal() || !$reflector->isInternal()) {
$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;

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 Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
use Symfony\Component\Cache\PruneableInterface;
use Symfony\Component\Cache\ResettableInterface;
use Symfony\Component\Cache\Traits\PhpArrayTrait;
@ -29,8 +28,6 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
{
use PhpArrayTrait;
private $marshaller;
/**
* @param string $file The PHP file were values are cached
* @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;
}
}
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;
}
@ -243,12 +233,6 @@ class PhpArrayCache implements CacheInterface, PruneableInterface, ResettableInt
} catch (\Throwable $e) {
yield $key => $default;
}
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
try {
yield $key => unserialize($value);
} catch (\Throwable $e) {
yield $key => $default;
}
} else {
yield $key => $value;
}

View File

@ -3,7 +3,8 @@
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'ArrayIterator',
)),
'1' =>
'1' => NULL,
'2' =>
array (
'ArrayIterator' =>
array (
@ -20,11 +21,9 @@
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
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(
'0' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyArrayObject',
)),
'1' =>
'1' => NULL,
'2' =>
array (
'ArrayObject' =>
array (
@ -20,11 +21,9 @@
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
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',
'1' => 'ArrayObject',
)),
'1' =>
'1' => NULL,
'2' =>
array (
'ArrayObject' =>
array (
@ -16,9 +17,7 @@
array (
0 => 1,
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
),
1 => 0,
),
@ -26,17 +25,13 @@
'foo' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 1,
)),
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
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',
'1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyNotCloneable',
)),
'1' =>
array (
),
'1' => NULL,
'2' =>
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' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
),
'4' =>
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(
'0' => 'DateTime',
)),
'1' =>
'1' => NULL,
'2' =>
array (
'DateTime' =>
array (
@ -21,11 +22,9 @@
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
array (
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(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'C:55:"Symfony\\Component\\Cache\\Tests\\Marshaller\\MySerializable":3:{123}',
'0' => 'stdClass',
)),
'1' =>
array (
),
Symfony\Component\Cache\Marshaller\PhpMarshaller\Values::__set_state(array(
'1' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
)),
'2' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
),
'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 (
),
));

View File

@ -1,28 +1,15 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'ArrayIterator',
'0' => 'O:20:"SomeNotExistingClass":0:{}',
)),
'1' =>
array (
'ArrayIterator' =>
array (
'' . "\0" . '' =>
array (
0 =>
array (
0 =>
array (
0 => 123,
),
1 => 1,
),
),
),
),
'1' => NULL,
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
array (
),
'3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
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',
'1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateChildValue',
)),
'1' =>
'1' => NULL,
'2' =>
array (
'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyPrivateValue' =>
array (
@ -26,18 +27,14 @@
),
),
),
'2' =>
'3' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 1,
)),
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
),
'3' =>
'4' =>
array (
),
));

View File

@ -3,21 +3,18 @@
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'C:55:"Symfony\\Component\\Cache\\Tests\\Marshaller\\MySerializable":3:{123}',
)),
'1' =>
array (
),
'1' => NULL,
'2' =>
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' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
1 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
),
'4' =>
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',
'1' => 'stdClass',
)),
'1' =>
'1' => NULL,
'2' =>
array (
'SplObjectStorage' =>
array (
@ -13,19 +14,15 @@
0 =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 1,
)),
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
1 => 345,
),
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
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',
'1' => 'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup',
)),
'1' =>
'1' => NULL,
'2' =>
array (
'Symfony\\Component\\Cache\\Tests\\Marshaller\\MyWakeup' =>
array (
'sub' =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 1,
)),
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
1 => 123,
),
'baz' =>
@ -22,11 +21,9 @@
),
),
),
'2' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Reference::__set_state(array(
'0' => 0,
)),
'3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
array (
1 => 1,
2 => 0,

View File

@ -13,35 +13,82 @@ namespace Symfony\Component\Cache\Tests\Marshaller;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Marshaller\PhpMarshaller;
use Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
class DoctrineProviderTest extends TestCase
{
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
*/
public function testMarshall(string $testName, $value, int $expectedObjectsCount)
public function testMarshall(string $testName, $value, bool $staticValueExpected = false)
{
$objectsCount = 0;
$marshalledValue = PhpMarshaller::marshall($value, $objectsCount);
$serializedValue = serialize($value);
$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';
$this->assertStringEqualsFile($fixtureFile, $dump);
if ($objectsCount) {
$marshalledValue = include $fixtureFile;
$this->assertDumpEquals($value, $marshalledValue);
if ('incomplete-class' === $testName) {
return;
}
$marshalledValue = include $fixtureFile;
$dump = PhpMarshaller::optimize($dump);
$fixtureFile = __DIR__.'/Fixtures/'.$testName.'.optimized.php';
$this->assertStringEqualsFile($fixtureFile, $dump);
$marshalledValue = include $fixtureFile;
if (!$isStaticValue) {
$this->assertDumpEquals($value, $marshalledValue);
} else {
$this->assertSame($value, $marshalledValue);
@ -50,23 +97,23 @@ class DoctrineProviderTest extends TestCase
public function provideMarshall()
{
yield array('bool', true, 0);
yield array('simple-array', array(123, array('abc')), 0);
yield array('datetime', \DateTime::createFromFormat('U', 0), 1);
yield array('bool', true, true);
yield array('simple-array', array(123, array('abc')), true);
yield array('datetime', \DateTime::createFromFormat('U', 0));
$value = new \ArrayObject();
$value[0] = 1;
$value->foo = new \ArrayObject();
$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-object-custom', new MyArrayObject(array(234)), 1);
yield array('array-iterator', new \ArrayIterator(array(123), 1));
yield array('array-object-custom', new MyArrayObject(array(234)));
$value = new MySerializable();
yield array('serializable', array($value, $value), 2);
yield array('serializable', array($value, $value));
$value = new MyWakeup();
$value->sub = new MyWakeup();
@ -74,16 +121,29 @@ class DoctrineProviderTest extends TestCase
$value->sub->bis = 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 \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) {
CacheItem::validateKey(\is_int($key) ? (string) $key : $key);
$objectsCount = 0;
$isStaticValue = true;
if (null === $value) {
$value = 'N;';
$value = "'N;'";
} elseif (\is_object($value) || \is_array($value)) {
try {
$e = null;
$serialized = serialize($value);
$value = PhpMarshaller::marshall($value, $isStaticValue);
} 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);
}
// Keep value serialized if it contains any internal references
$value = false !== strpos($serialized, ';R:') ? $serialized : PhpMarshaller::marshall($value, $objectsCount);
} elseif (\is_string($value)) {
// Wrap strings if they could be confused with serialized objects or arrays
if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
++$objectsCount;
// Wrap "N;" in a closure to not confuse it with an encoded `null`
if ('N;' === $value) {
$isStaticValue = false;
}
$value = var_export($value, true);
} elseif (!\is_scalar($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 ($objectsCount) {
$value = PhpMarshaller::optimize($value);
if (!$isStaticValue) {
$value = "static function () {\nreturn {$value};\n}";
}
$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\InvalidArgumentException;
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
use Symfony\Component\Cache\Marshaller\PhpMarshaller;
/**
@ -30,7 +29,6 @@ trait PhpFilesTrait
doDelete as private doCommonDelete;
}
private $marshaller;
private $includeHandler;
private $appendOnly;
private $values = array();
@ -92,8 +90,6 @@ trait PhpFilesTrait
$values[$id] = null;
} elseif ($value instanceof \Closure) {
$values[$id] = $value();
} elseif (\is_string($value) && isset($value[2]) && ':' === $value[1]) {
$values[$id] = ($this->marshaller ?? $this->marshaller = new DefaultMarshaller())->unmarshall($value);
} else {
$values[$id] = $value;
}
@ -165,32 +161,28 @@ trait PhpFilesTrait
foreach ($values as $key => $value) {
unset($this->values[$key]);
$objectsCount = 0;
$isStaticValue = true;
if (null === $value) {
$value = 'N;';
$value = "'N;'";
} elseif (\is_object($value) || \is_array($value)) {
try {
$e = null;
$serialized = serialize($value);
$value = PhpMarshaller::marshall($value, $isStaticValue);
} 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);
}
// Keep value serialized if it contains any internal references
$value = false !== strpos($serialized, ';R:') ? $serialized : PhpMarshaller::marshall($value, $objectsCount);
} elseif (\is_string($value)) {
// Wrap strings if they could be confused with serialized objects or arrays
if ('N;' === $value || (isset($value[2]) && ':' === $value[1])) {
++$objectsCount;
// Wrap "N;" in a closure to not confuse it with an encoded `null`
if ('N;' === $value) {
$isStaticValue = false;
}
$value = var_export($value, true);
} elseif (!\is_scalar($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 ($objectsCount) {
$value = PhpMarshaller::optimize($value);
if (!$isStaticValue) {
$value = "static function () {\n\nreturn {$value};\n\n}";
}