[VarExporter] a new component to serialize values to plain PHP code

This commit is contained in:
Nicolas Grekas 2018-08-19 20:28:31 +02:00
parent 2df7320c1c
commit 7831ad75e5
47 changed files with 651 additions and 433 deletions

View File

@ -24,7 +24,6 @@ return PhpCsFixer\Config::create()
->exclude(array(
'Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/Fixtures',
// directories containing files with content that is autogenerated by `var_export`, which breaks CS in output code
'Symfony/Component/Cache/Tests/Marshaller/Fixtures',
'Symfony/Component/DependencyInjection/Tests/Fixtures',
'Symfony/Component/Routing/Tests/Fixtures/dumper',
// fixture templates
@ -33,6 +32,7 @@ return PhpCsFixer\Config::create()
'Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Custom',
// generated fixtures
'Symfony/Component/VarDumper/Tests/Fixtures',
'Symfony/Component/VarExporter/Tests/Fixtures',
// resource templates
'Symfony/Bundle/FrameworkBundle/Resources/views/Form',
// explicit trigger_error tests

View File

@ -81,6 +81,7 @@
"symfony/twig-bundle": "self.version",
"symfony/validator": "self.version",
"symfony/var-dumper": "self.version",
"symfony/var-exporter": "self.version",
"symfony/web-link": "self.version",
"symfony/web-profiler-bundle": "self.version",
"symfony/web-server-bundle": "self.version",

View File

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

View File

@ -1,29 +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' => NULL,
'2' =>
array (
'ArrayObject' =>
array (
'' . "\0" . '' =>
array (
0 =>
array (
0 =>
array (
0 => 234,
),
1 => 1,
),
),
),
),
'3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
array (
),
));

View File

@ -1,37 +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' => NULL,
'2' =>
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],
),
),
),
'3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
array (
),
));

View File

@ -1 +0,0 @@
<?php return true;

View File

@ -1,21 +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' => NULL,
'2' =>
array (
),
'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,31 +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' => NULL,
'2' =>
array (
'DateTime' =>
array (
'date' =>
array (
0 => '1970-01-01 00:00:00.000000',
),
'timezone_type' =>
array (
0 => 1,
),
'timezone' =>
array (
0 => '+00:00',
),
),
),
'3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
array (
1 => 0,
),
));

View File

@ -1,22 +0,0 @@
<?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,26 +0,0 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'stdClass',
)),
'1' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Values::__set_state(array(
'1' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
)),
'2' =>
array (
),
'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,15 +0,0 @@
<?php return Symfony\Component\Cache\Marshaller\PhpMarshaller\Configurator::__set_state(array(
'0' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::__set_state(array(
'0' => 'O:20:"SomeNotExistingClass":0:{}',
)),
'1' => NULL,
'2' =>
array (
),
'3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
array (
),
));

View File

@ -1,40 +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' => NULL,
'2' =>
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,
),
),
),
'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,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' => 'C:55:"Symfony\\Component\\Cache\\Tests\\Marshaller\\MySerializable":3:{123}',
)),
'1' => NULL,
'2' =>
array (
),
'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,7 +0,0 @@
<?php return array (
0 => 123,
1 =>
array (
0 => 'abc',
),
);

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' => 'SplObjectStorage',
'1' => 'stdClass',
)),
'1' => NULL,
'2' =>
array (
'SplObjectStorage' =>
array (
'' . "\0" . '' =>
array (
0 =>
array (
0 =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[1],
1 => 345,
),
),
),
),
'3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
array (
),
));

View File

@ -1,31 +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' => NULL,
'2' =>
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,
),
),
),
'3' =>
Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry::$objects[0],
'4' =>
array (
1 => 1,
2 => 0,
),
));

View File

@ -13,7 +13,7 @@ namespace Symfony\Component\Cache\Traits;
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Marshaller\PhpMarshaller;
use Symfony\Component\VarExporter\VarExporter;
/**
* @author Titouan Galopin <galopintitouan@gmail.com>
@ -76,7 +76,7 @@ EOF;
$value = "'N;'";
} elseif (\is_object($value) || \is_array($value)) {
try {
$value = PhpMarshaller::marshall($value, $isStaticValue);
$value = VarExporter::export($value, $isStaticValue);
} catch (\Exception $e) {
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e);
}
@ -93,7 +93,8 @@ EOF;
}
if (!$isStaticValue) {
$value = "static function () {\nreturn {$value};\n}";
$value = str_replace("\n", "\n ", $value);
$value = "static function () {\n return {$value};\n}";
}
$hash = hash('md5', $value);

View File

@ -13,7 +13,7 @@ namespace Symfony\Component\Cache\Traits;
use Symfony\Component\Cache\Exception\CacheException;
use Symfony\Component\Cache\Exception\InvalidArgumentException;
use Symfony\Component\Cache\Marshaller\PhpMarshaller;
use Symfony\Component\VarExporter\VarExporter;
/**
* @author Piotr Stankowski <git@trakos.pl>
@ -166,7 +166,7 @@ trait PhpFilesTrait
$value = "'N;'";
} elseif (\is_object($value) || \is_array($value)) {
try {
$value = PhpMarshaller::marshall($value, $isStaticValue);
$value = VarExporter::export($value, $isStaticValue);
} catch (\Exception $e) {
throw new InvalidArgumentException(sprintf('Cache key "%s" has non-serializable %s value.', $key, \is_object($value) ? \get_class($value) : 'array'), 0, $e);
}
@ -183,7 +183,8 @@ trait PhpFilesTrait
}
if (!$isStaticValue) {
$value = "static function () {\n\nreturn {$value};\n\n}";
$value = str_replace("\n", "\n ", $value);
$value = "static function () {\n\n return {$value};\n\n}";
}
$file = $this->files[$key] = $this->getFile($key, true);

View File

@ -24,7 +24,8 @@
"psr/cache": "~1.0",
"psr/log": "~1.0",
"psr/simple-cache": "^1.0",
"symfony/contracts": "^1.0"
"symfony/contracts": "^1.0",
"symfony/var-exporter": "^4.2"
},
"require-dev": {
"cache/integration-tests": "dev-master",

View File

@ -0,0 +1,3 @@
composer.lock
phpunit.xml
vendor/

View File

@ -0,0 +1,7 @@
CHANGELOG
=========
4.2.0
-----
* added the component

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Marshaller\PhpMarshaller;
namespace Symfony\Component\VarExporter\Internal;
/**
* @author Nicolas Grekas <p@tchwork.com>
@ -20,20 +20,24 @@ class Configurator
{
public static $configurators = array();
public $registry;
public $values;
public $properties;
public $value;
public $wakeups;
public function __construct(?Registry $registry, ?Values $values, array $properties, $value, array $wakeups)
{
$this->{0} = $registry;
$this->{1} = $values;
$this->{2} = $properties;
$this->{3} = $value;
$this->{4} = $wakeups;
$this->registry = $registry;
$this->values = $values;
$this->properties = $properties;
$this->value = $value;
$this->wakeups = $wakeups;
}
public static function __set_state($state)
public static function pop($objects, $values, $properties, $value, $wakeups)
{
$objects = Registry::$objects;
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

@ -9,17 +9,17 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Marshaller\PhpMarshaller;
namespace Symfony\Component\VarExporter\Internal;
/**
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class Marshaller
class Exporter
{
/**
* Prepares an array of values for PhpMarshaller.
* Prepares an array of values for VarExporter.
*
* For performance this method is public and has no type-hints.
*
@ -32,10 +32,8 @@ class Marshaller
* @return int
*
* @throws \Exception When a value cannot be serialized
*
* @internal
*/
public static function marshall($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic)
public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic)
{
$refs = $values;
foreach ($values as $k => $value) {
@ -58,7 +56,7 @@ class Marshaller
if (\is_array($value)) {
if ($value) {
$value = self::marshall($value, $objectsPool, $refsPool, $objectsCount, $valueIsStatic);
$value = self::prepare($value, $objectsPool, $refsPool, $objectsCount, $valueIsStatic);
}
goto handle_value;
} elseif (!\is_object($value) && !$value instanceof \__PHP_Incomplete_Class) {
@ -144,7 +142,7 @@ class Marshaller
}
$objectsPool[$value] = array($id = \count($objectsPool));
$properties = self::marshall($properties, $objectsPool, $refsPool, $objectsCount, $valueIsStatic);
$properties = self::prepare($properties, $objectsPool, $refsPool, $objectsCount, $valueIsStatic);
++$objectsCount;
$objectsPool[$value] = array($id, $class, $properties, \method_exists($class, '__wakeup') ? $objectsCount : 0);
@ -162,6 +160,134 @@ class Marshaller
return $values;
}
public static function export($value, $indent = '')
{
switch (true) {
case \is_int($value) || \is_float($value): return var_export($value, true);
case array() === $value: return '[]';
case false === $value: return 'false';
case true === $value: return 'true';
case null === $value: return 'null';
case '' === $value: return "''";
}
if ($value instanceof Reference) {
if (0 <= $value->id) {
return '\\'.Registry::class.'::$objects['.$value->id.']';
}
$value = -$value->id;
return '&\\'.Registry::class.'::$references['.$value.']';
}
$subIndent = $indent.' ';
if (\is_string($value)) {
$code = var_export($value, true);
if (false !== strpos($value, "\n") || false !== strpos($value, "\r")) {
$code = strtr($code, array(
"\r\n" => "'.\"\\r\\n\"\n".$subIndent.".'",
"\r" => "'.\"\\r\"\n".$subIndent.".'",
"\n" => "'.\"\\n\"\n".$subIndent.".'",
));
}
if (false !== strpos($value, "\0")) {
$code = str_replace('\' . "\0" . \'', '\'."\0".\'', $code);
$code = str_replace('".\'\'."', '', $code);
}
if (false !== strpos($code, "''.")) {
$code = str_replace("''.", '', $code);
}
if (".''" === substr($code, -3)) {
$code = rtrim(substr($code, 0, -3));
}
return $code;
}
if (\is_array($value)) {
$j = -1;
$code = '';
foreach ($value as $k => $v) {
$code .= $subIndent;
if ($k !== ++$j) {
$code .= self::export($k, $subIndent).' => ';
$j = INF;
}
$code .= self::export($v, $subIndent).",\n";
}
return "[\n".$code.$indent.']';
}
if ($value instanceof Values) {
$code = '';
foreach ($value->values as $k => $v) {
$code .= $subIndent.'\\'.Registry::class.'::$references['.$k.'] = '.self::export($v, $subIndent).",\n";
}
return "[\n".$code.$indent.']';
}
if ($value instanceof Registry) {
$code = '';
$reflectors = array();
$serializables = array();
foreach ($value as $k => $class) {
if (':' === ($class[1] ?? null)) {
$serializables[$k] = $class;
continue;
}
$c = '\\'.$class.'::class';
$reflectors[$class] = '\\'.Registry::class.'::$reflectors['.$c.'] ?? \\'.Registry::class.'::getClassReflector('.$c.', '
.self::export(Registry::$instantiableWithoutConstructor[$class]).', '
.self::export(Registry::$cloneable[$class])
.')';
if (Registry::$cloneable[$class]) {
$code .= $subIndent.'clone \\'.Registry::class.'::$prototypes['.$c."],\n";
} elseif (Registry::$instantiableWithoutConstructor[$class]) {
$code .= $subIndent.'\\'.Registry::class.'::$reflectors['.$c."]->newInstanceWithoutConstructor(),\n";
} else {
$code .= $subIndent.'\\'.Registry::class.'::$reflectors['.$c."]->newInstance(),\n";
}
}
if ($reflectors) {
$code = "[\n".$subIndent.implode(",\n".$subIndent, $reflectors).",\n".$indent."], [\n".$code.$indent.'], ';
$code .= !$serializables ? "[\n".$indent.']' : self::export($serializables, $indent);
} else {
$code = '[], []';
$code .= ', '.self::export($serializables, $indent);
}
return '\\'.Registry::class.'::push('.$code.')';
}
if ($value instanceof Configurator) {
$code = '';
foreach ($value->properties as $class => $properties) {
$code .= $subIndent.' \\'.$class.'::class => '.self::export($properties, $subIndent.' ').",\n";
}
$code = array(
self::export($value->registry, $subIndent),
self::export($value->values, $subIndent),
'' !== $code ? "[\n".$code.$subIndent.']' : '[]',
self::export($value->value, $subIndent),
self::export($value->wakeups, $subIndent),
);
return '\\'.\get_class($value)."::pop(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')';
}
throw new \UnexpectedValueException(sprintf('Cannot export value of type "%s".', \is_object($value) ? \get_class($value) : \gettype($value)));
}
/**
* Extracts the state of an ArrayIterator or ArrayObject instance.
*
@ -172,8 +298,6 @@ class Marshaller
* @param object $proto
*
* @return array
*
* @internal
*/
public static function getArrayObjectProperties($value, &$arrayValue, $proto)
{

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Marshaller\PhpMarshaller;
namespace Symfony\Component\VarExporter\Internal;
/**
* @author Nicolas Grekas <p@tchwork.com>

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Marshaller\PhpMarshaller;
namespace Symfony\Component\VarExporter\Internal;
/**
* @author Nicolas Grekas <p@tchwork.com>
@ -33,44 +33,35 @@ class Registry
}
}
public static function __set_state($classes)
public static function push($reflectors, $objects, $serializables)
{
$unserializeCallback = null;
self::$stack[] = array(self::$objects, self::$references);
self::$objects = $classes;
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();
}
if (!$serializables) {
return self::$objects = $objects;
}
$unserializeCallback = ini_set('unserialize_callback_func', __CLASS__.'::getClassReflector');
try {
foreach ($serializables as $k => $v) {
$objects[$k] = unserialize($v);
}
} catch (\Throwable $e) {
list(self::$objects, self::$references) = \array_pop(self::$stack);
list(self::$objects, self::$references) = array_pop(self::$stack);
throw $e;
} finally {
if (null !== $unserializeCallback) {
ini_set('unserialize_callback_func', $unserializeCallback);
}
ini_set('unserialize_callback_func', $unserializeCallback);
}
return self::$objects = $objects;
}
public static function getClassReflector($class)
public static function getClassReflector($class, $instantiableWithoutConstructor = null, $cloneable = null)
{
$reflector = new \ReflectionClass($class);
if (self::$instantiableWithoutConstructor[$class] = !$reflector->isFinal() || !$reflector->isInternal()) {
if (self::$instantiableWithoutConstructor[$class] = $instantiableWithoutConstructor ?? (!$reflector->isFinal() || !$reflector->isInternal())) {
$proto = $reflector->newInstanceWithoutConstructor();
} else {
try {
@ -80,14 +71,23 @@ class Registry
}
}
if ($proto instanceof \Reflector || $proto instanceof \ReflectionGenerator || $proto instanceof \ReflectionType) {
if (null !== $cloneable) {
self::$prototypes[$class] = $proto;
self::$cloneable[$class] = $cloneable;
return self::$reflectors[$class] = $reflector;
}
if ($proto instanceof \Reflector || $proto instanceof \ReflectionGenerator || $proto instanceof \ReflectionType || $proto instanceof \IteratorIterator || $proto instanceof \RecursiveIteratorIterator) {
if (!$proto instanceof \Serializable && !\method_exists($proto, '__wakeup')) {
throw new \Exception(sprintf("Serialization of '%s' is not allowed", $class));
}
self::$cloneable[$class] = false;
} else {
self::$cloneable[$class] = !$reflector->hasMethod('__clone');
}
self::$prototypes[$class] = $proto;
self::$cloneable[$class] = !$reflector->hasMethod('__clone');
if ($proto instanceof \Throwable) {
static $trace;

View File

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Marshaller\PhpMarshaller;
namespace Symfony\Component\VarExporter\Internal;
/**
* @author Nicolas Grekas <p@tchwork.com>
@ -18,17 +18,10 @@ namespace Symfony\Component\Cache\Marshaller\PhpMarshaller;
*/
class Values
{
public $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;
}
$this->values = $values;
}
}

View File

@ -0,0 +1,19 @@
Copyright (c) 2018 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,34 @@
VarExporter Component
=====================
The VarExporter component allows exporting any serializable PHP data structure to
plain PHP code. While doing so, it preserves all the semantics associated with
the serialization mechanism of PHP (`__wakeup`, `__sleep`, `Serializable`).
The reason to use this component *vs* `serialize()` or
[igbinary](https://github.com/igbinary/igbinary) is performance: thanks to
OPcache, the resulting code is significantly faster and more memory efficient
than using `unserialize()` or `igbinary_unserialize()`.
Unlike `var_export()`, this works on any serializable PHP value.
It also provides a few improvements over `var_export()`/`serialize()`:
* the output is PSR-2 compatible;
* the output can be re-indented without messing up with `\r` or `\n` in the data
* missing classes throw a `ReflectionException` instead of being unserialized to
`PHP_Incomplete_Class` objects;
* references involving `SplObjectStorage`, `ArrayObject` or `ArrayIterator`
instances are preserved;
* `Reflection*`, `IteratorIterator` and `RecursiveIteratorIterator` classes
throw an exception when being serialized (their unserialized version is broken
anyway, see https://bugs.php.net/76737).
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/var_exporter/introduction.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

View File

@ -0,0 +1,25 @@
<?php
return \Symfony\Component\VarExporter\Internal\Configurator::pop(
\Symfony\Component\VarExporter\Internal\Registry::push([
\Symfony\Component\VarExporter\Internal\Registry::$reflectors[\ArrayIterator::class] ?? \Symfony\Component\VarExporter\Internal\Registry::getClassReflector(\ArrayIterator::class, true, true),
], [
clone \Symfony\Component\VarExporter\Internal\Registry::$prototypes[\ArrayIterator::class],
], [
]),
null,
[
\ArrayIterator::class => [
"\0" => [
[
[
123,
],
1,
],
],
],
],
\Symfony\Component\VarExporter\Internal\Registry::$objects[0],
[]
);

View File

@ -0,0 +1,25 @@
<?php
return \Symfony\Component\VarExporter\Internal\Configurator::pop(
\Symfony\Component\VarExporter\Internal\Registry::push([
\Symfony\Component\VarExporter\Internal\Registry::$reflectors[\Symfony\Component\VarExporter\Tests\MyArrayObject::class] ?? \Symfony\Component\VarExporter\Internal\Registry::getClassReflector(\Symfony\Component\VarExporter\Tests\MyArrayObject::class, true, true),
], [
clone \Symfony\Component\VarExporter\Internal\Registry::$prototypes[\Symfony\Component\VarExporter\Tests\MyArrayObject::class],
], [
]),
null,
[
\ArrayObject::class => [
"\0" => [
[
[
234,
],
1,
],
],
],
],
\Symfony\Component\VarExporter\Internal\Registry::$objects[0],
[]
);

View File

@ -0,0 +1,30 @@
<?php
return \Symfony\Component\VarExporter\Internal\Configurator::pop(
\Symfony\Component\VarExporter\Internal\Registry::push([
\Symfony\Component\VarExporter\Internal\Registry::$reflectors[\ArrayObject::class] ?? \Symfony\Component\VarExporter\Internal\Registry::getClassReflector(\ArrayObject::class, true, true),
], [
clone \Symfony\Component\VarExporter\Internal\Registry::$prototypes[\ArrayObject::class],
clone \Symfony\Component\VarExporter\Internal\Registry::$prototypes[\ArrayObject::class],
], [
]),
null,
[
\ArrayObject::class => [
"\0" => [
[
[
1,
\Symfony\Component\VarExporter\Internal\Registry::$objects[0],
],
0,
],
],
'foo' => [
\Symfony\Component\VarExporter\Internal\Registry::$objects[1],
],
],
],
\Symfony\Component\VarExporter\Internal\Registry::$objects[0],
[]
);

View File

@ -0,0 +1,3 @@
<?php
return true;

View File

@ -0,0 +1,19 @@
<?php
return \Symfony\Component\VarExporter\Internal\Configurator::pop(
\Symfony\Component\VarExporter\Internal\Registry::push([
\Symfony\Component\VarExporter\Internal\Registry::$reflectors[\Symfony\Component\VarExporter\Tests\MyCloneable::class] ?? \Symfony\Component\VarExporter\Internal\Registry::getClassReflector(\Symfony\Component\VarExporter\Tests\MyCloneable::class, true, false),
\Symfony\Component\VarExporter\Internal\Registry::$reflectors[\Symfony\Component\VarExporter\Tests\MyNotCloneable::class] ?? \Symfony\Component\VarExporter\Internal\Registry::getClassReflector(\Symfony\Component\VarExporter\Tests\MyNotCloneable::class, true, false),
], [
\Symfony\Component\VarExporter\Internal\Registry::$reflectors[\Symfony\Component\VarExporter\Tests\MyCloneable::class]->newInstanceWithoutConstructor(),
\Symfony\Component\VarExporter\Internal\Registry::$reflectors[\Symfony\Component\VarExporter\Tests\MyNotCloneable::class]->newInstanceWithoutConstructor(),
], [
]),
null,
[],
[
\Symfony\Component\VarExporter\Internal\Registry::$objects[0],
\Symfony\Component\VarExporter\Internal\Registry::$objects[1],
],
[]
);

View File

@ -0,0 +1,28 @@
<?php
return \Symfony\Component\VarExporter\Internal\Configurator::pop(
\Symfony\Component\VarExporter\Internal\Registry::push([
\Symfony\Component\VarExporter\Internal\Registry::$reflectors[\DateTime::class] ?? \Symfony\Component\VarExporter\Internal\Registry::getClassReflector(\DateTime::class, true, true),
], [
clone \Symfony\Component\VarExporter\Internal\Registry::$prototypes[\DateTime::class],
], [
]),
null,
[
\DateTime::class => [
'date' => [
'1970-01-01 00:00:00.000000',
],
'timezone_type' => [
1,
],
'timezone' => [
'+00:00',
],
],
],
\Symfony\Component\VarExporter\Internal\Registry::$objects[0],
[
1 => 0,
]
);

View File

@ -0,0 +1,15 @@
<?php
return \Symfony\Component\VarExporter\Internal\Configurator::pop(
\Symfony\Component\VarExporter\Internal\Registry::push([], [], []),
[
\Symfony\Component\VarExporter\Internal\Registry::$references[1] = [
&\Symfony\Component\VarExporter\Internal\Registry::$references[1],
],
],
[],
[
&\Symfony\Component\VarExporter\Internal\Registry::$references[1],
],
[]
);

View File

@ -0,0 +1,20 @@
<?php
return \Symfony\Component\VarExporter\Internal\Configurator::pop(
\Symfony\Component\VarExporter\Internal\Registry::push([
\Symfony\Component\VarExporter\Internal\Registry::$reflectors[\stdClass::class] ?? \Symfony\Component\VarExporter\Internal\Registry::getClassReflector(\stdClass::class, true, true),
], [
clone \Symfony\Component\VarExporter\Internal\Registry::$prototypes[\stdClass::class],
], [
]),
[
\Symfony\Component\VarExporter\Internal\Registry::$references[1] = \Symfony\Component\VarExporter\Internal\Registry::$objects[0],
],
[],
[
&\Symfony\Component\VarExporter\Internal\Registry::$references[1],
&\Symfony\Component\VarExporter\Internal\Registry::$references[1],
\Symfony\Component\VarExporter\Internal\Registry::$objects[0],
],
[]
);

View File

@ -0,0 +1,11 @@
<?php
return \Symfony\Component\VarExporter\Internal\Configurator::pop(
\Symfony\Component\VarExporter\Internal\Registry::push([], [], [
'O:20:"SomeNotExistingClass":0:{}',
]),
null,
[],
\Symfony\Component\VarExporter\Internal\Registry::$objects[0],
[]
);

View File

@ -0,0 +1,8 @@
<?php
return [
"\0\0\r\n"
.'A' => 'B'."\r"
.'C'."\n"
."\n",
];

View File

@ -0,0 +1,34 @@
<?php
return \Symfony\Component\VarExporter\Internal\Configurator::pop(
\Symfony\Component\VarExporter\Internal\Registry::push([
\Symfony\Component\VarExporter\Internal\Registry::$reflectors[\Symfony\Component\VarExporter\Tests\MyPrivateValue::class] ?? \Symfony\Component\VarExporter\Internal\Registry::getClassReflector(\Symfony\Component\VarExporter\Tests\MyPrivateValue::class, true, true),
\Symfony\Component\VarExporter\Internal\Registry::$reflectors[\Symfony\Component\VarExporter\Tests\MyPrivateChildValue::class] ?? \Symfony\Component\VarExporter\Internal\Registry::getClassReflector(\Symfony\Component\VarExporter\Tests\MyPrivateChildValue::class, true, true),
], [
clone \Symfony\Component\VarExporter\Internal\Registry::$prototypes[\Symfony\Component\VarExporter\Tests\MyPrivateValue::class],
clone \Symfony\Component\VarExporter\Internal\Registry::$prototypes[\Symfony\Component\VarExporter\Tests\MyPrivateChildValue::class],
], [
]),
null,
[
\Symfony\Component\VarExporter\Tests\MyPrivateValue::class => [
'prot' => [
123,
],
'priv' => [
234,
234,
],
],
\Symfony\Component\VarExporter\Tests\MyPrivateChildValue::class => [
'prot' => [
1 => 123,
],
],
],
[
\Symfony\Component\VarExporter\Internal\Registry::$objects[0],
\Symfony\Component\VarExporter\Internal\Registry::$objects[1],
],
[]
);

View File

@ -0,0 +1,14 @@
<?php
return \Symfony\Component\VarExporter\Internal\Configurator::pop(
\Symfony\Component\VarExporter\Internal\Registry::push([], [], [
'C:50:"Symfony\\Component\\VarExporter\\Tests\\MySerializable":3:{123}',
]),
null,
[],
[
\Symfony\Component\VarExporter\Internal\Registry::$objects[0],
\Symfony\Component\VarExporter\Internal\Registry::$objects[0],
],
[]
);

View File

@ -0,0 +1,8 @@
<?php
return [
123,
[
'abc',
],
];

View File

@ -0,0 +1,25 @@
<?php
return \Symfony\Component\VarExporter\Internal\Configurator::pop(
\Symfony\Component\VarExporter\Internal\Registry::push([
\Symfony\Component\VarExporter\Internal\Registry::$reflectors[\SplObjectStorage::class] ?? \Symfony\Component\VarExporter\Internal\Registry::getClassReflector(\SplObjectStorage::class, true, true),
\Symfony\Component\VarExporter\Internal\Registry::$reflectors[\stdClass::class] ?? \Symfony\Component\VarExporter\Internal\Registry::getClassReflector(\stdClass::class, true, true),
], [
clone \Symfony\Component\VarExporter\Internal\Registry::$prototypes[\SplObjectStorage::class],
clone \Symfony\Component\VarExporter\Internal\Registry::$prototypes[\stdClass::class],
], [
]),
null,
[
\SplObjectStorage::class => [
"\0" => [
[
\Symfony\Component\VarExporter\Internal\Registry::$objects[1],
345,
],
],
],
],
\Symfony\Component\VarExporter\Internal\Registry::$objects[0],
[]
);

View File

@ -0,0 +1,28 @@
<?php
return \Symfony\Component\VarExporter\Internal\Configurator::pop(
\Symfony\Component\VarExporter\Internal\Registry::push([
\Symfony\Component\VarExporter\Internal\Registry::$reflectors[\Symfony\Component\VarExporter\Tests\MyWakeup::class] ?? \Symfony\Component\VarExporter\Internal\Registry::getClassReflector(\Symfony\Component\VarExporter\Tests\MyWakeup::class, true, true),
], [
clone \Symfony\Component\VarExporter\Internal\Registry::$prototypes[\Symfony\Component\VarExporter\Tests\MyWakeup::class],
clone \Symfony\Component\VarExporter\Internal\Registry::$prototypes[\Symfony\Component\VarExporter\Tests\MyWakeup::class],
], [
]),
null,
[
\Symfony\Component\VarExporter\Tests\MyWakeup::class => [
'sub' => [
\Symfony\Component\VarExporter\Internal\Registry::$objects[1],
123,
],
'baz' => [
1 => 123,
],
],
],
\Symfony\Component\VarExporter\Internal\Registry::$objects[0],
[
1 => 1,
2 => 0,
]
);

View File

@ -9,14 +9,14 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Tests\Marshaller;
namespace Symfony\Component\VarExporter\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Marshaller\PhpMarshaller;
use Symfony\Component\Cache\Marshaller\PhpMarshaller\Registry;
use Symfony\Component\VarExporter\Internal\Registry;
use Symfony\Component\VarExporter\VarExporter;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
class DoctrineProviderTest extends TestCase
class VarExporterTest extends TestCase
{
use VarDumperTestTrait;
@ -28,7 +28,7 @@ class DoctrineProviderTest extends TestCase
{
$unserializeCallback = ini_set('unserialize_callback_func', 'var_dump');
try {
Registry::__set_state(array('O:20:"SomeNotExistingClass":0:{}'));
Registry::push(array(), array(), array('O:20:"SomeNotExistingClass":0:{}'));
} finally {
$this->assertSame('var_dump', ini_set('unserialize_callback_func', $unserializeCallback));
}
@ -43,7 +43,7 @@ class DoctrineProviderTest extends TestCase
{
$expectedDump = $this->getDump($value);
try {
PhpMarshaller::marshall($value);
VarExporter::export($value);
} finally {
$this->assertDumpEquals(rtrim($expectedDump), $value);
}
@ -74,12 +74,12 @@ class DoctrineProviderTest extends TestCase
{
$serializedValue = serialize($value);
$isStaticValue = true;
$marshalledValue = PhpMarshaller::marshall($value, $isStaticValue);
$marshalledValue = VarExporter::export($value, $isStaticValue);
$this->assertSame($staticValueExpected, $isStaticValue);
$this->assertSame($serializedValue, serialize($value));
$dump = '<?php return '.$marshalledValue.";\n";
$dump = "<?php\n\nreturn ".$marshalledValue.";\n";
$fixtureFile = __DIR__.'/Fixtures/'.$testName.'.php';
$this->assertStringEqualsFile($fixtureFile, $dump);
@ -97,6 +97,8 @@ class DoctrineProviderTest extends TestCase
public function provideMarshall()
{
yield array('multiline-string', array("\0\0\r\nA" => "B\rC\n\n"), true);
yield array('bool', true, true);
yield array('simple-array', array(123, array('abc')), true);
yield array('datetime', \DateTime::createFromFormat('U', 0));

View File

@ -9,32 +9,42 @@
* file that was distributed with this source code.
*/
namespace Symfony\Component\Cache\Marshaller;
namespace Symfony\Component\VarExporter;
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;
use Symfony\Component\VarExporter\Internal\Configurator;
use Symfony\Component\VarExporter\Internal\Exporter;
use Symfony\Component\VarExporter\Internal\Reference;
use Symfony\Component\VarExporter\Internal\Registry;
use Symfony\Component\VarExporter\Internal\Values;
/**
* @author Nicolas Grekas <p@tchwork.com>
* Exports serializable PHP values to PHP code.
*
* PhpMarshaller allows serializing PHP data structures using var_export()
* while preserving all the semantics associated to serialize().
* VarExporter allows serializing PHP data structures to plain PHP code (like var_export())
* while preserving all the semantics associated with serialize() (unlike var_export()).
*
* By leveraging OPcache, the generated PHP code is faster than doing the same with unserialize().
*
* @internal
* @author Nicolas Grekas <p@tchwork.com>
*/
class PhpMarshaller
final class VarExporter
{
public static function marshall($value, bool &$isStaticValue = null): string
/**
* Exports a serializable PHP value to PHP code.
*
* @param mixed $value The value to export
* @param bool &$isStaticValue Set to true after execution if the provided value is static, false otherwise
*
* @return string The value exported as PHP code
*
* @throws \Exception When the provided value cannot be serialized
*/
public static function export($value, bool &$isStaticValue = null): string
{
$isStaticValue = true;
if (!\is_object($value) && !(\is_array($value) && $value) && !$value instanceof \__PHP_Incomplete_Class && !\is_resource($value)) {
return var_export($value, true);
return Exporter::export($value);
}
$objectsPool = new \SplObjectStorage();
@ -42,7 +52,7 @@ class PhpMarshaller
$objectsCount = 0;
try {
$value = Marshaller::marshall(array($value), $objectsPool, $refsPool, $objectsCount, $isStaticValue)[0];
$value = Exporter::prepare(array($value), $objectsPool, $refsPool, $objectsCount, $isStaticValue)[0];
} finally {
$references = array();
foreach ($refsPool as $i => $v) {
@ -52,7 +62,7 @@ class PhpMarshaller
}
if ($isStaticValue) {
return var_export($value, true);
return Exporter::export($value);
}
$classes = array();
@ -75,13 +85,8 @@ class PhpMarshaller
}
}
$value = new Configurator($classes ? new Registry($classes) : null, $references ? new Values($references) : null, $properties, $value, $wakeups);
$value = var_export($value, true);
$value = new Configurator(new Registry($classes), $references ? new Values($references) : null, $properties, $value, $wakeups);
$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);
return $value;
return Exporter::export($value);
}
}

View File

@ -0,0 +1,36 @@
{
"name": "symfony/var-exporter",
"type": "library",
"description": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code",
"keywords": ["export", "serialize"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": "^7.1.3"
},
"require-dev": {
"symfony/var-dumper": "^4.1.1"
},
"autoload": {
"psr-4": { "Symfony\\Component\\VarExporter\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "4.2-dev"
}
}
}

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="Symfony VarExporter Component Test Suite">
<directory>./Tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./Resources</directory>
<directory>./Tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>