LazyProxyTrait   F
last analyzed

Complexity

Total Complexity 83

Size/Duplication

Total Lines 349
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 83
eloc 181
dl 0
loc 349
rs 2
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
F __get() 0 72 21
B createLazyProxy() 0 33 8
A initializeLazyObject() 0 7 2
A isLazyObjectInitialized() 0 4 3
B __unset() 0 32 8
A __destruct() 0 8 3
A resetLazyObject() 0 9 3
B __isset() 0 31 8
B __set() 0 32 8
A __clone() 0 14 4
B __serialize() 0 34 9
A __unserialize() 0 23 6

How to fix   Complexity   

Complex Class

Complex classes like LazyProxyTrait often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use LazyProxyTrait, and based on these observations, apply Extract Interface, too.

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\Serializer\Attribute\Ignore;
15
use Symfony\Component\VarExporter\Hydrator as PublicHydrator;
16
use Symfony\Component\VarExporter\Internal\Hydrator;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Symfony\Component\VarExporter\Hydrator. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
17
use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry;
18
use Symfony\Component\VarExporter\Internal\LazyObjectState;
19
use Symfony\Component\VarExporter\Internal\LazyObjectTrait;
20
21
trait LazyProxyTrait
22
{
23
    use LazyObjectTrait;
24
25
    /**
26
     * Creates a lazy-loading virtual proxy.
27
     *
28
     * @param \Closure():object $initializer Returns the proxied object
29
     * @param static|null       $instance
30
     */
31
    public static function createLazyProxy(\Closure $initializer, ?object $instance = null): static
32
    {
33
        if (self::class !== $class = $instance ? $instance::class : static::class) {
34
            $skippedProperties = ["\0".self::class."\0lazyObjectState" => true];
35
        }
36
37
        if (!isset(Registry::$defaultProperties[$class])) {
38
            Registry::$classReflectors[$class] ??= new \ReflectionClass($class);
39
            $instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor();
40
            Registry::$defaultProperties[$class] ??= (array) $instance;
41
            Registry::$classResetters[$class] ??= Registry::getClassResetters($class);
42
43
            if (self::class === $class && \defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) {
44
                Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES;
45
            }
46
        } else {
47
            $instance ??= Registry::$classReflectors[$class]->newInstanceWithoutConstructor();
48
        }
49
50
        if (isset($instance->lazyObjectState)) {
51
            $instance->lazyObjectState->initializer = $initializer;
52
            unset($instance->lazyObjectState->realInstance);
53
54
            return $instance;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $instance returns the type object which includes types incompatible with the type-hinted return Symfony\Component\VarExporter\LazyProxyTrait.
Loading history...
55
        }
56
57
        $instance->lazyObjectState = new LazyObjectState($initializer);
58
59
        foreach (Registry::$classResetters[$class] as $reset) {
60
            $reset($instance, $skippedProperties ??= []);
61
        }
62
63
        return $instance;
64
    }
65
66
    /**
67
     * Returns whether the object is initialized.
68
     *
69
     * @param bool $partial Whether partially initialized objects should be considered as initialized
70
     */
71
    #[Ignore]
72
    public function isLazyObjectInitialized(bool $partial = false): bool
0 ignored issues
show
Unused Code introduced by
The parameter $partial 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

72
    public function isLazyObjectInitialized(/** @scrutinizer ignore-unused */ bool $partial = false): bool

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...
73
    {
74
        return !isset($this->lazyObjectState) || isset($this->lazyObjectState->realInstance) || Registry::$noInitializerState === $this->lazyObjectState->initializer;
75
    }
76
77
    /**
78
     * Forces initialization of a lazy object and returns it.
79
     */
80
    public function initializeLazyObject(): parent
81
    {
82
        if ($state = $this->lazyObjectState ?? null) {
83
            return $state->realInstance ??= ($state->initializer)();
84
        }
85
86
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Symfony\Component\VarExporter\LazyProxyTrait which is incompatible with the type-hinted return parent.
Loading history...
87
    }
88
89
    /**
90
     * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object
91
     */
92
    public function resetLazyObject(): bool
93
    {
94
        if (!isset($this->lazyObjectState) || Registry::$noInitializerState === $this->lazyObjectState->initializer) {
95
            return false;
96
        }
97
98
        unset($this->lazyObjectState->realInstance);
99
100
        return true;
101
    }
102
103
    public function &__get($name): mixed
104
    {
105
        $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
106
        $scope = null;
107
        $instance = $this;
108
        $notByRef = 0;
109
110
        if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
111
            $notByRef = $access & Hydrator::PROPERTY_NOT_BY_REF;
112
            $scope = Registry::getScopeForRead($propertyScopes, $class, $name);
113
114
            if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) {
115
                if ($state = $this->lazyObjectState ?? null) {
116
                    $instance = $state->realInstance ??= ($state->initializer)();
117
                }
118
                if (\PHP_VERSION_ID >= 80400 && !$notByRef && ($access >> 2) & \ReflectionProperty::IS_PRIVATE_SET) {
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...
119
                    $scope ??= $writeScope;
120
                }
121
                $parent = 2;
122
                goto get_in_scope;
123
            }
124
        }
125
        $parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get'];
126
127
        if ($state = $this->lazyObjectState ?? null) {
128
            $instance = $state->realInstance ??= ($state->initializer)();
129
        } else {
130
            if (2 === $parent) {
131
                return parent::__get($name);
132
            }
133
            $value = parent::__get($name);
134
135
            return $value;
136
        }
137
138
        if (!$parent && null === $class && !\array_key_exists($name, (array) $instance)) {
139
            $frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0];
140
            trigger_error(\sprintf('Undefined property: %s::$%s in %s on line %s', $instance::class, $name, $frame['file'], $frame['line']), \E_USER_NOTICE);
141
        }
142
143
        get_in_scope:
144
        $notByRef = $notByRef || 1 === $parent;
145
146
        try {
147
            if (null === $scope) {
148
                if (!$notByRef) {
149
                    return $instance->$name;
150
                }
151
                $value = $instance->$name;
152
153
                return $value;
154
            }
155
            $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
156
157
            return $accessor['get']($instance, $name, $notByRef);
158
        } catch (\Error $e) {
159
            if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) {
160
                throw $e;
161
            }
162
163
            try {
164
                if (null === $scope) {
165
                    $instance->$name = [];
166
167
                    return $instance->$name;
168
                }
169
170
                $accessor['set']($instance, $name, []);
171
172
                return $accessor['get']($instance, $name, $notByRef);
173
            } catch (\Error) {
174
                throw $e;
175
            }
176
        }
177
    }
178
179
    public function __set($name, $value): void
180
    {
181
        $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
182
        $scope = null;
183
        $instance = $this;
184
185
        if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
186
            $scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2);
187
188
            if ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"])) {
189
                if ($state = $this->lazyObjectState ?? null) {
190
                    $instance = $state->realInstance ??= ($state->initializer)();
191
                }
192
                goto set_in_scope;
193
            }
194
        }
195
196
        if ($state = $this->lazyObjectState ?? null) {
197
            $instance = $state->realInstance ??= ($state->initializer)();
198
        } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) {
199
            parent::__set($name, $value);
200
201
            return;
202
        }
203
204
        set_in_scope:
205
206
        if (null === $scope) {
207
            $instance->$name = $value;
208
        } else {
209
            $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
210
            $accessor['set']($instance, $name, $value);
211
        }
212
    }
213
214
    public function __isset($name): bool
215
    {
216
        $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
217
        $scope = null;
218
        $instance = $this;
219
220
        if ([$class] = $propertyScopes[$name] ?? null) {
221
            $scope = Registry::getScopeForRead($propertyScopes, $class, $name);
222
223
            if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) {
224
                if ($state = $this->lazyObjectState ?? null) {
225
                    $instance = $state->realInstance ??= ($state->initializer)();
226
                }
227
                goto isset_in_scope;
228
            }
229
        }
230
231
        if ($state = $this->lazyObjectState ?? null) {
232
            $instance = $state->realInstance ??= ($state->initializer)();
233
        } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) {
234
            return parent::__isset($name);
235
        }
236
237
        isset_in_scope:
238
239
        if (null === $scope) {
240
            return isset($instance->$name);
241
        }
242
        $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
243
244
        return $accessor['isset']($instance, $name);
245
    }
246
247
    public function __unset($name): void
248
    {
249
        $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
250
        $scope = null;
251
        $instance = $this;
252
253
        if ([$class, , $writeScope, $access] = $propertyScopes[$name] ?? null) {
254
            $scope = Registry::getScopeForWrite($propertyScopes, $class, $name, $access >> 2);
255
256
            if ($writeScope === $scope || isset($propertyScopes["\0$scope\0$name"])) {
257
                if ($state = $this->lazyObjectState ?? null) {
258
                    $instance = $state->realInstance ??= ($state->initializer)();
259
                }
260
                goto unset_in_scope;
261
            }
262
        }
263
264
        if ($state = $this->lazyObjectState ?? null) {
265
            $instance = $state->realInstance ??= ($state->initializer)();
266
        } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) {
267
            parent::__unset($name);
268
269
            return;
270
        }
271
272
        unset_in_scope:
273
274
        if (null === $scope) {
275
            unset($instance->$name);
276
        } else {
277
            $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
278
            $accessor['unset']($instance, $name);
279
        }
280
    }
281
282
    public function __clone(): void
283
    {
284
        if (!isset($this->lazyObjectState)) {
285
            if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) {
286
                parent::__clone();
287
            }
288
289
            return;
290
        }
291
292
        $this->lazyObjectState = clone $this->lazyObjectState;
293
294
        if (isset($this->lazyObjectState->realInstance)) {
295
            $this->lazyObjectState->realInstance = clone $this->lazyObjectState->realInstance;
296
        }
297
    }
298
299
    public function __serialize(): array
300
    {
301
        $class = self::class;
302
        $state = $this->lazyObjectState ?? null;
303
304
        if (!$state && (Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) {
305
            $properties = parent::__serialize();
306
        } else {
307
            $properties = (array) $this;
308
309
            if ($state) {
310
                unset($properties["\0$class\0lazyObjectState"]);
311
                $properties["\0$class\0lazyObjectReal"] = $state->realInstance ??= ($state->initializer)();
312
            }
313
        }
314
315
        if ($state || Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) {
316
            return $properties;
317
        }
318
319
        $scope = get_parent_class($class);
320
        $data = [];
321
322
        foreach (parent::__sleep() as $name) {
323
            $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0$class\0$name"] ?? $properties[$k = "\0$scope\0$name"] ?? $k = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $k is dead and can be removed.
Loading history...
324
325
            if (null === $k) {
326
                trigger_error(\sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $name), \E_USER_NOTICE);
327
            } else {
328
                $data[$k] = $value;
329
            }
330
        }
331
332
        return $data;
333
    }
334
335
    public function __unserialize(array $data): void
336
    {
337
        $class = self::class;
338
339
        if ($instance = $data["\0$class\0lazyObjectReal"] ?? null) {
340
            unset($data["\0$class\0lazyObjectReal"]);
341
342
            foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) {
343
                $reset($this, $data);
344
            }
345
346
            if ($data) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
347
                PublicHydrator::hydrate($this, $data);
348
            }
349
            $this->lazyObjectState = new LazyObjectState(Registry::$noInitializerState ??= static fn () => throw new \LogicException('Lazy proxy has no initializer.'));
350
            $this->lazyObjectState->realInstance = $instance;
351
        } elseif ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['unserialize']) {
352
            parent::__unserialize($data);
353
        } else {
354
            PublicHydrator::hydrate($this, $data);
355
356
            if (Registry::$parentMethods[$class]['wakeup']) {
357
                parent::__wakeup();
358
            }
359
        }
360
    }
361
362
    public function __destruct()
363
    {
364
        if (isset($this->lazyObjectState)) {
365
            return;
366
        }
367
368
        if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) {
369
            parent::__destruct();
370
        }
371
    }
372
}
373