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; |
|
|
|
|
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)) { |
|
|
|
|
101
|
|
|
$ret = $this->getInstance($configuration); |
102
|
|
|
} elseif ($configuration->isInterface()) { |
103
|
|
|
$ret = $this->wireInstance($configuration); |
104
|
|
|
} elseif ($configuration->hasModelOrController()) { |
|
|
|
|
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; |
|
|
|
|
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.