|
1
|
|
|
<?php |
|
2
|
|
|
namespace Lebran; |
|
3
|
|
|
|
|
4
|
|
|
use Closure; |
|
5
|
|
|
use ArrayAccess; |
|
6
|
|
|
use ReflectionClass; |
|
7
|
|
|
use ReflectionMethod; |
|
8
|
|
|
use ReflectionFunction; |
|
9
|
|
|
use Lebran\Container\NotFoundException; |
|
10
|
|
|
use Lebran\Container\InjectableInterface; |
|
11
|
|
|
use Interop\Container\ContainerInterface; |
|
12
|
|
|
use Lebran\Container\ServiceProviderInterface; |
|
13
|
|
|
|
|
14
|
|
|
/** |
|
15
|
|
|
* Lebran\Di it's a component that implements Dependency Injection/Service Location patterns. |
|
16
|
|
|
* Supports string, object and anonymous function definition. Allows using the array syntax. |
|
17
|
|
|
* |
|
18
|
|
|
* Examples |
|
19
|
|
|
* <code> |
|
20
|
|
|
* $di = \Lebran\Di\Container(); |
|
21
|
|
|
* |
|
22
|
|
|
* // Using string definition |
|
23
|
|
|
* $di->set('test', '\Lebran\App\TestController'); |
|
24
|
|
|
* |
|
25
|
|
|
* // Using object definition (singleton) |
|
26
|
|
|
* $di->set('test', new \Lebran\App\TestController('param1')); |
|
27
|
|
|
* |
|
28
|
|
|
* // Using anonymous function definition |
|
29
|
|
|
* $di->set('test', function ($param1, $param2) { |
|
30
|
|
|
* return new \Lebran\App\TestController($param1, $param2) |
|
31
|
|
|
* }); |
|
32
|
|
|
* |
|
33
|
|
|
* </code> |
|
34
|
|
|
* |
|
35
|
|
|
* @package Di |
|
36
|
|
|
* @version 2.0.0 |
|
37
|
|
|
* @author Roman Kritskiy <[email protected]> |
|
38
|
|
|
* @license GNU Licence |
|
39
|
|
|
* @copyright 2014 - 2015 Roman Kritskiy |
|
40
|
|
|
*/ |
|
41
|
|
|
class Container implements ContainerInterface, ArrayAccess |
|
42
|
|
|
{ |
|
43
|
|
|
/** |
|
44
|
|
|
* @var self |
|
45
|
|
|
*/ |
|
46
|
|
|
protected static $instance; |
|
47
|
|
|
|
|
48
|
|
|
/** |
|
49
|
|
|
* Store services. |
|
50
|
|
|
* |
|
51
|
|
|
* @var array |
|
52
|
|
|
*/ |
|
53
|
|
|
protected $services = []; |
|
54
|
|
|
|
|
55
|
|
|
/** |
|
56
|
|
|
* @var array |
|
57
|
|
|
*/ |
|
58
|
|
|
protected $shared = []; |
|
59
|
|
|
|
|
60
|
|
|
/** |
|
61
|
|
|
* Returns last container instance. |
|
62
|
|
|
* |
|
63
|
|
|
* @return Container |
|
64
|
|
|
*/ |
|
65
|
|
|
public static function instance() |
|
66
|
|
|
{ |
|
67
|
|
|
return static::$instance; |
|
68
|
|
|
} |
|
69
|
|
|
|
|
70
|
|
|
/** |
|
71
|
|
|
* Initialisation |
|
72
|
|
|
*/ |
|
73
|
|
|
public function __construct() |
|
74
|
|
|
{ |
|
75
|
|
|
static::$instance = $this; |
|
76
|
|
|
} |
|
77
|
|
|
|
|
78
|
|
|
/** |
|
79
|
|
|
* Registers a service in the services container. |
|
80
|
|
|
* |
|
81
|
|
|
* @param string $id Service id. |
|
82
|
|
|
* @param mixed $definition Service definition. |
|
83
|
|
|
* @param bool $shared Shared or not. |
|
84
|
|
|
* |
|
85
|
|
|
* @return self |
|
86
|
|
|
*/ |
|
87
|
|
|
public function set($id, $definition, $shared = false) |
|
88
|
|
|
{ |
|
89
|
|
|
$id = trim(trim($id, '\\')); |
|
90
|
|
|
if (is_string($definition)) { |
|
91
|
|
|
$definition = trim(trim($definition, '\\')); |
|
92
|
|
|
} |
|
93
|
|
|
$this->services[$id] = compact('definition', 'shared'); |
|
94
|
|
|
return $this; |
|
95
|
|
|
} |
|
96
|
|
|
|
|
97
|
|
|
/** |
|
98
|
|
|
* Registers a shared service in the services container. |
|
99
|
|
|
* |
|
100
|
|
|
* @param string $id Service id. |
|
101
|
|
|
* @param mixed $definition Service definition. |
|
102
|
|
|
* |
|
103
|
|
|
* @return self |
|
104
|
|
|
*/ |
|
105
|
|
|
public function shared($id, $definition) |
|
106
|
|
|
{ |
|
107
|
|
|
return $this->set($id, $definition, true); |
|
108
|
|
|
} |
|
109
|
|
|
|
|
110
|
|
|
/** |
|
111
|
|
|
* Finds an entry of the container by its identifier and returns it. |
|
112
|
|
|
* |
|
113
|
|
|
* @param string $id Identifier of the entry to look for. |
|
114
|
|
|
* @param array $params Parameter for service construct. |
|
115
|
|
|
* |
|
116
|
|
|
* @throws NotFoundException No entry was found for this identifier. |
|
117
|
|
|
* @throws ContainerException Error while retrieving the entry. |
|
118
|
|
|
* |
|
119
|
|
|
* @return mixed Entry. |
|
120
|
|
|
*/ |
|
121
|
|
|
public function get($id, array $params = []) |
|
122
|
|
|
{ |
|
123
|
|
|
if (array_key_exists($id, $this->shared)) { |
|
124
|
|
|
return $this->shared[$id]; |
|
125
|
|
|
} |
|
126
|
|
|
|
|
127
|
|
|
$shared = false; |
|
128
|
|
|
if (array_key_exists($id, $this->services)) { |
|
129
|
|
|
$definition = $this->services[$id]['definition']; |
|
130
|
|
|
if ($this->services[$id]['shared']) { |
|
131
|
|
|
$shared = true; |
|
132
|
|
|
} |
|
133
|
|
|
} else { |
|
134
|
|
|
$definition = $id; |
|
135
|
|
|
} |
|
136
|
|
|
$instance = $this->resolveService($definition, $params); |
|
137
|
|
|
|
|
138
|
|
|
if ($shared) { |
|
139
|
|
|
$this->shared[$id] = $instance; |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
if ($instance instanceof InjectableInterface) { |
|
143
|
|
|
$instance->setDi($this); |
|
144
|
|
|
} |
|
145
|
|
|
|
|
146
|
|
|
return $instance; |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
/** |
|
150
|
|
|
* |
|
151
|
|
|
* |
|
152
|
|
|
* @param callable $callback |
|
153
|
|
|
* @param array $params |
|
154
|
|
|
* |
|
155
|
|
|
* @return mixed |
|
156
|
|
|
* @throws ContainerException Error while retrieving the entry. |
|
157
|
|
|
* @throws NotFoundException No entry was found for this identifier. |
|
158
|
|
|
*/ |
|
159
|
|
|
public function call(callable $callback, array $params = []) |
|
160
|
|
|
{ |
|
161
|
|
|
if (is_string($callback) && strpos($callback, '::') === true) { |
|
162
|
|
|
$callback = explode('::', $callback); |
|
163
|
|
|
} |
|
164
|
|
|
if (is_array($callback)) { |
|
165
|
|
|
$reflection = new ReflectionMethod($callback[0], $callback[1]); |
|
166
|
|
|
} else { |
|
167
|
|
|
$reflection = new ReflectionFunction($callback); |
|
168
|
|
|
} |
|
169
|
|
|
|
|
170
|
|
|
return call_user_func_array( |
|
171
|
|
|
$callback, |
|
172
|
|
|
$this->resolveOptions( |
|
173
|
|
|
$reflection->getParameters(), |
|
174
|
|
|
$params |
|
175
|
|
|
) |
|
176
|
|
|
); |
|
177
|
|
|
} |
|
178
|
|
|
|
|
179
|
|
|
/** |
|
180
|
|
|
* Resolves the service. |
|
181
|
|
|
* |
|
182
|
|
|
* @param mixed $definition The definition of service. |
|
183
|
|
|
* @param array $params Parameters for service construct. |
|
184
|
|
|
* |
|
185
|
|
|
* @return mixed Entry. |
|
186
|
|
|
* @throws ContainerException Error while retrieving the entry. |
|
187
|
|
|
* @throws NotFoundException No entry was found for this identifier. |
|
188
|
|
|
*/ |
|
189
|
|
|
protected function resolveService($definition, array $params) |
|
190
|
|
|
{ |
|
191
|
|
|
switch (gettype($definition)) { |
|
192
|
|
|
case 'string': |
|
193
|
|
|
if (array_key_exists($definition, $this->services)) { |
|
194
|
|
|
return $this->get($definition, $params); |
|
195
|
|
|
} else if (class_exists($definition)) { |
|
196
|
|
|
$parameters = []; |
|
197
|
|
|
$reflection = new ReflectionClass($definition); |
|
198
|
|
|
if (($construct = $reflection->getConstructor())) { |
|
199
|
|
|
$parameters = $this->resolveOptions( |
|
200
|
|
|
$construct->getParameters(), |
|
201
|
|
|
$params |
|
202
|
|
|
); |
|
203
|
|
|
} |
|
204
|
|
|
return $reflection->newInstanceArgs($parameters); |
|
205
|
|
|
} else { |
|
206
|
|
|
throw new NotFoundException(''); |
|
207
|
|
|
} |
|
208
|
|
|
break; |
|
|
|
|
|
|
209
|
|
|
case 'object': |
|
210
|
|
|
if ($definition instanceof Closure) { |
|
211
|
|
|
return call_user_func_array($definition->bindTo($this), $params); |
|
212
|
|
|
} else { |
|
213
|
|
|
return clone $definition; |
|
214
|
|
|
} |
|
215
|
|
|
break; |
|
|
|
|
|
|
216
|
|
|
default: |
|
217
|
|
|
throw new ContainerException(''); |
|
218
|
|
|
} |
|
219
|
|
|
} |
|
220
|
|
|
|
|
221
|
|
|
/** |
|
222
|
|
|
* Resolve parameters of service construct. |
|
223
|
|
|
* |
|
224
|
|
|
* @param array $dependencies |
|
225
|
|
|
* @param array $parameters |
|
226
|
|
|
* |
|
227
|
|
|
* @return array Resolved parameters. |
|
228
|
|
|
* @throws ContainerException Error while retrieving the entry. |
|
229
|
|
|
* @throws NotFoundException No entry was found for this identifier. |
|
230
|
|
|
*/ |
|
231
|
|
|
protected function resolveOptions(array $dependencies, array $parameters) |
|
232
|
|
|
{ |
|
233
|
|
|
foreach ($parameters as $key => $value) { |
|
234
|
|
|
if (is_numeric($key)) { |
|
235
|
|
|
unset($parameters[$key]); |
|
236
|
|
|
$parameters[$dependencies[$key]->name] = $value; |
|
237
|
|
|
} |
|
238
|
|
|
} |
|
239
|
|
|
|
|
240
|
|
|
$resolved = []; |
|
241
|
|
|
foreach ($dependencies as $parameter) { |
|
242
|
|
|
/** @var \ReflectionParameter $parameter */ |
|
243
|
|
|
if (array_key_exists($parameter->name, $parameters)) { |
|
244
|
|
|
$resolved[] = $parameters[$parameter->name]; |
|
245
|
|
|
} else if (($type = $parameter->getClass())) { |
|
246
|
|
|
try{ |
|
247
|
|
|
$params = []; |
|
248
|
|
|
if (array_key_exists($type->name, $parameters)) { |
|
249
|
|
|
$params = $parameters[$type->name]; |
|
250
|
|
|
unset($parameters[$type->name]); |
|
251
|
|
|
} |
|
252
|
|
|
$resolved[] = $this->get($type->name, $params); |
|
253
|
|
|
} catch(ContainerException $e){ |
|
254
|
|
|
if ($parameter->isOptional()) { |
|
255
|
|
|
$resolved[] = $parameter->getDefaultValue(); |
|
256
|
|
|
} else { |
|
257
|
|
|
throw $e; |
|
258
|
|
|
} |
|
259
|
|
|
} |
|
260
|
|
|
} else { |
|
261
|
|
|
if ($parameter->isOptional()) { |
|
262
|
|
|
$resolved[] = $parameter->getDefaultValue(); |
|
263
|
|
|
} else { |
|
264
|
|
|
throw new ContainerException(''); |
|
265
|
|
|
} |
|
266
|
|
|
} |
|
267
|
|
|
} |
|
268
|
|
|
|
|
269
|
|
|
return $resolved; |
|
270
|
|
|
} |
|
271
|
|
|
|
|
272
|
|
|
/** |
|
273
|
|
|
* Removes a service in the services container. |
|
274
|
|
|
* |
|
275
|
|
|
* @param string $id Service id. |
|
276
|
|
|
* |
|
277
|
|
|
* @return void |
|
278
|
|
|
*/ |
|
279
|
|
|
public function remove($id) |
|
280
|
|
|
{ |
|
281
|
|
|
unset($this->services[$id]); |
|
282
|
|
|
} |
|
283
|
|
|
|
|
284
|
|
|
/** |
|
285
|
|
|
* Returns true if the container can return an entry for the given identifier. |
|
286
|
|
|
* Returns false otherwise. |
|
287
|
|
|
* |
|
288
|
|
|
* @param string $id Identifier of the entry to look for. |
|
289
|
|
|
* |
|
290
|
|
|
* @return bool |
|
291
|
|
|
*/ |
|
292
|
|
|
public function has($id) |
|
293
|
|
|
{ |
|
294
|
|
|
return array_key_exists($id, $this->services); |
|
295
|
|
|
} |
|
296
|
|
|
|
|
297
|
|
|
/** |
|
298
|
|
|
* Merge two containers into one. |
|
299
|
|
|
* |
|
300
|
|
|
* @param ServiceProviderInterface $provider Another container. |
|
301
|
|
|
* |
|
302
|
|
|
* @return self |
|
303
|
|
|
*/ |
|
304
|
|
|
public function register(ServiceProviderInterface $provider) |
|
305
|
|
|
{ |
|
306
|
|
|
$provider->register($this); |
|
307
|
|
|
return $this; |
|
308
|
|
|
} |
|
309
|
|
|
|
|
310
|
|
|
/** |
|
311
|
|
|
* Check whether the service is shared or not. |
|
312
|
|
|
* |
|
313
|
|
|
* @param string $id Service id. |
|
314
|
|
|
* |
|
315
|
|
|
* @return bool True if shared, false - not. |
|
316
|
|
|
*/ |
|
317
|
|
|
public function isShared($id) |
|
318
|
|
|
{ |
|
319
|
|
|
return $this->has($id)?$this->services[$id]['shared']:false; |
|
320
|
|
|
} |
|
321
|
|
|
|
|
322
|
|
|
/** |
|
323
|
|
|
* Sets if the service is shared or not. |
|
324
|
|
|
* |
|
325
|
|
|
* @param string $id Service id. |
|
326
|
|
|
* @param bool $shared Shared or not. |
|
327
|
|
|
* |
|
328
|
|
|
* @return self |
|
329
|
|
|
*/ |
|
330
|
|
|
public function setShared($id, $shared = true) |
|
331
|
|
|
{ |
|
332
|
|
|
if ($this->has($id)) { |
|
333
|
|
|
$this->services[$id]['shared'] = $shared; |
|
334
|
|
|
} |
|
335
|
|
|
return $this; |
|
336
|
|
|
} |
|
337
|
|
|
|
|
338
|
|
|
/** |
|
339
|
|
|
* Allows to register a shared service using the array syntax. |
|
340
|
|
|
* |
|
341
|
|
|
* @param string $id Service id. |
|
342
|
|
|
* @param mixed $definition Service definition. |
|
343
|
|
|
* |
|
344
|
|
|
* @return self |
|
345
|
|
|
*/ |
|
346
|
|
|
public function offsetSet($id, $definition) |
|
347
|
|
|
{ |
|
348
|
|
|
return $this->set($id, $definition); |
|
349
|
|
|
} |
|
350
|
|
|
|
|
351
|
|
|
/** |
|
352
|
|
|
* Finds an entry of the container by its identifier and returns it. |
|
353
|
|
|
* |
|
354
|
|
|
* @param string $id Identifier of the entry to look for. |
|
355
|
|
|
* |
|
356
|
|
|
* @throws NotFoundException No entry was found for this identifier. |
|
357
|
|
|
* @throws ContainerException Error while retrieving the entry. |
|
358
|
|
|
* |
|
359
|
|
|
* @return mixed Entry. |
|
360
|
|
|
*/ |
|
361
|
|
|
public function offsetGet($id) |
|
362
|
|
|
{ |
|
363
|
|
|
return $this->get($id); |
|
364
|
|
|
} |
|
365
|
|
|
|
|
366
|
|
|
/** |
|
367
|
|
|
* Removes a service from the services container using the array syntax. |
|
368
|
|
|
* |
|
369
|
|
|
* @param string $id Service id. |
|
370
|
|
|
* |
|
371
|
|
|
* @return void |
|
372
|
|
|
*/ |
|
373
|
|
|
public function offsetUnset($id) |
|
374
|
|
|
{ |
|
375
|
|
|
$this->remove($id); |
|
376
|
|
|
} |
|
377
|
|
|
|
|
378
|
|
|
/** |
|
379
|
|
|
* Returns true if the container can return an entry for the given identifier. |
|
380
|
|
|
* Returns false otherwise. |
|
381
|
|
|
* |
|
382
|
|
|
* @param string $id Identifier of the entry to look for. |
|
383
|
|
|
* |
|
384
|
|
|
* @return bool |
|
385
|
|
|
*/ |
|
386
|
|
|
public function offsetExists($id) |
|
387
|
|
|
{ |
|
388
|
|
|
return $this->has($id); |
|
389
|
|
|
} |
|
390
|
|
|
|
|
391
|
|
|
/** |
|
392
|
|
|
* Allows to register a shared service using the array syntax. |
|
393
|
|
|
* |
|
394
|
|
|
* @param string $id Service id. |
|
395
|
|
|
* @param mixed $definition Service definition. |
|
396
|
|
|
* |
|
397
|
|
|
* @return self |
|
398
|
|
|
*/ |
|
399
|
|
|
public function __set($id, $definition) |
|
400
|
|
|
{ |
|
401
|
|
|
$this->set($id, $definition, true); |
|
402
|
|
|
} |
|
403
|
|
|
|
|
404
|
|
|
/** |
|
405
|
|
|
* Finds an entry of the container by its identifier and returns it. |
|
406
|
|
|
* |
|
407
|
|
|
* @param string $id Identifier of the entry to look for. |
|
408
|
|
|
* |
|
409
|
|
|
* @throws NotFoundException No entry was found for this identifier. |
|
410
|
|
|
* @throws ContainerException Error while retrieving the entry. |
|
411
|
|
|
* |
|
412
|
|
|
* @return mixed Entry. |
|
413
|
|
|
*/ |
|
414
|
|
|
public function __get($id) |
|
415
|
|
|
{ |
|
416
|
|
|
return $this->get($id); |
|
417
|
|
|
} |
|
418
|
|
|
|
|
419
|
|
|
/** |
|
420
|
|
|
* Removes a service from the services container using the array syntax. |
|
421
|
|
|
* |
|
422
|
|
|
* @param string $id Service id. |
|
423
|
|
|
* |
|
424
|
|
|
* @return void |
|
425
|
|
|
*/ |
|
426
|
|
|
public function __unset($id) |
|
427
|
|
|
{ |
|
428
|
|
|
$this->remove($id); |
|
429
|
|
|
} |
|
430
|
|
|
|
|
431
|
|
|
/** |
|
432
|
|
|
* Returns true if the container can return an entry for the given identifier. |
|
433
|
|
|
* Returns false otherwise. |
|
434
|
|
|
* |
|
435
|
|
|
* @param string $id Identifier of the entry to look for. |
|
436
|
|
|
* |
|
437
|
|
|
* @return bool |
|
438
|
|
|
*/ |
|
439
|
|
|
public function __isset($id) |
|
440
|
|
|
{ |
|
441
|
|
|
return $this->has($id); |
|
442
|
|
|
} |
|
443
|
|
|
} |
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,dieorexitstatements that have been added for debug purposes.In the above example, the last
return falsewill never be executed, because a return statement has already been met in every possible execution path.