Total Complexity | 43 |
Total Lines | 253 |
Duplicated Lines | 0 % |
Coverage | 99.07% |
Changes | 3 | ||
Bugs | 0 | Features | 0 |
Complex classes like Container 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 Container, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
17 | class Container implements ContainerInterface |
||
18 | { |
||
19 | private ?DependencyResolver $dependencyResolver = null; |
||
20 | |||
21 | /** @var array<class-string|string, list<mixed>> */ |
||
1 ignored issue
–
show
|
|||
22 | private array $cachedDependencies = []; |
||
23 | |||
24 | /** @var array<string,mixed> */ |
||
25 | private array $instances = []; |
||
26 | |||
27 | private SplObjectStorage $factoryInstances; |
||
28 | |||
29 | private SplObjectStorage $protectedInstances; |
||
30 | |||
31 | /** @var array<string,bool> */ |
||
32 | private array $frozenInstances = []; |
||
33 | |||
34 | private ?string $currentlyExtending = null; |
||
35 | |||
36 | /** |
||
37 | * @param array<class-string, class-string|callable|object> $bindings |
||
38 | * @param array<string, list<Closure>> $instancesToExtend |
||
39 | */ |
||
40 | 42 | public function __construct( |
|
46 | } |
||
47 | |||
48 | /** |
||
49 | * @param class-string $className |
||
50 | */ |
||
51 | 5 | public static function create(string $className): mixed |
|
54 | } |
||
55 | |||
56 | 37 | public function has(string $id): bool |
|
57 | { |
||
58 | 37 | return isset($this->instances[$id]); |
|
59 | } |
||
60 | |||
61 | 19 | public function set(string $id, mixed $instance): void |
|
62 | { |
||
63 | 19 | if (!empty($this->frozenInstances[$id])) { |
|
64 | 1 | throw ContainerException::frozenInstanceOverride($id); |
|
65 | } |
||
66 | |||
67 | 19 | $this->instances[$id] = $instance; |
|
68 | |||
69 | 19 | if ($this->currentlyExtending === $id) { |
|
70 | 3 | return; |
|
71 | } |
||
72 | |||
73 | 19 | $this->extendService($id); |
|
74 | } |
||
75 | |||
76 | /** |
||
77 | * @param class-string|string $id |
||
78 | */ |
||
79 | 33 | public function get(string $id): mixed |
|
80 | { |
||
81 | 33 | if ($this->has($id)) { |
|
82 | 15 | return $this->getInstance($id); |
|
83 | } |
||
84 | |||
85 | 18 | return $this->createInstance($id); |
|
86 | } |
||
87 | |||
88 | 5 | public function resolve(callable $callable): mixed |
|
89 | { |
||
90 | 5 | $callable = Closure::fromCallable($callable); |
|
91 | 5 | $reflectionFn = new ReflectionFunction($callable); |
|
92 | 5 | $callableKey = md5(serialize($reflectionFn->__toString())); |
|
93 | |||
94 | 5 | if (!isset($this->cachedDependencies[$callableKey])) { |
|
95 | 5 | $this->cachedDependencies[$callableKey] = $this |
|
96 | 5 | ->getDependencyResolver() |
|
97 | 5 | ->resolveDependencies($callable); |
|
98 | } |
||
99 | |||
100 | /** @psalm-suppress MixedMethodCall */ |
||
101 | 5 | return $callable(...$this->cachedDependencies[$callableKey]); |
|
102 | } |
||
103 | |||
104 | 1 | public function factory(Closure $instance): Closure |
|
105 | { |
||
106 | 1 | $this->factoryInstances->attach($instance); |
|
107 | |||
108 | 1 | return $instance; |
|
109 | } |
||
110 | |||
111 | 1 | public function remove(string $id): void |
|
112 | { |
||
113 | 1 | unset( |
|
114 | 1 | $this->instances[$id], |
|
115 | 1 | $this->frozenInstances[$id] |
|
116 | 1 | ); |
|
117 | } |
||
118 | |||
119 | /** |
||
120 | * @psalm-suppress MixedAssignment |
||
121 | */ |
||
122 | 11 | public function extend(string $id, Closure $instance): Closure |
|
123 | { |
||
124 | 11 | if (!$this->has($id)) { |
|
125 | 4 | $this->extendLater($id, $instance); |
|
126 | |||
127 | 4 | return $instance; |
|
128 | } |
||
129 | |||
130 | 10 | if (isset($this->frozenInstances[$id])) { |
|
131 | 4 | throw ContainerException::frozenInstanceExtend($id); |
|
132 | } |
||
133 | |||
134 | 8 | if (is_object($this->instances[$id]) && isset($this->protectedInstances[$this->instances[$id]])) { |
|
135 | 1 | throw ContainerException::instanceProtected($id); |
|
136 | } |
||
137 | |||
138 | 7 | $factory = $this->instances[$id]; |
|
139 | 7 | $extended = $this->generateExtendedInstance($instance, $factory); |
|
140 | 6 | $this->set($id, $extended); |
|
141 | |||
142 | 6 | return $extended; |
|
143 | } |
||
144 | |||
145 | 2 | public function protect(Closure $instance): Closure |
|
150 | } |
||
151 | |||
152 | 15 | private function getInstance(string $id): mixed |
|
153 | { |
||
154 | 15 | $this->frozenInstances[$id] = true; |
|
155 | |||
156 | 15 | if (!is_object($this->instances[$id]) |
|
157 | 14 | || isset($this->protectedInstances[$this->instances[$id]]) |
|
158 | 15 | || !method_exists($this->instances[$id], '__invoke') |
|
159 | ) { |
||
160 | 8 | return $this->instances[$id]; |
|
161 | } |
||
162 | |||
163 | 12 | if (isset($this->factoryInstances[$this->instances[$id]])) { |
|
164 | 1 | return $this->instances[$id]($this); |
|
165 | } |
||
166 | |||
167 | 11 | $rawService = $this->instances[$id]; |
|
168 | |||
169 | /** @var mixed $resolvedService */ |
||
170 | 11 | $resolvedService = $rawService($this); |
|
171 | |||
172 | 11 | $this->instances[$id] = $resolvedService; |
|
173 | |||
174 | 11 | return $resolvedService; |
|
175 | } |
||
176 | |||
177 | 18 | private function createInstance(string $class): ?object |
|
178 | { |
||
179 | 18 | if (isset($this->bindings[$class])) { |
|
180 | 4 | $binding = $this->bindings[$class]; |
|
181 | 4 | if (is_callable($binding)) { |
|
182 | /** @var mixed $binding */ |
||
183 | 2 | $binding = $binding(); |
|
184 | } |
||
185 | 4 | if (is_object($binding)) { |
|
186 | 2 | return $binding; |
|
187 | } |
||
188 | |||
189 | /** @var class-string $binding */ |
||
190 | 2 | if (class_exists($binding)) { |
|
191 | 2 | return $this->instantiateClass($binding); |
|
192 | } |
||
193 | } |
||
194 | |||
195 | 14 | if (class_exists($class)) { |
|
196 | 10 | return $this->instantiateClass($class); |
|
197 | } |
||
198 | |||
199 | 4 | return null; |
|
200 | } |
||
201 | |||
202 | /** |
||
203 | * @param class-string $class |
||
204 | */ |
||
205 | 12 | private function instantiateClass(string $class): ?object |
|
206 | { |
||
207 | 12 | if (class_exists($class)) { |
|
208 | 12 | if (!isset($this->cachedDependencies[$class])) { |
|
209 | 12 | $this->cachedDependencies[$class] = $this |
|
210 | 12 | ->getDependencyResolver() |
|
211 | 12 | ->resolveDependencies($class); |
|
212 | } |
||
213 | |||
214 | /** @psalm-suppress MixedMethodCall */ |
||
215 | 12 | return new $class(...$this->cachedDependencies[$class]); |
|
216 | } |
||
217 | |||
218 | return null; |
||
219 | } |
||
220 | |||
221 | 4 | private function extendLater(string $id, Closure $instance): void |
|
222 | { |
||
223 | 4 | $this->instancesToExtend[$id][] = $instance; |
|
224 | } |
||
225 | |||
226 | 17 | private function getDependencyResolver(): DependencyResolver |
|
227 | { |
||
228 | 17 | if ($this->dependencyResolver === null) { |
|
229 | 17 | $this->dependencyResolver = new DependencyResolver( |
|
230 | 17 | $this->bindings, |
|
231 | 17 | ); |
|
232 | } |
||
233 | |||
234 | 17 | return $this->dependencyResolver; |
|
1 ignored issue
–
show
|
|||
235 | } |
||
236 | |||
237 | /** |
||
238 | * @psalm-suppress MissingClosureReturnType,MixedAssignment |
||
239 | */ |
||
240 | 7 | private function generateExtendedInstance(Closure $instance, mixed $factory): Closure |
|
241 | { |
||
242 | 7 | if (is_callable($factory)) { |
|
243 | 5 | return static function (self $container) use ($instance, $factory) { |
|
244 | 5 | $result = $factory($container); |
|
245 | |||
246 | 5 | return $instance($result, $container) ?? $result; |
|
247 | 5 | }; |
|
248 | } |
||
249 | |||
250 | 4 | if (is_object($factory) || is_array($factory)) { |
|
251 | 3 | return static fn (self $container) => $instance($factory, $container) ?? $factory; |
|
252 | } |
||
253 | |||
254 | 1 | throw ContainerException::instanceNotExtendable(); |
|
255 | } |
||
256 | |||
257 | 19 | private function extendService(string $id): void |
|
270 | } |
||
271 | } |
||
272 |