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

Skip to content

Commit 1540977

Browse files
[VarExporter] Add Hydrator::hydrate() and preserve PHP references when using it
1 parent a057029 commit 1540977

File tree

5 files changed

+188
-26
lines changed

5 files changed

+188
-26
lines changed

src/Symfony/Component/VarExporter/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
CHANGELOG
22
=========
33

4+
6.2
5+
---
6+
7+
* Add `Hydrator::hydrate()`
8+
* Preserve PHP references also when using `Hydrator::hydrate()` or `Instantiator::instantiate()`
9+
410
5.1.0
511
-----
612

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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\Internal\Hydrator as InternalHydrator;
15+
16+
/**
17+
* A utility class to hydrate objects' properties.
18+
*
19+
* @author Nicolas Grekas <[email protected]>
20+
*/
21+
final class Hydrator
22+
{
23+
/**
24+
* Sets the properties of an object without calling its constructor nor any other methods.
25+
*
26+
* For example:
27+
*
28+
* // Sets the public or protected $object->propertyName property
29+
* Hydrator::hydrate($object, ['propertyName' => $propertyValue]);
30+
*
31+
* // creates a Foo instance and sets a private property defined on its parent Bar class
32+
* // Sets the private $object->privateBarProperty property
33+
* Hydrator::hydrate($object, [], [
34+
* Bar::class => ['privateBarProperty' => $propertyValue],
35+
* ]);
36+
*
37+
* Instances of ArrayObject, ArrayIterator and SplObjectHash can be hydrated
38+
* by using the special "\0" property name to define their internal value:
39+
*
40+
* // creates an SplObjectHash where $info1 is attached to $obj1, etc.
41+
* Hydrator::hydrate($object, ["\0" => [$obj1, $info1, $obj2, $info2...]]);
42+
*
43+
* // creates an ArrayObject populated with $inputArray
44+
* Hydrator::hydrate($object, ["\0" => [$inputArray]]);
45+
*
46+
* @template T of object
47+
*
48+
* @param T $instance The object to hydrate
49+
* @param array<string, mixed> $properties The properties to set on the instance
50+
* @param array<class-string, array<string, mixed>> $privateProperties The private properties to set on the instance,
51+
* keyed by their declaring class
52+
*
53+
* @return T
54+
*/
55+
public static function hydrate(object $instance, array $properties = [], array $privateProperties = []): object
56+
{
57+
if ($properties) {
58+
$class = \get_class($instance);
59+
$privateProperties[$class] = isset($privateProperties[$class]) ? $properties + $privateProperties[$class] : $properties;
60+
}
61+
62+
foreach ($privateProperties as $class => $properties) {
63+
if ($properties) {
64+
(InternalHydrator::$simpleHydrators[$class] ?? InternalHydrator::getSimpleHydrator($class))($properties, $instance);
65+
}
66+
}
67+
68+
return $instance;
69+
}
70+
}

src/Symfony/Component/VarExporter/Instantiator.php

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
use Symfony\Component\VarExporter\Exception\ExceptionInterface;
1515
use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException;
16-
use Symfony\Component\VarExporter\Internal\Hydrator;
1716
use Symfony\Component\VarExporter\Internal\Registry;
1817

1918
/**
@@ -48,10 +47,14 @@ final class Instantiator
4847
* // creates an ArrayObject populated with $inputArray
4948
* Instantiator::instantiate(ArrayObject::class, ["\0" => [$inputArray]]);
5049
*
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
50+
* @template T of object
51+
*
52+
* @param class-string<T> $class The class of the instance to create
53+
* @param array<string, mixed> $properties The properties to set on the instance
54+
* @param array<class-string, array<string, mixed>> $privateProperties The private properties to set on the instance,
55+
* keyed by their declaring class
56+
*
57+
* @return T
5558
*
5659
* @throws ExceptionInterface When the instance cannot be created
5760
*/
@@ -60,33 +63,17 @@ public static function instantiate(string $class, array $properties = [], array
6063
$reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class);
6164

6265
if (Registry::$cloneable[$class]) {
63-
$wrappedInstance = [clone Registry::$prototypes[$class]];
66+
$instance = clone Registry::$prototypes[$class];
6467
} elseif (Registry::$instantiableWithoutConstructor[$class]) {
65-
$wrappedInstance = [$reflector->newInstanceWithoutConstructor()];
68+
$instance = $reflector->newInstanceWithoutConstructor();
6669
} elseif (null === Registry::$prototypes[$class]) {
6770
throw new NotInstantiableTypeException($class);
6871
} elseif ($reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize')) {
69-
$wrappedInstance = [unserialize('C:'.\strlen($class).':"'.$class.'":0:{}')];
72+
$instance = unserialize('C:'.\strlen($class).':"'.$class.'":0:{}');
7073
} else {
71-
$wrappedInstance = [unserialize('O:'.\strlen($class).':"'.$class.'":0:{}')];
72-
}
73-
74-
if ($properties) {
75-
$privateProperties[$class] = isset($privateProperties[$class]) ? $properties + $privateProperties[$class] : $properties;
76-
}
77-
78-
foreach ($privateProperties as $class => $properties) {
79-
if (!$properties) {
80-
continue;
81-
}
82-
foreach ($properties as $name => $value) {
83-
// because they're also used for "unserialization", hydrators
84-
// deal with array of instances, so we need to wrap values
85-
$properties[$name] = [$value];
86-
}
87-
(Hydrator::$hydrators[$class] ?? Hydrator::getHydrator($class))($properties, $wrappedInstance);
74+
$instance = unserialize('O:'.\strlen($class).':"'.$class.'":0:{}');
8875
}
8976

90-
return $wrappedInstance[0];
77+
return Hydrator::hydrate($instance, $properties, $privateProperties);
9178
}
9279
}

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

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
class Hydrator
2222
{
2323
public static $hydrators = [];
24+
public static $simpleHydrators = [];
2425

2526
public $registry;
2627
public $values;
@@ -148,4 +149,87 @@ public static function getHydrator($class)
148149
}
149150
};
150151
}
152+
153+
public static function getSimpleHydrator($class)
154+
{
155+
switch ($class) {
156+
case 'stdClass':
157+
return self::$simpleHydrators[$class] = static function ($properties, $object) {
158+
foreach ($properties as $name => &$value) {
159+
$object->$name = &$value;
160+
}
161+
};
162+
163+
case 'ErrorException':
164+
return self::$simpleHydrators[$class] = (self::$simpleHydrators['stdClass'] ?? self::getSimpleHydrator('stdClass'))->bindTo(null, new class() extends \ErrorException {
165+
});
166+
167+
case 'TypeError':
168+
return self::$simpleHydrators[$class] = (self::$simpleHydrators['stdClass'] ?? self::getSimpleHydrator('stdClass'))->bindTo(null, new class() extends \Error {
169+
});
170+
171+
case 'SplObjectStorage':
172+
return self::$simpleHydrators[$class] = static function ($properties, $object) {
173+
foreach ($properties as $name => &$value) {
174+
if ("\0" !== $name) {
175+
$object->$name = &$value;
176+
continue;
177+
}
178+
for ($i = 0; $i < \count($value); ++$i) {
179+
$object->attach($value[$i], $value[++$i]);
180+
}
181+
}
182+
};
183+
}
184+
185+
if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) {
186+
throw new ClassNotFoundException($class);
187+
}
188+
$classReflector = new \ReflectionClass($class);
189+
190+
switch ($class) {
191+
case 'ArrayIterator':
192+
case 'ArrayObject':
193+
$constructor = $classReflector->getConstructor()->invokeArgs(...);
194+
195+
return self::$simpleHydrators[$class] = static function ($properties, $object) use ($constructor) {
196+
foreach ($properties as $name => &$value) {
197+
if ("\0" === $name) {
198+
$constructor($object, $value);
199+
} else {
200+
$object->$name = &$value;
201+
}
202+
}
203+
};
204+
}
205+
206+
if (!$classReflector->isInternal()) {
207+
return self::$simpleHydrators[$class] = (self::$simpleHydrators['stdClass'] ?? self::getSimpleHydrator('stdClass'))->bindTo(null, $class);
208+
}
209+
210+
if ($classReflector->name !== $class) {
211+
return self::$simpleHydrators[$classReflector->name] ?? self::getSimpleHydrator($classReflector->name);
212+
}
213+
214+
$propertySetters = [];
215+
foreach ($classReflector->getProperties() as $propertyReflector) {
216+
if (!$propertyReflector->isStatic()) {
217+
$propertySetters[$propertyReflector->name] = $propertyReflector->setValue(...);
218+
}
219+
}
220+
221+
if (!$propertySetters) {
222+
return self::$simpleHydrators[$class] = self::$simpleHydrators['stdClass'] ?? self::getSimpleHydrator('stdClass');
223+
}
224+
225+
return self::$simpleHydrators[$class] = static function ($properties, $object) use ($propertySetters) {
226+
foreach ($properties as $name => &$value) {
227+
if ($setValue = $propertySetters[$name] ?? null) {
228+
$setValue($object, $value);
229+
} else {
230+
$object->$name = &$value;
231+
}
232+
}
233+
};
234+
}
151235
}

src/Symfony/Component/VarExporter/Tests/InstantiatorTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,21 @@ public function testInstantiate()
6565

6666
$this->assertSame([234], $e->getTrace());
6767
}
68+
69+
public function testPhpReferences()
70+
{
71+
$properties = ['p1' => 1];
72+
$properties['p2'] = &$properties['p1'];
73+
74+
$obj = Instantiator::instantiate('stdClass', $properties);
75+
76+
$this->assertSame($properties, (array) $obj);
77+
78+
$properties['p1'] = 2;
79+
$this->assertSame(2, $properties['p2']);
80+
$this->assertSame(2, $obj->p1);
81+
$this->assertSame(2, $obj->p2);
82+
}
6883
}
6984

7085
class Foo

0 commit comments

Comments
 (0)