1 | <?php |
||||
2 | |||||
3 | namespace HexMakina\LeMarchand; |
||||
4 | |||||
5 | use Psr\Container\ContainerInterface; |
||||
6 | use Psr\Container\ContainerExceptionInterface; |
||||
7 | use Psr\Container\NotFoundExceptionInterface; |
||||
8 | |||||
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 | if ($this->isFirstLevelKey($configuration_string)) { |
||||
90 | return $this->configurations[$configuration_string]; |
||||
91 | } |
||||
92 | |||||
93 | // not a simple configuration string, it has meaning |
||||
94 | $configuration = new Configuration($configuration_string); |
||||
95 | |||||
96 | $ret = null; |
||||
97 | |||||
98 | if ($configuration->isSettings()) { |
||||
99 | $ret = $this->getSettings($configuration); |
||||
100 | } elseif (class_exists($lament)) { |
||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
101 | $ret = $this->getInstance($configuration); |
||||
102 | } elseif ($configuration->isInterface()) { |
||||
103 | $ret = $this->wireInstance($configuration); |
||||
104 | } elseif ($configuration->hasModelOrController()) { |
||||
0 ignored issues
–
show
The method
hasModelOrController() does not exist on HexMakina\LeMarchand\Configuration .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||
105 | $ret = $this->cascadeInstance($configuration); |
||||
106 | } |
||||
107 | |||||
108 | if (is_null($ret)) { |
||||
109 | throw new NotFoundException($configuration); |
||||
110 | } |
||||
111 | |||||
112 | return $ret; |
||||
113 | } |
||||
114 | |||||
115 | private function isFirstLevelKey($configuration_string) |
||||
116 | { |
||||
117 | return isset($this->configurations[$configuration_string]); |
||||
118 | } |
||||
119 | |||||
120 | private function getSettings($setting) |
||||
121 | { |
||||
122 | // vd(__FUNCTION__); |
||||
123 | $ret = $this->configurations; |
||||
124 | |||||
125 | //dot based hierarchy, parse and climb |
||||
126 | foreach (explode('.', $setting) as $k) { |
||||
127 | if (!isset($ret[$k])) { |
||||
128 | throw new NotFoundException($setting); |
||||
129 | } |
||||
130 | $ret = $ret[$k]; |
||||
131 | } |
||||
132 | |||||
133 | return $ret; |
||||
134 | } |
||||
135 | |||||
136 | private function cascadeInstance($configuration){ |
||||
137 | $class_name = $configuration->getModelOrControllerName(); |
||||
138 | $class_name = $this->cascadeNamespace($class_name); |
||||
139 | |||||
140 | if ($configuration->hasClassNameModifier()) { |
||||
141 | $ret = $class_name; |
||||
0 ignored issues
–
show
|
|||||
142 | } |
||||
143 | elseif ($configuration->hasNewInstanceModifier()) { |
||||
144 | $ret = $this->makeInstance($class_name); |
||||
145 | } |
||||
146 | |||||
147 | $ret = $this->getInstance($class_name); |
||||
148 | |||||
149 | return $ret; |
||||
150 | } |
||||
151 | |||||
152 | public function resolved($clue, $solution = null) |
||||
153 | { |
||||
154 | if (!is_null($solution)) { |
||||
155 | $this->resolved_cache[$clue] = $solution; |
||||
156 | } |
||||
157 | // vd($clue, __FUNCTION__); |
||||
158 | return $this->resolved_cache[$clue] ?? null; |
||||
159 | } |
||||
160 | |||||
161 | private function isResolved($clue): bool |
||||
162 | { |
||||
163 | return isset($this->resolved_cache[$clue]); |
||||
164 | } |
||||
165 | |||||
166 | private function cascadeNamespace($class_name) |
||||
167 | { |
||||
168 | if ($this->isResolved($class_name)) { |
||||
169 | return $this->resolved($class_name); |
||||
170 | } |
||||
171 | |||||
172 | // not fully namespaced, lets cascade |
||||
173 | foreach ($this->namespace_cascade as $ns) { |
||||
174 | if (class_exists($fully_namespaced = $ns . $class_name)) { |
||||
175 | $this->resolved($class_name, $fully_namespaced); |
||||
176 | return $fully_namespaced; |
||||
177 | } |
||||
178 | } |
||||
179 | throw new NotFoundException($class_name); |
||||
180 | } |
||||
181 | |||||
182 | private function wireInstance($interface) |
||||
183 | { |
||||
184 | if (!isset($this->interface_wiring[$interface])) { |
||||
185 | throw new NotFoundException($interface); |
||||
186 | } |
||||
187 | |||||
188 | $wire = $this->interface_wiring[$interface]; |
||||
189 | |||||
190 | // interface + constructor params |
||||
191 | if ($this->hasEmbeddedConstructorParameters($wire)) { |
||||
192 | $class = array_shift($wire); |
||||
193 | $args = $wire; |
||||
194 | } else { |
||||
195 | $class = $wire; |
||||
196 | $args = null; |
||||
197 | } |
||||
198 | |||||
199 | if ($this->isResolved($class) && $this->hasPrivateContructor($class)) { |
||||
200 | return $this->resolved($class); |
||||
201 | } |
||||
202 | |||||
203 | return $this->getInstance($class, $args); |
||||
204 | } |
||||
205 | |||||
206 | private function hasPrivateContructor($class_name): bool |
||||
207 | { |
||||
208 | $rc = new \ReflectionClass($class_name); |
||||
209 | return !is_null($constructor = $rc->getConstructor()) && $constructor->isPrivate(); |
||||
210 | } |
||||
211 | |||||
212 | private function hasEmbeddedConstructorParameters($wire) |
||||
213 | { |
||||
214 | return is_array($wire); |
||||
215 | } |
||||
216 | |||||
217 | private function getInstance($class, $construction_args = []) |
||||
218 | { |
||||
219 | if (isset($this->instance_cache[$class])) { |
||||
220 | return $this->instance_cache[$class]; |
||||
221 | } |
||||
222 | |||||
223 | return $this->makeInstance($class, $construction_args); |
||||
224 | } |
||||
225 | |||||
226 | private function makeInstance($class, $construction_args = []) |
||||
227 | { |
||||
228 | $instance = ReflectionFactory::make($class, $construction_args, $this); |
||||
229 | $this->instance_cache[$class] = $instance; |
||||
230 | return $instance; |
||||
231 | } |
||||
232 | } |
||||
233 |
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.