ClassExistenceResource::throwOnRequiredClass()   F
last analyzed

Complexity

Conditions 31
Paths 140

Size

Total Lines 84
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 53
dl 0
loc 84
rs 3.8333
c 0
b 0
f 0
cc 31
nc 140
nop 2

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\Config\Resource;
13
14
/**
15
 * ClassExistenceResource represents a class existence.
16
 * Freshness is only evaluated against resource existence.
17
 *
18
 * The resource must be a fully-qualified class name.
19
 *
20
 * @author Fabien Potencier <[email protected]>
21
 *
22
 * @final
23
 */
24
class ClassExistenceResource implements SelfCheckingResourceInterface
25
{
26
    private ?array $exists = null;
27
28
    private static int $autoloadLevel = 0;
29
    private static ?string $autoloadedClass = null;
30
    private static array $existsCache = [];
31
32
    /**
33
     * @param string    $resource The fully-qualified class name
34
     * @param bool|null $exists   Boolean when the existence check has already been done
35
     */
36
    public function __construct(
37
        private string $resource,
38
        ?bool $exists = null,
39
    ) {
40
        if (null !== $exists) {
41
            $this->exists = [$exists, null];
42
        }
43
    }
44
45
    public function __toString(): string
46
    {
47
        return $this->resource;
48
    }
49
50
    public function getResource(): string
51
    {
52
        return $this->resource;
53
    }
54
55
    /**
56
     * @throws \ReflectionException when a parent class/interface/trait is not found
57
     */
58
    public function isFresh(int $timestamp): bool
59
    {
60
        $loaded = class_exists($this->resource, false) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
61
62
        if (null !== $exists = &self::$existsCache[$this->resource]) {
63
            if ($loaded) {
64
                $exists = [true, null];
65
            } elseif (0 >= $timestamp && !$exists[0] && null !== $exists[1]) {
66
                throw new \ReflectionException($exists[1]);
67
            }
68
        } elseif ([false, null] === $exists = [$loaded, null]) {
69
            if (!self::$autoloadLevel++) {
70
                spl_autoload_register(__CLASS__.'::throwOnRequiredClass');
71
            }
72
            $autoloadedClass = self::$autoloadedClass;
73
            self::$autoloadedClass = ltrim($this->resource, '\\');
74
75
            try {
76
                $exists[0] = class_exists($this->resource) || interface_exists($this->resource, false) || trait_exists($this->resource, false);
77
            } catch (\Exception $e) {
78
                $exists[1] = $e->getMessage();
79
80
                try {
81
                    self::throwOnRequiredClass($this->resource, $e);
82
                } catch (\ReflectionException $e) {
83
                    if (0 >= $timestamp) {
84
                        throw $e;
85
                    }
86
                }
87
            } catch (\Throwable $e) {
88
                $exists[1] = $e->getMessage();
89
90
                throw $e;
91
            } finally {
92
                self::$autoloadedClass = $autoloadedClass;
93
                if (!--self::$autoloadLevel) {
94
                    spl_autoload_unregister(__CLASS__.'::throwOnRequiredClass');
95
                }
96
            }
97
        }
98
99
        $this->exists ??= $exists;
100
101
        return $this->exists[0] xor !$exists[0];
102
    }
103
104
    /**
105
     * @internal
106
     */
107
    public function __sleep(): array
108
    {
109
        if (null === $this->exists) {
110
            $this->isFresh(0);
111
        }
112
113
        return ['resource', 'exists'];
114
    }
115
116
    /**
117
     * @internal
118
     */
119
    public function __wakeup(): void
120
    {
121
        if (\is_bool($this->exists)) {
0 ignored issues
show
introduced by
The condition is_bool($this->exists) is always false.
Loading history...
122
            $this->exists = [$this->exists, null];
123
        }
124
    }
125
126
    /**
127
     * Throws a reflection exception when the passed class does not exist but is required.
128
     *
129
     * A class is considered "not required" when it's loaded as part of a "class_exists" or similar check.
130
     *
131
     * This function can be used as an autoload function to throw a reflection
132
     * exception if the class was not found by previous autoload functions.
133
     *
134
     * A previous exception can be passed. In this case, the class is considered as being
135
     * required totally, so if it doesn't exist, a reflection exception is always thrown.
136
     * If it exists, the previous exception is rethrown.
137
     *
138
     * @throws \ReflectionException
139
     *
140
     * @internal
141
     */
142
    public static function throwOnRequiredClass(string $class, ?\Exception $previous = null): void
143
    {
144
        // If the passed class is the resource being checked, we shouldn't throw.
145
        if (null === $previous && self::$autoloadedClass === $class) {
146
            return;
147
        }
148
149
        if (class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) {
150
            if (null !== $previous) {
151
                throw $previous;
152
            }
153
154
            return;
155
        }
156
157
        if ($previous instanceof \ReflectionException) {
158
            throw $previous;
159
        }
160
161
        $message = \sprintf('Class "%s" not found.', $class);
162
163
        if ($class !== (self::$autoloadedClass ?? $class)) {
164
            $message = substr_replace($message, \sprintf(' while loading "%s"', self::$autoloadedClass), -1, 0);
165
        }
166
167
        if (null !== $previous) {
168
            $message = $previous->getMessage();
169
        }
170
171
        $e = new \ReflectionException($message, 0, $previous);
0 ignored issues
show
Bug introduced by
It seems like $message can also be of type array; however, parameter $message of ReflectionException::__construct() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

171
        $e = new \ReflectionException(/** @scrutinizer ignore-type */ $message, 0, $previous);
Loading history...
172
173
        if (null !== $previous) {
174
            throw $e;
175
        }
176
177
        $trace = debug_backtrace();
178
        $autoloadFrame = [
179
            'function' => 'spl_autoload_call',
180
            'args' => [$class],
181
        ];
182
183
        if (isset($trace[1])) {
184
            $callerFrame = $trace[1];
185
            $i = 2;
186
        } elseif (false !== $i = array_search($autoloadFrame, $trace, true)) {
187
            $callerFrame = $trace[++$i];
188
        } else {
189
            throw $e;
190
        }
191
192
        if (isset($callerFrame['function']) && !isset($callerFrame['class'])) {
193
            switch ($callerFrame['function']) {
194
                case 'get_class_methods':
195
                case 'get_class_vars':
196
                case 'get_parent_class':
197
                case 'is_a':
198
                case 'is_subclass_of':
199
                case 'class_exists':
200
                case 'class_implements':
201
                case 'class_parents':
202
                case 'trait_exists':
203
                case 'defined':
204
                case 'interface_exists':
205
                case 'method_exists':
206
                case 'property_exists':
207
                case 'is_callable':
208
                    return;
209
            }
210
211
            $props = [
212
                'file' => $callerFrame['file'] ?? null,
213
                'line' => $callerFrame['line'] ?? null,
214
                'trace' => \array_slice($trace, 1 + $i),
215
            ];
216
217
            foreach ($props as $p => $v) {
218
                if (null !== $v) {
219
                    $r = new \ReflectionProperty(\Exception::class, $p);
220
                    $r->setValue($e, $v);
221
                }
222
            }
223
        }
224
225
        throw $e;
226
    }
227
}
228