bug #28437 [VarExporter] fix more edge cases (nicolas-grekas)

This PR was merged into the 4.2-dev branch.

Discussion
----------

[VarExporter] fix more edge cases

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

As found while preparing #28417

Commits
-------

443bd119b1 [VarExporter] fix more edge cases
This commit is contained in:
Nicolas Grekas 2018-09-11 10:47:39 +02:00
commit 2879baf3bd
17 changed files with 100 additions and 69 deletions

View File

@ -77,19 +77,7 @@ class Exporter
$properties = array();
$sleep = null;
$arrayValue = (array) $value;
if (!isset(Registry::$reflectors[$class])) {
Registry::getClassReflector($class);
try {
serialize(Registry::$prototypes[$class]);
} catch (\Exception $e) {
throw new NotInstantiableTypeException($class, $e);
}
if (\method_exists($class, '__sleep')) {
Registry::getClassReflector($class, Registry::$instantiableWithoutConstructor[$class], Registry::$cloneable[$class]);
}
}
$reflector = Registry::$reflectors[$class];
$reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class);
$proto = Registry::$prototypes[$class];
if (($value instanceof \ArrayIterator || $value instanceof \ArrayObject) && null !== $proto) {
@ -133,7 +121,7 @@ class Exporter
foreach ($arrayValue as $name => $v) {
$n = (string) $name;
if ('' === $n || "\0" !== $n[0]) {
$c = '*';
$c = 'stdClass';
$properties[$c][$n] = $v;
unset($sleep[$n]);
} elseif ('*' === $n[1]) {
@ -305,7 +293,7 @@ class Exporter
$code .= $subIndent.(1 !== $k - $j ? $k.' => ' : '');
$j = $k;
$eol = ",\n";
$c = '[\\'.$class.'::class]';
$c = '['.self::export($class).']';
if ($seen[$class] ?? false) {
if (Registry::$cloneable[$class]) {
@ -351,8 +339,7 @@ class Exporter
{
$code = '';
foreach ($value->properties as $class => $properties) {
$c = '*' !== $class ? '\\'.$class.'::class' : "'*'";
$code .= $subIndent.' '.$c.' => '.self::export($properties, $subIndent.' ').",\n";
$code .= $subIndent.' '.self::export($class).' => '.self::export($properties, $subIndent.' ').",\n";
}
$code = array(

View File

@ -49,7 +49,7 @@ class Hydrator
public static function getHydrator($class)
{
if ('*' === $class) {
if ('stdClass' === $class) {
return self::$hydrators[$class] = static function ($properties, $objects) {
foreach ($properties as $name => $values) {
foreach ($values as $i => $v) {
@ -62,13 +62,17 @@ class Hydrator
$classReflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class);
if (!$classReflector->isInternal()) {
return self::$hydrators[$class] = (self::$hydrators['*'] ?? self::getHydrator('*'))->bindTo(null, $class);
return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, $class);
}
if ($classReflector->name !== $class) {
return self::$hydrators[$classReflector->name] ?? self::getHydrator($classReflector->name);
}
switch ($class) {
case 'ArrayIterator':
case 'ArrayObject':
$constructor = $classReflector->getConstructor();
$constructor = \Closure::fromCallable(array($classReflector->getConstructor(), 'invokeArgs'));
return self::$hydrators[$class] = static function ($properties, $objects) use ($constructor) {
foreach ($properties as $name => $values) {
@ -78,17 +82,17 @@ class Hydrator
}
}
}
foreach ($properties["\0"] as $i => $v) {
$constructor->invokeArgs($objects[$i], $v);
foreach ($properties["\0"] ?? array() as $i => $v) {
$constructor($objects[$i], $v);
}
};
case 'ErrorException':
return self::$hydrators[$class] = (self::$hydrators['*'] ?? self::getHydrator('*'))->bindTo(null, new class() extends \ErrorException {
return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \ErrorException {
});
case 'TypeError':
return self::$hydrators[$class] = (self::$hydrators['*'] ?? self::getHydrator('*'))->bindTo(null, new class() extends \Error {
return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \Error {
});
case 'SplObjectStorage':
@ -109,19 +113,28 @@ class Hydrator
};
}
$propertyReflectors = array();
$propertySetters = array();
foreach ($classReflector->getProperties() as $propertyReflector) {
if (!$propertyReflector->isStatic()) {
$propertyReflector->setAccessible(true);
$propertyReflectors[$propertyReflector->name] = $propertyReflector;
$propertySetters[$propertyReflector->name] = \Closure::fromCallable(array($propertyReflector, 'setValue'));
}
}
return self::$hydrators[$class] = static function ($properties, $objects) use ($propertyReflectors) {
if (!$propertySetters) {
return self::$hydrators[$class] = self::$hydrators['stdClass'] ?? self::getHydrator('stdClass');
}
return self::$hydrators[$class] = static function ($properties, $objects) use ($propertySetters) {
foreach ($properties as $name => $values) {
$p = $propertyReflectors[$name];
if ($setValue = $propertySetters[$name] ?? null) {
foreach ($values as $i => $v) {
$setValue($objects[$i], $v);
}
continue;
}
foreach ($values as $i => $v) {
$p->setValue($objects[$i], $v);
$objects[$i]->$name = $v;
}
}
};

View File

@ -65,17 +65,26 @@ class Registry
public static function getClassReflector($class, $instantiableWithoutConstructor = false, $cloneable = null)
{
if (!\class_exists($class)) {
if (!\class_exists($class) && !\interface_exists($class, false) && !\trait_exists($class, false)) {
throw new ClassNotFoundException($class);
}
$reflector = new \ReflectionClass($class);
if (self::$instantiableWithoutConstructor[$class] = $instantiableWithoutConstructor || !$reflector->isFinal()) {
if ($instantiableWithoutConstructor) {
$proto = $reflector->newInstanceWithoutConstructor();
} elseif (!$reflector->isInstantiable()) {
throw new NotInstantiableTypeException($class);
} elseif ($reflector->name !== $class) {
$reflector = self::$reflectors[$name = $reflector->name] ?? self::getClassReflector($name, $instantiableWithoutConstructor, $cloneable);
self::$cloneable[$class] = self::$cloneable[$name];
self::$instantiableWithoutConstructor[$class] = self::$instantiableWithoutConstructor[$name];
self::$prototypes[$class] = self::$prototypes[$name];
return self::$reflectors[$class] = $reflector;
} else {
try {
$proto = $reflector->newInstanceWithoutConstructor();
self::$instantiableWithoutConstructor[$class] = true;
$instantiableWithoutConstructor = true;
} catch (\ReflectionException $e) {
$proto = $reflector->implementsInterface('Serializable') ? 'C:' : 'O:';
if ('C:' === $proto && !$reflector->getMethod('unserialize')->isInternal()) {
@ -84,31 +93,48 @@ class Registry
throw new NotInstantiableTypeException($class);
}
}
if (null !== $proto && !$proto instanceof \Throwable) {
try {
if (!$proto instanceof \Serializable && !\method_exists($class, '__sleep')) {
serialize($proto);
} elseif ($instantiableWithoutConstructor) {
serialize($reflector->newInstanceWithoutConstructor());
} else {
serialize(unserialize(($proto instanceof \Serializable ? 'C:' : 'O:').\strlen($class).':"'.$class.'":0:{}'));
}
} catch (\Exception $e) {
throw new NotInstantiableTypeException($class, $e);
}
}
}
if (null === self::$cloneable[$class] = $cloneable) {
if (null === $cloneable) {
if (($proto instanceof \Reflector || $proto instanceof \ReflectionGenerator || $proto instanceof \ReflectionType || $proto instanceof \IteratorIterator || $proto instanceof \RecursiveIteratorIterator) && (!$proto instanceof \Serializable && !\method_exists($proto, '__wakeup'))) {
throw new NotInstantiableTypeException($class);
}
self::$cloneable[$class] = $reflector->isCloneable() && !$reflector->hasMethod('__clone');
$cloneable = $reflector->isCloneable() && !$reflector->hasMethod('__clone');
}
self::$cloneable[$class] = $cloneable;
self::$instantiableWithoutConstructor[$class] = $instantiableWithoutConstructor;
self::$prototypes[$class] = $proto;
if ($proto instanceof \Throwable) {
static $trace;
static $setTrace;
if (null === $trace) {
$trace = array(
if (null === $setTrace) {
$setTrace = array(
new \ReflectionProperty(\Error::class, 'trace'),
new \ReflectionProperty(\Exception::class, 'trace'),
);
$trace[0]->setAccessible(true);
$trace[1]->setAccessible(true);
$setTrace[0]->setAccessible(true);
$setTrace[1]->setAccessible(true);
$setTrace[0] = \Closure::fromCallable(array($setTrace[0], 'setValue'));
$setTrace[1] = \Closure::fromCallable(array($setTrace[1], 'setValue'));
}
$trace[$proto instanceof \Exception]->setValue($proto, array());
$setTrace[$proto instanceof \Exception]($proto, array());
}
return self::$reflectors[$class] = $reflector;

View File

@ -2,11 +2,11 @@
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
$o = [
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes[\ArrayIterator::class] ?? \Symfony\Component\VarExporter\Internal\Registry::p(\ArrayIterator::class)),
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['ArrayIterator'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('ArrayIterator')),
],
null,
[
\ArrayIterator::class => [
'ArrayIterator' => [
"\0" => [
[
[

View File

@ -2,11 +2,11 @@
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
$o = [
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes[\Symfony\Component\VarExporter\Tests\MyArrayObject::class] ?? \Symfony\Component\VarExporter\Internal\Registry::p(\Symfony\Component\VarExporter\Tests\MyArrayObject::class)),
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['Symfony\\Component\\VarExporter\\Tests\\MyArrayObject'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\MyArrayObject')),
],
null,
[
\ArrayObject::class => [
'ArrayObject' => [
"\0" => [
[
[

View File

@ -2,12 +2,12 @@
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
$o = [
clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)[\ArrayObject::class] ?? \Symfony\Component\VarExporter\Internal\Registry::p(\ArrayObject::class)),
clone $p[\ArrayObject::class],
clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)['ArrayObject'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('ArrayObject')),
clone $p['ArrayObject'],
],
null,
[
\ArrayObject::class => [
'ArrayObject' => [
"\0" => [
[
[
@ -18,7 +18,7 @@ return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
],
],
],
'*' => [
'stdClass' => [
'foo' => [
$o[1],
],

View File

@ -2,8 +2,8 @@
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
$o = [
(($f = &\Symfony\Component\VarExporter\Internal\Registry::$factories)[\Symfony\Component\VarExporter\Tests\MyCloneable::class] ?? \Symfony\Component\VarExporter\Internal\Registry::f(\Symfony\Component\VarExporter\Tests\MyCloneable::class))(),
($f[\Symfony\Component\VarExporter\Tests\MyNotCloneable::class] ?? \Symfony\Component\VarExporter\Internal\Registry::f(\Symfony\Component\VarExporter\Tests\MyNotCloneable::class))(),
(($f = &\Symfony\Component\VarExporter\Internal\Registry::$factories)['Symfony\\Component\\VarExporter\\Tests\\MyCloneable'] ?? \Symfony\Component\VarExporter\Internal\Registry::f('Symfony\\Component\\VarExporter\\Tests\\MyCloneable'))(),
($f['Symfony\\Component\\VarExporter\\Tests\\MyNotCloneable'] ?? \Symfony\Component\VarExporter\Internal\Registry::f('Symfony\\Component\\VarExporter\\Tests\\MyNotCloneable'))(),
],
null,
[],

View File

@ -2,11 +2,11 @@
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
$o = [
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes[\DateTime::class] ?? \Symfony\Component\VarExporter\Internal\Registry::p(\DateTime::class)),
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['DateTime'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('DateTime')),
],
null,
[
'*' => [
'stdClass' => [
'date' => [
'1970-01-01 00:00:00.000000',
],

View File

@ -2,11 +2,11 @@
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
$o = [
(\Symfony\Component\VarExporter\Internal\Registry::$factories[\Error::class] ?? \Symfony\Component\VarExporter\Internal\Registry::f(\Error::class))(),
(\Symfony\Component\VarExporter\Internal\Registry::$factories['Error'] ?? \Symfony\Component\VarExporter\Internal\Registry::f('Error'))(),
],
null,
[
\TypeError::class => [
'TypeError' => [
'file' => [
\dirname(__DIR__).\DIRECTORY_SEPARATOR.'VarExporterTest.php',
],
@ -14,7 +14,7 @@ return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
234,
],
],
\Error::class => [
'Error' => [
'trace' => [
[
'file' => \dirname(__DIR__).\DIRECTORY_SEPARATOR.'VarExporterTest.php',

View File

@ -6,7 +6,7 @@ return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
]),
null,
[
\TypeError::class => [
'TypeError' => [
'file' => [
\dirname(__DIR__).\DIRECTORY_SEPARATOR.'VarExporterTest.php',
],

View File

@ -2,7 +2,7 @@
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
$o = [
(\Symfony\Component\VarExporter\Internal\Registry::$factories[\Symfony\Component\VarExporter\Tests\FinalStdClass::class] ?? \Symfony\Component\VarExporter\Internal\Registry::f(\Symfony\Component\VarExporter\Tests\FinalStdClass::class))(),
(\Symfony\Component\VarExporter\Internal\Registry::$factories['Symfony\\Component\\VarExporter\\Tests\\FinalStdClass'] ?? \Symfony\Component\VarExporter\Internal\Registry::f('Symfony\\Component\\VarExporter\\Tests\\FinalStdClass'))(),
],
null,
[],

View File

@ -2,7 +2,7 @@
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
$o = [
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes[\stdClass::class] ?? \Symfony\Component\VarExporter\Internal\Registry::p(\stdClass::class)),
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['stdClass'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('stdClass')),
],
[
$r = [],

View File

@ -2,12 +2,12 @@
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
$o = [
clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)[\Symfony\Component\VarExporter\Tests\MyPrivateValue::class] ?? \Symfony\Component\VarExporter\Internal\Registry::p(\Symfony\Component\VarExporter\Tests\MyPrivateValue::class)),
clone ($p[\Symfony\Component\VarExporter\Tests\MyPrivateChildValue::class] ?? \Symfony\Component\VarExporter\Internal\Registry::p(\Symfony\Component\VarExporter\Tests\MyPrivateChildValue::class)),
clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)['Symfony\\Component\\VarExporter\\Tests\\MyPrivateValue'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\MyPrivateValue')),
clone ($p['Symfony\\Component\\VarExporter\\Tests\\MyPrivateChildValue'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\MyPrivateChildValue')),
],
null,
[
\Symfony\Component\VarExporter\Tests\MyPrivateValue::class => [
'Symfony\\Component\\VarExporter\\Tests\\MyPrivateValue' => [
'prot' => [
123,
123,

View File

@ -2,12 +2,12 @@
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
$o = [
clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)[\SplObjectStorage::class] ?? \Symfony\Component\VarExporter\Internal\Registry::p(\SplObjectStorage::class)),
clone ($p[\stdClass::class] ?? \Symfony\Component\VarExporter\Internal\Registry::p(\stdClass::class)),
clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)['SplObjectStorage'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('SplObjectStorage')),
clone ($p['stdClass'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('stdClass')),
],
null,
[
\SplObjectStorage::class => [
'SplObjectStorage' => [
"\0" => [
[
$o[1],

View File

@ -2,11 +2,11 @@
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
$o = [
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes[\Symfony\Component\VarExporter\Tests\GoodNight::class] ?? \Symfony\Component\VarExporter\Internal\Registry::p(\Symfony\Component\VarExporter\Tests\GoodNight::class)),
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['Symfony\\Component\\VarExporter\\Tests\\GoodNight'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\GoodNight')),
],
null,
[
'*' => [
'stdClass' => [
'good' => [
'night',
],

View File

@ -2,12 +2,12 @@
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
$o = [
clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)[\Symfony\Component\VarExporter\Tests\MyWakeup::class] ?? \Symfony\Component\VarExporter\Internal\Registry::p(\Symfony\Component\VarExporter\Tests\MyWakeup::class)),
clone $p[\Symfony\Component\VarExporter\Tests\MyWakeup::class],
clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)['Symfony\\Component\\VarExporter\\Tests\\MyWakeup'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\MyWakeup')),
clone $p['Symfony\\Component\\VarExporter\\Tests\\MyWakeup'],
],
null,
[
'*' => [
'stdClass' => [
'sub' => [
$o[1],
123,

View File

@ -61,6 +61,11 @@ class VarExporterTest extends TestCase
yield array($h = fopen(__FILE__, 'r'));
yield array(array($h));
$a = new class() {
};
yield array($a);
$a = array(null, $h);
$a[0] = &$a;