Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 127a2bd

Browse files
[VarExporter] add Instantiator::instantiate() to create+populate objects without calling their constructor nor any other methods
1 parent deae538 commit 127a2bd

19 files changed

+238
-56
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\VarExporter;
13+
14+
use Symfony\Component\VarExporter\Exception\ExceptionInterface;
15+
use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException;
16+
use Symfony\Component\VarExporter\Internal\Hydrator;
17+
use Symfony\Component\VarExporter\Internal\Registry;
18+
19+
/**
20+
* A utility class to create objects without calling their constructor.
21+
*
22+
* @author Nicolas Grekas <[email protected]>
23+
*/
24+
final class Instantiator
25+
{
26+
/**
27+
* Creates an object and sets its properties without calling its constructor nor any other methods.
28+
*
29+
* For example:
30+
*
31+
* // creates an empty instance of Foo
32+
* Instantiator::instantiate(Foo::class);
33+
*
34+
* // creates a Foo instance and sets one of its properties
35+
* Instantiator::instantiate(Foo::class, ['propertyName' => $propertyValue]);
36+
*
37+
* // creates a Foo instance and sets a private property defined on its parent Bar class
38+
* Instantiator::instantiate(Foo::class, [], [
39+
* Bar::class => ['privateBarProperty' => $propertyValue],
40+
* ]);
41+
*
42+
* Instances of ArrayObject, ArrayIterator and SplObjectHash can be created
43+
* by using the special "\0" property name to define their internal value:
44+
*
45+
* // creates an SplObjectHash where $info1 is attached to $obj1, etc.
46+
* Instantiator::instantiate(SplObjectStorage::class, ["\0" => [$obj1, $info1, $obj2, $info2...]]);
47+
*
48+
* // creates an ArrayObject populated with $inputArray
49+
* Instantiator::instantiate(ArrayObject::class, ["\0" => [$inputArray]]);
50+
*
51+
* @param string $class The class of the instance to create
52+
* @param array $properties The properties to set on the instance
53+
* @param array $privateProperties The private properties to set on the instance,
54+
* keyed by their declaring class
55+
*
56+
* @return object The created instance
57+
*
58+
* @throws ExceptionInterface When the instance cannot be created
59+
*/
60+
public static function instantiate(string $class, array $properties = array(), array $privateProperties = array())
61+
{
62+
$reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class);
63+
64+
if (Registry::$cloneable[$class]) {
65+
$wrappedInstance = array(clone Registry::$prototypes[$class]);
66+
} elseif (Registry::$instantiableWithoutConstructor[$class]) {
67+
$wrappedInstance = array($reflector->newInstanceWithoutConstructor());
68+
} elseif (null === Registry::$prototypes[$class]) {
69+
throw new NotInstantiableTypeException($class);
70+
} elseif ($reflector->implementsInterface('Serializable')) {
71+
$wrappedInstance = array(unserialize('C:'.\strlen($class).':"'.$class.'":0:{}'));
72+
} else {
73+
$wrappedInstance = array(unserialize('O:'.\strlen($class).':"'.$class.'":0:{}'));
74+
}
75+
76+
if ($properties) {
77+
$privateProperties[$class] = isset($privateProperties[$class]) ? $properties + $privateProperties[$class] : $properties;
78+
}
79+
80+
foreach ($privateProperties as $class => $properties) {
81+
if (!$properties) {
82+
continue;
83+
}
84+
foreach ($properties as $name => $value) {
85+
// because they're also used for "unserialization", hydrators
86+
// deal with array of instances, so we need to wrap values
87+
$properties[$name] = array($value);
88+
}
89+
(Hydrator::$hydrators[$class] ?? Hydrator::getHydrator($class))($properties, $wrappedInstance);
90+
}
91+
92+
return $wrappedInstance[0];
93+
}
94+
}

src/Symfony/Component/VarExporter/Internal/Exporter.php

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,7 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount
7777
$properties = array();
7878
$sleep = null;
7979
$arrayValue = (array) $value;
80-
81-
if (!isset(Registry::$reflectors[$class])) {
82-
Registry::getClassReflector($class);
83-
try {
84-
serialize(Registry::$prototypes[$class]);
85-
} catch (\Exception $e) {
86-
throw new NotInstantiableTypeException($class, $e);
87-
}
88-
if (\method_exists($class, '__sleep')) {
89-
Registry::getClassReflector($class, Registry::$instantiableWithoutConstructor[$class], Registry::$cloneable[$class]);
90-
}
91-
}
92-
$reflector = Registry::$reflectors[$class];
80+
$reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class);
9381
$proto = Registry::$prototypes[$class];
9482

9583
if (($value instanceof \ArrayIterator || $value instanceof \ArrayObject) && null !== $proto) {
@@ -133,7 +121,7 @@ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount
133121
foreach ($arrayValue as $name => $v) {
134122
$n = (string) $name;
135123
if ('' === $n || "\0" !== $n[0]) {
136-
$c = '*';
124+
$c = 'stdClass';
137125
$properties[$c][$n] = $v;
138126
unset($sleep[$n]);
139127
} elseif ('*' === $n[1]) {
@@ -305,7 +293,7 @@ private static function exportRegistry(Registry $value, string $indent, string $
305293
$code .= $subIndent.(1 !== $k - $j ? $k.' => ' : '');
306294
$j = $k;
307295
$eol = ",\n";
308-
$c = '[\\'.$class.'::class]';
296+
$c = '['.self::export($class).']';
309297

310298
if ($seen[$class] ?? false) {
311299
if (Registry::$cloneable[$class]) {
@@ -351,8 +339,7 @@ private static function exportHydrator(Hydrator $value, string $indent, string $
351339
{
352340
$code = '';
353341
foreach ($value->properties as $class => $properties) {
354-
$c = '*' !== $class ? '\\'.$class.'::class' : "'*'";
355-
$code .= $subIndent.' '.$c.' => '.self::export($properties, $subIndent.' ').",\n";
342+
$code .= $subIndent.' '.self::export($class).' => '.self::export($properties, $subIndent.' ').",\n";
356343
}
357344

358345
$code = array(

src/Symfony/Component/VarExporter/Internal/Hydrator.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public static function hydrate($objects, $values, $properties, $value, $wakeups)
4949

5050
public static function getHydrator($class)
5151
{
52-
if ('*' === $class) {
52+
if ('stdClass' === $class) {
5353
return self::$hydrators[$class] = static function ($properties, $objects) {
5454
foreach ($properties as $name => $values) {
5555
foreach ($values as $i => $v) {
@@ -62,7 +62,7 @@ public static function getHydrator($class)
6262
$classReflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class);
6363

6464
if (!$classReflector->isInternal()) {
65-
return self::$hydrators[$class] = (self::$hydrators['*'] ?? self::getHydrator('*'))->bindTo(null, $class);
65+
return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, $class);
6666
}
6767

6868
switch ($class) {
@@ -78,17 +78,17 @@ public static function getHydrator($class)
7878
}
7979
}
8080
}
81-
foreach ($properties["\0"] as $i => $v) {
81+
foreach ($properties["\0"] ?? array() as $i => $v) {
8282
$constructor->invokeArgs($objects[$i], $v);
8383
}
8484
};
8585

8686
case 'ErrorException':
87-
return self::$hydrators[$class] = (self::$hydrators['*'] ?? self::getHydrator('*'))->bindTo(null, new class() extends \ErrorException {
87+
return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \ErrorException {
8888
});
8989

9090
case 'TypeError':
91-
return self::$hydrators[$class] = (self::$hydrators['*'] ?? self::getHydrator('*'))->bindTo(null, new class() extends \Error {
91+
return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \Error {
9292
});
9393

9494
case 'SplObjectStorage':
@@ -119,9 +119,14 @@ public static function getHydrator($class)
119119

120120
return self::$hydrators[$class] = static function ($properties, $objects) use ($propertyReflectors) {
121121
foreach ($properties as $name => $values) {
122-
$p = $propertyReflectors[$name];
122+
if ($p = $propertyReflectors[$name] ?? null) {
123+
foreach ($values as $i => $v) {
124+
$p->setValue($objects[$i], $v);
125+
}
126+
continue;
127+
}
123128
foreach ($values as $i => $v) {
124-
$p->setValue($objects[$i], $v);
129+
$objects[$i]->$name = $v;
125130
}
126131
}
127132
};

src/Symfony/Component/VarExporter/Internal/Registry.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,19 @@ public static function f($class)
6565

6666
public static function getClassReflector($class, $instantiableWithoutConstructor = false, $cloneable = null)
6767
{
68-
if (!\class_exists($class)) {
68+
if (!\class_exists($class) && !\interface_exists($class, false) && !\trait_exists($class, false)) {
6969
throw new ClassNotFoundException($class);
7070
}
7171
$reflector = new \ReflectionClass($class);
7272

73-
if (self::$instantiableWithoutConstructor[$class] = $instantiableWithoutConstructor || !$reflector->isFinal()) {
73+
if ($instantiableWithoutConstructor) {
7474
$proto = $reflector->newInstanceWithoutConstructor();
75+
} elseif (!$reflector->isInstantiable()) {
76+
throw new NotInstantiableTypeException($class);
7577
} else {
7678
try {
7779
$proto = $reflector->newInstanceWithoutConstructor();
78-
self::$instantiableWithoutConstructor[$class] = true;
80+
$instantiableWithoutConstructor = true;
7981
} catch (\ReflectionException $e) {
8082
$proto = $reflector->implementsInterface('Serializable') ? 'C:' : 'O:';
8183
if ('C:' === $proto && !$reflector->getMethod('unserialize')->isInternal()) {
@@ -84,6 +86,19 @@ public static function getClassReflector($class, $instantiableWithoutConstructor
8486
throw new NotInstantiableTypeException($class);
8587
}
8688
}
89+
if (null !== $proto && !$proto instanceof \Throwable) {
90+
try {
91+
if (!$proto instanceof \Serializable && !\method_exists($class, '__sleep')) {
92+
serialize($proto);
93+
} elseif ($instantiableWithoutConstructor) {
94+
serialize($reflector->newInstanceWithoutConstructor());
95+
} else {
96+
serialize(unserialize(($proto instanceof \Serializable ? 'C:' : 'O:').\strlen($class).':"'.$class.'":0:{}'));
97+
}
98+
} catch (\Exception $e) {
99+
throw new NotInstantiableTypeException($class, $e);
100+
}
101+
}
87102
}
88103

89104
if (null === self::$cloneable[$class] = $cloneable) {
@@ -94,6 +109,7 @@ public static function getClassReflector($class, $instantiableWithoutConstructor
94109
self::$cloneable[$class] = $reflector->isCloneable() && !$reflector->hasMethod('__clone');
95110
}
96111

112+
self::$instantiableWithoutConstructor[$class] = $instantiableWithoutConstructor;
97113
self::$prototypes[$class] = $proto;
98114

99115
if ($proto instanceof \Throwable) {

src/Symfony/Component/VarExporter/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ The VarExporter component allows exporting any serializable PHP data structure t
55
plain PHP code. While doing so, it preserves all the semantics associated with
66
the serialization mechanism of PHP (`__wakeup`, `__sleep`, `Serializable`).
77

8+
It also provides an instantiator that allows creating and populating objects
9+
without calling their constructor nor any other methods.
10+
811
The reason to use this component *vs* `serialize()` or
912
[igbinary](https://github.com/igbinary/igbinary) is performance: thanks to
1013
OPcache, the resulting code is significantly faster and more memory efficient

src/Symfony/Component/VarExporter/Tests/Fixtures/array-iterator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
44
$o = [
5-
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes[\ArrayIterator::class] ?? \Symfony\Component\VarExporter\Internal\Registry::p(\ArrayIterator::class)),
5+
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['ArrayIterator'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('ArrayIterator')),
66
],
77
null,
88
[
9-
\ArrayIterator::class => [
9+
'ArrayIterator' => [
1010
"\0" => [
1111
[
1212
[

src/Symfony/Component/VarExporter/Tests/Fixtures/array-object-custom.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
44
$o = [
5-
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)),
5+
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['Symfony\\Component\\VarExporter\\Tests\\MyArrayObject'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('Symfony\\Component\\VarExporter\\Tests\\MyArrayObject')),
66
],
77
null,
88
[
9-
\ArrayObject::class => [
9+
'ArrayObject' => [
1010
"\0" => [
1111
[
1212
[

src/Symfony/Component/VarExporter/Tests/Fixtures/array-object.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
44
$o = [
5-
clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)[\ArrayObject::class] ?? \Symfony\Component\VarExporter\Internal\Registry::p(\ArrayObject::class)),
6-
clone $p[\ArrayObject::class],
5+
clone (($p = &\Symfony\Component\VarExporter\Internal\Registry::$prototypes)['ArrayObject'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('ArrayObject')),
6+
clone $p['ArrayObject'],
77
],
88
null,
99
[
10-
\ArrayObject::class => [
10+
'ArrayObject' => [
1111
"\0" => [
1212
[
1313
[
@@ -18,7 +18,7 @@
1818
],
1919
],
2020
],
21-
'*' => [
21+
'stdClass' => [
2222
'foo' => [
2323
$o[1],
2424
],

src/Symfony/Component/VarExporter/Tests/Fixtures/clone.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
44
$o = [
5-
(($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))(),
6-
($f[\Symfony\Component\VarExporter\Tests\MyNotCloneable::class] ?? \Symfony\Component\VarExporter\Internal\Registry::f(\Symfony\Component\VarExporter\Tests\MyNotCloneable::class))(),
5+
(($f = &\Symfony\Component\VarExporter\Internal\Registry::$factories)['Symfony\\Component\\VarExporter\\Tests\\MyCloneable'] ?? \Symfony\Component\VarExporter\Internal\Registry::f('Symfony\\Component\\VarExporter\\Tests\\MyCloneable'))(),
6+
($f['Symfony\\Component\\VarExporter\\Tests\\MyNotCloneable'] ?? \Symfony\Component\VarExporter\Internal\Registry::f('Symfony\\Component\\VarExporter\\Tests\\MyNotCloneable'))(),
77
],
88
null,
99
[],

src/Symfony/Component/VarExporter/Tests/Fixtures/datetime.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
44
$o = [
5-
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes[\DateTime::class] ?? \Symfony\Component\VarExporter\Internal\Registry::p(\DateTime::class)),
5+
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['DateTime'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('DateTime')),
66
],
77
null,
88
[
9-
'*' => [
9+
'stdClass' => [
1010
'date' => [
1111
'1970-01-01 00:00:00.000000',
1212
],

src/Symfony/Component/VarExporter/Tests/Fixtures/error.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
22

33
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
44
$o = [
5-
(\Symfony\Component\VarExporter\Internal\Registry::$factories[\Error::class] ?? \Symfony\Component\VarExporter\Internal\Registry::f(\Error::class))(),
5+
(\Symfony\Component\VarExporter\Internal\Registry::$factories['Error'] ?? \Symfony\Component\VarExporter\Internal\Registry::f('Error'))(),
66
],
77
null,
88
[
9-
\TypeError::class => [
9+
'TypeError' => [
1010
'file' => [
1111
\dirname(__DIR__).\DIRECTORY_SEPARATOR.'VarExporterTest.php',
1212
],
1313
'line' => [
1414
234,
1515
],
1616
],
17-
\Error::class => [
17+
'Error' => [
1818
'trace' => [
1919
[
2020
'file' => \dirname(__DIR__).\DIRECTORY_SEPARATOR.'VarExporterTest.php',

src/Symfony/Component/VarExporter/Tests/Fixtures/final-error.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
]),
77
null,
88
[
9-
\TypeError::class => [
9+
'TypeError' => [
1010
'file' => [
1111
\dirname(__DIR__).\DIRECTORY_SEPARATOR.'VarExporterTest.php',
1212
],

src/Symfony/Component/VarExporter/Tests/Fixtures/final-stdclass.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
44
$o = [
5-
(\Symfony\Component\VarExporter\Internal\Registry::$factories[\Symfony\Component\VarExporter\Tests\FinalStdClass::class] ?? \Symfony\Component\VarExporter\Internal\Registry::f(\Symfony\Component\VarExporter\Tests\FinalStdClass::class))(),
5+
(\Symfony\Component\VarExporter\Internal\Registry::$factories['Symfony\\Component\\VarExporter\\Tests\\FinalStdClass'] ?? \Symfony\Component\VarExporter\Internal\Registry::f('Symfony\\Component\\VarExporter\\Tests\\FinalStdClass'))(),
66
],
77
null,
88
[],

src/Symfony/Component/VarExporter/Tests/Fixtures/hard-references.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
return \Symfony\Component\VarExporter\Internal\Hydrator::hydrate(
44
$o = [
5-
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes[\stdClass::class] ?? \Symfony\Component\VarExporter\Internal\Registry::p(\stdClass::class)),
5+
clone (\Symfony\Component\VarExporter\Internal\Registry::$prototypes['stdClass'] ?? \Symfony\Component\VarExporter\Internal\Registry::p('stdClass')),
66
],
77
[
88
$r = [],

0 commit comments

Comments
 (0)