Total Complexity | 52 |
Total Lines | 262 |
Duplicated Lines | 0 % |
Changes | 43 | ||
Bugs | 8 | Features | 4 |
Complex classes like LeMarchand 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 LeMarchand, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
9 | class LeMarchand implements ContainerInterface |
||
10 | { |
||
11 | private static $instance = null; |
||
12 | // stores all the settings |
||
13 | private $configurations = []; |
||
14 | |||
15 | // stores the namespace cascade |
||
16 | private $namespace_cascade = []; |
||
17 | |||
18 | // stores the interface to class wiring |
||
19 | private $interface_wiring = []; |
||
20 | |||
21 | // store the resolved names for performance |
||
22 | private $resolved_cache = []; |
||
23 | |||
24 | // stores the automatically created instances, by class name |
||
25 | private $instance_cache = []; |
||
26 | |||
27 | |||
28 | public static function box($settings = null): ContainerInterface |
||
29 | { |
||
30 | if (is_null(self::$instance)) { |
||
31 | if (is_array($settings)) { |
||
32 | return (self::$instance = new LeMarchand($settings)); |
||
33 | } |
||
34 | throw new ContainerException('UNABLE_TO_OPEN_BOX'); |
||
35 | } |
||
36 | |||
37 | return self::$instance; |
||
38 | } |
||
39 | |||
40 | |||
41 | private function __construct($settings) |
||
42 | { |
||
43 | if (isset($settings[__CLASS__])) { |
||
44 | $this->namespace_cascade = $settings[__CLASS__]['cascade'] ?? []; |
||
45 | $this->interface_wiring = $settings[__CLASS__]['wiring'] ?? []; |
||
46 | unset($settings[__CLASS__]); |
||
47 | } |
||
48 | $this->configurations['settings'] = $settings; |
||
49 | } |
||
50 | |||
51 | public function __debugInfo(): array |
||
52 | { |
||
53 | $dbg = get_object_vars($this); |
||
54 | |||
55 | foreach ($dbg['instance_cache'] as $class => $instance) { |
||
56 | $dbg['instance_cache'][$class] = true; |
||
57 | } |
||
58 | |||
59 | foreach ($dbg['interface_wiring'] as $interface => $wire) { |
||
60 | if (is_array($wire)) { |
||
61 | $wire = array_shift($wire) . ' --array #' . count($wire); |
||
62 | } |
||
63 | $dbg['interface_wiring'][$interface] = $wire; |
||
64 | } |
||
65 | |||
66 | return $dbg; |
||
67 | } |
||
68 | |||
69 | public function has($configuration) |
||
70 | { |
||
71 | try { |
||
72 | $this->get($configuration); |
||
73 | return true; |
||
74 | } catch (NotFoundExceptionInterface $e) { |
||
75 | return false; |
||
76 | } catch (ContainerExceptionInterface $e) { |
||
77 | return false; |
||
78 | } |
||
79 | return false; |
||
1 ignored issue
–
show
|
|||
80 | } |
||
81 | |||
82 | |||
83 | public function get($configuration_string) |
||
84 | { |
||
85 | if (!is_string($configuration_string)) { |
||
86 | throw new ContainerException($configuration_string); |
||
87 | } |
||
88 | |||
89 | $ret = null; |
||
90 | |||
91 | |||
92 | if ($this->isFirstLevelKey($configuration_string)) { |
||
93 | return $this->configurations[$configuration_string]; |
||
94 | } |
||
95 | |||
96 | // not a simple configuration string, it has meaning |
||
97 | $configuration = new Configuration($configuration_string); |
||
98 | if ($configuration->isSettings()) { |
||
99 | $ret = $this->getSettings($configuration); |
||
100 | } elseif (class_exists($lament)) { |
||
101 | $ret = $this->getInstance($configuration); |
||
102 | } elseif ($configuration->isInterface()) { |
||
103 | $ret = $this->wireInstance($configuration); |
||
104 | } elseif ($configuration->hasModelOrController()) { |
||
105 | // 5. is it cascadable ? |
||
106 | |||
107 | $class_name = $configuration->getModelOrControllerName(); |
||
108 | $class_name = $this->cascadeNamespace($class_name); |
||
109 | |||
110 | if ($configuration->hasClassNameModifier()) { |
||
111 | $ret = $class_name; |
||
112 | } elseif ($configuration->hasNewInstanceModifier()) { |
||
113 | $ret = $this->makeInstance($class_name); |
||
114 | } |
||
115 | $ret = $this->getInstance($class_name); |
||
116 | } |
||
117 | |||
118 | if (is_null($ret)) { |
||
119 | throw new NotFoundException($configuration); |
||
120 | } |
||
121 | |||
122 | return $ret; |
||
123 | } |
||
124 | |||
125 | public function isFirstLevelKey($configuration_string) |
||
126 | { |
||
127 | return isset($this->configurations[$configuration_string]); |
||
128 | } |
||
129 | |||
130 | private function getSettings($setting) |
||
131 | { |
||
132 | // vd(__FUNCTION__); |
||
133 | $ret = $this->configurations; |
||
134 | |||
135 | //dot based hierarchy, parse and climb |
||
136 | foreach (explode('.', $setting) as $k) { |
||
137 | if (!isset($ret[$k])) { |
||
138 | throw new NotFoundException($setting); |
||
139 | } |
||
140 | $ret = $ret[$k]; |
||
141 | } |
||
142 | |||
143 | return $ret; |
||
144 | } |
||
145 | |||
146 | private function resolved($clue, $solution = null) |
||
147 | { |
||
148 | if (!is_null($solution)) { |
||
149 | $this->resolved_cache[$clue] = $solution; |
||
150 | } |
||
151 | // vd($clue, __FUNCTION__); |
||
152 | return $this->resolved_cache[$clue] ?? null; |
||
153 | } |
||
154 | |||
155 | private function isResolved($clue): bool |
||
156 | { |
||
157 | return isset($this->resolved_cache[$clue]); |
||
158 | } |
||
159 | |||
160 | private function cascadeNamespace($class_name) |
||
161 | { |
||
162 | if ($this->isResolved($class_name)) { |
||
163 | return $this->resolved($class_name); |
||
164 | } |
||
165 | |||
166 | // not fully namespaced, lets cascade |
||
167 | foreach ($this->namespace_cascade as $ns) { |
||
168 | if (class_exists($fully_namespaced = $ns . $class_name)) { |
||
169 | $this->resolved($class_name, $fully_namespaced); |
||
170 | return $fully_namespaced; |
||
171 | } |
||
172 | } |
||
173 | throw new NotFoundException($class_name); |
||
174 | } |
||
175 | |||
176 | private function wireInstance($interface) |
||
177 | { |
||
178 | if (!isset($this->interface_wiring[$interface])) { |
||
179 | throw new NotFoundException($interface); |
||
180 | } |
||
181 | |||
182 | $wire = $this->interface_wiring[$interface]; |
||
183 | |||
184 | // interface + constructor params |
||
185 | if ($this->hasEmbeddedConstructorParameters($wire)) { |
||
186 | $class = array_shift($wire); |
||
187 | $args = $wire; |
||
188 | } else { |
||
189 | $class = $wire; |
||
190 | $args = null; |
||
191 | } |
||
192 | |||
193 | if ($this->isResolved($class) && $this->hasPrivateContructor($class)) { |
||
194 | return $this->resolved($class); |
||
195 | } |
||
196 | |||
197 | return $this->getInstance($class, $args); |
||
198 | } |
||
199 | |||
200 | private function hasPrivateContructor($class_name): bool |
||
201 | { |
||
202 | $rc = new \ReflectionClass($class_name); |
||
203 | return !is_null($constructor = $rc->getConstructor()) && $constructor->isPrivate(); |
||
204 | } |
||
205 | |||
206 | private function hasEmbeddedConstructorParameters($wire) |
||
207 | { |
||
208 | return is_array($wire); |
||
209 | } |
||
210 | |||
211 | private function getInstance($class, $construction_args = []) |
||
212 | { |
||
213 | if (isset($this->instance_cache[$class])) { |
||
214 | return $this->instance_cache[$class]; |
||
215 | } |
||
216 | |||
217 | return $this->makeInstance($class, $construction_args); |
||
218 | } |
||
219 | |||
220 | private function makeInstance($class, $construction_args = []) |
||
249 | } |
||
250 | } |
||
251 | |||
252 | private function getConstructorParameters(\ReflectionMethod $constructor, $construction_args = []) |
||
253 | { |
||
271 | } |
||
272 | } |
||
273 |
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.