Hydrator.php$3 ➔ getSimpleHydrator()   F
last analyzed

Complexity

Conditions 31

Size

Total Lines 104

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 104
rs 3.3333
c 0
b 0
f 0
cc 31

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Internal;
13
14
use Symfony\Component\VarExporter\Exception\ClassNotFoundException;
15
16
/**
17
 * @author Nicolas Grekas <[email protected]>
18
 *
19
 * @internal
20
 */
21
class Hydrator
22
{
23
    public const PROPERTY_HAS_HOOKS = 1;
24
    public const PROPERTY_NOT_BY_REF = 2;
25
26
    public static array $hydrators = [];
27
    public static array $simpleHydrators = [];
28
    public static array $propertyScopes = [];
29
30
    public function __construct(
31
        public readonly Registry $registry,
32
        public readonly ?Values $values,
33
        public readonly array $properties,
34
        public readonly mixed $value,
35
        public readonly array $wakeups,
36
    ) {
37
    }
38
39
    public static function hydrate($objects, $values, $properties, $value, $wakeups)
0 ignored issues
show
Unused Code introduced by
The parameter $values is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

39
    public static function hydrate($objects, /** @scrutinizer ignore-unused */ $values, $properties, $value, $wakeups)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
40
    {
41
        foreach ($properties as $class => $vars) {
42
            (self::$hydrators[$class] ??= self::getHydrator($class))($vars, $objects);
43
        }
44
        foreach ($wakeups as $k => $v) {
45
            if (\is_array($v)) {
46
                $objects[-$k]->__unserialize($v);
47
            } else {
48
                $objects[$v]->__wakeup();
49
            }
50
        }
51
52
        return $value;
53
    }
54
55
    public static function getHydrator($class)
56
    {
57
        $baseHydrator = self::$hydrators['stdClass'] ??= static function ($properties, $objects) {
58
            foreach ($properties as $name => $values) {
59
                foreach ($values as $i => $v) {
60
                    $objects[$i]->$name = $v;
61
                }
62
            }
63
        };
64
65
        switch ($class) {
66
            case 'stdClass':
67
                return $baseHydrator;
68
69
            case 'ErrorException':
70
                return $baseHydrator->bindTo(null, new class extends \ErrorException {
71
                });
72
73
            case 'TypeError':
74
                return $baseHydrator->bindTo(null, new class extends \Error {
75
                });
76
77
            case 'SplObjectStorage':
78
                return static function ($properties, $objects) {
79
                    foreach ($properties as $name => $values) {
80
                        if ("\0" === $name) {
81
                            foreach ($values as $i => $v) {
82
                                for ($j = 0; $j < \count($v); ++$j) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
83
                                    $objects[$i]->attach($v[$j], $v[++$j]);
84
                                }
85
                            }
86
                            continue;
87
                        }
88
                        foreach ($values as $i => $v) {
89
                            $objects[$i]->$name = $v;
90
                        }
91
                    }
92
                };
93
        }
94
95
        if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) {
96
            throw new ClassNotFoundException($class);
97
        }
98
        $classReflector = new \ReflectionClass($class);
99
100
        switch ($class) {
101
            case 'ArrayIterator':
102
            case 'ArrayObject':
103
                $constructor = $classReflector->getConstructor()->invokeArgs(...);
104
105
                return static function ($properties, $objects) use ($constructor) {
106
                    foreach ($properties as $name => $values) {
107
                        if ("\0" !== $name) {
108
                            foreach ($values as $i => $v) {
109
                                $objects[$i]->$name = $v;
110
                            }
111
                        }
112
                    }
113
                    foreach ($properties["\0"] ?? [] as $i => $v) {
114
                        $constructor($objects[$i], $v);
115
                    }
116
                };
117
        }
118
119
        if (!$classReflector->isInternal()) {
120
            return $baseHydrator->bindTo(null, $class);
121
        }
122
123
        if ($classReflector->name !== $class) {
124
            return self::$hydrators[$classReflector->name] ??= self::getHydrator($classReflector->name);
125
        }
126
127
        $propertySetters = [];
128
        foreach ($classReflector->getProperties() as $propertyReflector) {
129
            if (!$propertyReflector->isStatic()) {
130
                $propertySetters[$propertyReflector->name] = $propertyReflector->setValue(...);
131
            }
132
        }
133
134
        if (!$propertySetters) {
135
            return $baseHydrator;
136
        }
137
138
        return static function ($properties, $objects) use ($propertySetters) {
139
            foreach ($properties as $name => $values) {
140
                if ($setValue = $propertySetters[$name] ?? null) {
141
                    foreach ($values as $i => $v) {
142
                        $setValue($objects[$i], $v);
143
                    }
144
                    continue;
145
                }
146
                foreach ($values as $i => $v) {
147
                    $objects[$i]->$name = $v;
148
                }
149
            }
150
        };
151
    }
152
153
    public static function getSimpleHydrator($class)
154
    {
155
        $baseHydrator = self::$simpleHydrators['stdClass'] ??= (function ($properties, $object) {
156
            $notByRef = (array) $this;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $this seems to be never defined.
Loading history...
157
158
            foreach ($properties as $name => &$value) {
159
                if (!$noRef = $notByRef[$name] ?? false) {
160
                    $object->$name = $value;
161
                    $object->$name = &$value;
162
                } elseif (true !== $noRef) {
163
                    $noRef($object, $value);
164
                } else {
165
                    $object->$name = $value;
166
                }
167
            }
168
        })->bindTo(new \stdClass());
169
170
        switch ($class) {
171
            case 'stdClass':
172
                return $baseHydrator;
173
174
            case 'ErrorException':
175
                return $baseHydrator->bindTo(new \stdClass(), new class extends \ErrorException {
176
                });
177
178
            case 'TypeError':
179
                return $baseHydrator->bindTo(new \stdClass(), new class extends \Error {
180
                });
181
182
            case 'SplObjectStorage':
183
                return static function ($properties, $object) {
184
                    foreach ($properties as $name => &$value) {
185
                        if ("\0" !== $name) {
186
                            $object->$name = $value;
187
                            $object->$name = &$value;
188
                            continue;
189
                        }
190
                        for ($i = 0; $i < \count($value); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
191
                            $object->attach($value[$i], $value[++$i]);
192
                        }
193
                    }
194
                };
195
        }
196
197
        if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) {
198
            throw new ClassNotFoundException($class);
199
        }
200
        $classReflector = new \ReflectionClass($class);
201
202
        switch ($class) {
203
            case 'ArrayIterator':
204
            case 'ArrayObject':
205
                $constructor = $classReflector->getConstructor()->invokeArgs(...);
206
207
                return static function ($properties, $object) use ($constructor) {
208
                    foreach ($properties as $name => &$value) {
209
                        if ("\0" === $name) {
210
                            $constructor($object, $value);
211
                        } else {
212
                            $object->$name = $value;
213
                            $object->$name = &$value;
214
                        }
215
                    }
216
                };
217
        }
218
219
        if (!$classReflector->isInternal()) {
220
            $notByRef = new \stdClass();
221
            foreach ($classReflector->getProperties() as $propertyReflector) {
222
                if ($propertyReflector->isStatic()) {
223
                    continue;
224
                }
225
                if (\PHP_VERSION_ID >= 80400 && !$propertyReflector->isAbstract() && $propertyReflector->getHooks()) {
0 ignored issues
show
Bug introduced by
The method getHooks() does not exist on ReflectionProperty. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

225
                if (\PHP_VERSION_ID >= 80400 && !$propertyReflector->isAbstract() && $propertyReflector->/** @scrutinizer ignore-call */ getHooks()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method isAbstract() does not exist on ReflectionProperty. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

225
                if (\PHP_VERSION_ID >= 80400 && !$propertyReflector->/** @scrutinizer ignore-call */ isAbstract() && $propertyReflector->getHooks()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
226
                    $notByRef->{$propertyReflector->name} = $propertyReflector->setRawValue(...);
227
                } elseif ($propertyReflector->isReadOnly()) {
0 ignored issues
show
Bug introduced by
The method isReadOnly() does not exist on ReflectionProperty. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

227
                } elseif ($propertyReflector->/** @scrutinizer ignore-call */ isReadOnly()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
228
                    $notByRef->{$propertyReflector->name} = true;
229
                }
230
            }
231
232
            return $baseHydrator->bindTo($notByRef, $class);
233
        }
234
235
        if ($classReflector->name !== $class) {
236
            return self::$simpleHydrators[$classReflector->name] ??= self::getSimpleHydrator($classReflector->name);
237
        }
238
239
        $propertySetters = [];
240
        foreach ($classReflector->getProperties() as $propertyReflector) {
241
            if (!$propertyReflector->isStatic()) {
242
                $propertySetters[$propertyReflector->name] = $propertyReflector->setValue(...);
243
            }
244
        }
245
246
        if (!$propertySetters) {
247
            return $baseHydrator;
248
        }
249
250
        return static function ($properties, $object) use ($propertySetters) {
251
            foreach ($properties as $name => &$value) {
252
                if ($setValue = $propertySetters[$name] ?? null) {
253
                    $setValue($object, $value);
254
                } else {
255
                    $object->$name = $value;
256
                    $object->$name = &$value;
257
                }
258
            }
259
        };
260
    }
261
262
    public static function getPropertyScopes($class): array
263
    {
264
        $propertyScopes = [];
265
        $r = new \ReflectionClass($class);
266
267
        foreach ($r->getProperties() as $property) {
268
            $flags = $property->getModifiers();
269
270
            if (\ReflectionProperty::IS_STATIC & $flags) {
271
                continue;
272
            }
273
            $name = $property->name;
274
            $access = ($flags << 2) | ($flags & \ReflectionProperty::IS_READONLY ? self::PROPERTY_NOT_BY_REF : 0);
0 ignored issues
show
Bug introduced by
The constant ReflectionProperty::IS_READONLY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
275
276
            if (\PHP_VERSION_ID >= 80400 && !$property->isAbstract() && $h = $property->getHooks()) {
277
                $access |= self::PROPERTY_HAS_HOOKS | (isset($h['get']) && !$h['get']->returnsReference() ? self::PROPERTY_NOT_BY_REF : 0);
278
            }
279
280
            if (\ReflectionProperty::IS_PRIVATE & $flags) {
281
                $propertyScopes["\0$class\0$name"] = $propertyScopes[$name] = [$class, $name, null, $access, $property];
282
283
                continue;
284
            }
285
286
            $propertyScopes[$name] = [$class, $name, null, $access, $property];
287
288
            if ($flags & (\PHP_VERSION_ID >= 80400 ? \ReflectionProperty::IS_PRIVATE_SET : \ReflectionProperty::IS_READONLY)) {
0 ignored issues
show
Bug introduced by
The constant ReflectionProperty::IS_PRIVATE_SET was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
289
                $propertyScopes[$name][2] = $property->class;
290
            }
291
292
            if (\ReflectionProperty::IS_PROTECTED & $flags) {
293
                $propertyScopes["\0*\0$name"] = $propertyScopes[$name];
294
            }
295
        }
296
297
        while ($r = $r->getParentClass()) {
298
            $class = $r->name;
299
300
            foreach ($r->getProperties(\ReflectionProperty::IS_PRIVATE) as $property) {
301
                $flags = $property->getModifiers();
302
303
                if (\ReflectionProperty::IS_STATIC & $flags) {
304
                    continue;
305
                }
306
                $name = $property->name;
307
                $access = ($flags << 2) | ($flags & \ReflectionProperty::IS_READONLY ? self::PROPERTY_NOT_BY_REF : 0);
308
309
                if (\PHP_VERSION_ID >= 80400 && $h = $property->getHooks()) {
310
                    $access |= self::PROPERTY_HAS_HOOKS | (isset($h['get']) && !$h['get']->returnsReference() ? self::PROPERTY_NOT_BY_REF : 0);
311
                }
312
313
                $propertyScopes["\0$class\0$name"] = [$class, $name, null, $access, $property];
314
                $propertyScopes[$name] ??= $propertyScopes["\0$class\0$name"];
315
            }
316
        }
317
318
        return $propertyScopes;
319
    }
320
}
321