1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\VarExporter\Internal;
13
14use Symfony\Component\VarExporter\Exception\ClassNotFoundException;
15
16/**
17 * @author Nicolas Grekas <p@tchwork.com>
18 *
19 * @internal
20 */
21class Hydrator
22{
23    public static $hydrators = [];
24
25    public $registry;
26    public $values;
27    public $properties;
28    public $value;
29    public $wakeups;
30
31    public function __construct(?Registry $registry, ?Values $values, array $properties, $value, array $wakeups)
32    {
33        $this->registry = $registry;
34        $this->values = $values;
35        $this->properties = $properties;
36        $this->value = $value;
37        $this->wakeups = $wakeups;
38    }
39
40    public static function hydrate($objects, $values, $properties, $value, $wakeups)
41    {
42        foreach ($properties as $class => $vars) {
43            (self::$hydrators[$class] ?? self::getHydrator($class))($vars, $objects);
44        }
45        foreach ($wakeups as $k => $v) {
46            if (\is_array($v)) {
47                $objects[-$k]->__unserialize($v);
48            } else {
49                $objects[$v]->__wakeup();
50            }
51        }
52
53        return $value;
54    }
55
56    public static function getHydrator($class)
57    {
58        if ('stdClass' === $class) {
59            return self::$hydrators[$class] = static function ($properties, $objects) {
60                foreach ($properties as $name => $values) {
61                    foreach ($values as $i => $v) {
62                        $objects[$i]->$name = $v;
63                    }
64                }
65            };
66        }
67
68        if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) {
69            throw new ClassNotFoundException($class);
70        }
71        $classReflector = new \ReflectionClass($class);
72
73        if (!$classReflector->isInternal()) {
74            return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, $class);
75        }
76
77        if ($classReflector->name !== $class) {
78            return self::$hydrators[$classReflector->name] ?? self::getHydrator($classReflector->name);
79        }
80
81        switch ($class) {
82            case 'ArrayIterator':
83            case 'ArrayObject':
84                $constructor = \Closure::fromCallable([$classReflector->getConstructor(), 'invokeArgs']);
85
86                return self::$hydrators[$class] = static function ($properties, $objects) use ($constructor) {
87                    foreach ($properties as $name => $values) {
88                        if ("\0" !== $name) {
89                            foreach ($values as $i => $v) {
90                                $objects[$i]->$name = $v;
91                            }
92                        }
93                    }
94                    foreach ($properties["\0"] ?? [] as $i => $v) {
95                        $constructor($objects[$i], $v);
96                    }
97                };
98
99            case 'ErrorException':
100                return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \ErrorException {
101                });
102
103            case 'TypeError':
104                return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \Error {
105                });
106
107            case 'SplObjectStorage':
108                return self::$hydrators[$class] = static function ($properties, $objects) {
109                    foreach ($properties as $name => $values) {
110                        if ("\0" === $name) {
111                            foreach ($values as $i => $v) {
112                                for ($j = 0; $j < \count($v); ++$j) {
113                                    $objects[$i]->attach($v[$j], $v[++$j]);
114                                }
115                            }
116                            continue;
117                        }
118                        foreach ($values as $i => $v) {
119                            $objects[$i]->$name = $v;
120                        }
121                    }
122                };
123        }
124
125        $propertySetters = [];
126        foreach ($classReflector->getProperties() as $propertyReflector) {
127            if (!$propertyReflector->isStatic()) {
128                $propertyReflector->setAccessible(true);
129                $propertySetters[$propertyReflector->name] = \Closure::fromCallable([$propertyReflector, 'setValue']);
130            }
131        }
132
133        if (!$propertySetters) {
134            return self::$hydrators[$class] = self::$hydrators['stdClass'] ?? self::getHydrator('stdClass');
135        }
136
137        return self::$hydrators[$class] = static function ($properties, $objects) use ($propertySetters) {
138            foreach ($properties as $name => $values) {
139                if ($setValue = $propertySetters[$name] ?? null) {
140                    foreach ($values as $i => $v) {
141                        $setValue($objects[$i], $v);
142                    }
143                    continue;
144                }
145                foreach ($values as $i => $v) {
146                    $objects[$i]->$name = $v;
147                }
148            }
149        };
150    }
151}
152