Total Complexity | 56 |
Total Lines | 202 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like ClassExistenceResource 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 ClassExistenceResource, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
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 |
||
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 |
||
226 | } |
||
227 | } |
||
228 |