1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Fwk |
4
|
|
|
* |
5
|
|
|
* Copyright (c) 2011-2012, Julien Ballestracci <[email protected]>. |
6
|
|
|
* All rights reserved. |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
* |
11
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
12
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
13
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
14
|
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
15
|
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
16
|
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
17
|
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
18
|
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
19
|
|
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
20
|
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
21
|
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
22
|
|
|
* POSSIBILITY OF SUCH DAMAGE. |
23
|
|
|
* |
24
|
|
|
* PHP Version 5.3 |
25
|
|
|
* |
26
|
|
|
* @category DependencyInjection |
27
|
|
|
* @package Fwk\Di |
28
|
|
|
* @author Julien Ballestracci <[email protected]> |
29
|
|
|
* @copyright 2011-2014 Julien Ballestracci <[email protected]> |
30
|
|
|
* @license http://www.opensource.org/licenses/bsd-license.php BSD License |
31
|
|
|
* @link http://www.nitronet.org/fwk |
32
|
|
|
*/ |
33
|
|
|
namespace Fwk\Di; |
34
|
|
|
|
35
|
|
|
use Fwk\Di\Definitions\ArrayDefinition; |
36
|
|
|
use Fwk\Di\Definitions\CallableDefinition; |
37
|
|
|
use Fwk\Di\Definitions\ScalarDefinition; |
38
|
|
|
use Fwk\Di\Events\AfterServiceLoadedEvent; |
39
|
|
|
use Fwk\Di\Events\AfterServiceRegisteredEvent; |
40
|
|
|
use Fwk\Di\Events\BeforeServiceLoadedEvent; |
41
|
|
|
use Fwk\Di\Events\BeforeServiceRegisteredEvent; |
42
|
|
|
use Fwk\Di\Exceptions\SearchException; |
43
|
|
|
use Fwk\Events\Dispatcher; |
44
|
|
|
use \ArrayAccess; |
45
|
|
|
use Interop\Container\ContainerInterface; |
46
|
|
|
use \SplObjectStorage; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Container |
50
|
|
|
* |
51
|
|
|
* THE Dependency Injection Container. |
52
|
|
|
* |
53
|
|
|
* @category Container |
54
|
|
|
* @package Fwk\Di |
55
|
|
|
* @author Julien Ballestracci <[email protected]> |
56
|
|
|
* @license http://www.opensource.org/licenses/bsd-license.php BSD License |
57
|
|
|
* @link http://www.nitronet.org/fwk |
58
|
|
|
*/ |
59
|
|
|
class Container extends Dispatcher implements ArrayAccess, ContainerInterface |
60
|
|
|
{ |
61
|
|
|
/** |
62
|
|
|
* The objects store |
63
|
|
|
* @var SplObjectStorage |
64
|
|
|
*/ |
65
|
|
|
protected $store = array(); |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* Shared instances store |
69
|
|
|
* @var SplObjectStorage |
70
|
|
|
*/ |
71
|
|
|
private $_sharedInstances; |
|
|
|
|
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Container Properties |
75
|
|
|
* @var array |
76
|
|
|
*/ |
77
|
|
|
protected $properties = array(); |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Properties Keys (cached) |
81
|
|
|
* @var array |
82
|
|
|
*/ |
83
|
|
|
protected $propertiesMap = array(); |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* Delegates Containers |
87
|
|
|
* @var SplObjectStorage |
88
|
|
|
*/ |
89
|
|
|
protected $delegates; |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* Constructor |
93
|
|
|
* |
94
|
|
|
* @return void |
|
|
|
|
95
|
|
|
*/ |
96
|
|
|
public function __construct() |
97
|
|
|
{ |
98
|
|
|
$this->store = new SplObjectStorage(); |
99
|
|
|
$this->delegates = new SplObjectStorage(); |
100
|
|
|
$this->_sharedInstances = new SplObjectStorage(); |
101
|
|
|
$this->set('self', $this); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
/** |
105
|
|
|
* Registers a definition |
106
|
|
|
* |
107
|
|
|
* @param string $name Identifier |
108
|
|
|
* @param DefinitionInterface|mixed $definition Definition, callable or value |
109
|
|
|
* |
110
|
|
|
* @return Container |
111
|
|
|
*/ |
112
|
|
|
public function set($name, $definition) |
113
|
|
|
{ |
114
|
|
|
if (!$definition instanceof DefinitionInterface) { |
115
|
|
|
if (is_callable($definition)) { |
116
|
|
|
$definition = CallableDefinition::factory($definition); |
117
|
|
|
} elseif (is_array($definition)) { |
118
|
|
|
$definition = ArrayDefinition::factory($definition); |
119
|
|
|
} else { |
120
|
|
|
$wasObj = is_object($definition); |
121
|
|
|
$definition = ScalarDefinition::factory($definition); |
122
|
|
|
if ($wasObj) { |
123
|
|
|
$definition->setShared(true); |
124
|
|
|
$this->_sharedInstances->attach($definition, $name); |
125
|
|
|
} |
126
|
|
|
} |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
$event = new BeforeServiceRegisteredEvent($this, $name, $definition); |
130
|
|
|
$this->notify($event); |
131
|
|
|
|
132
|
|
|
if ($event->isStopped()) { |
133
|
|
|
return $this; |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
$this->store->attach($definition, $name); |
137
|
|
|
$this->notify(new AfterServiceRegisteredEvent($this, $name, $definition)); |
138
|
|
|
|
139
|
|
|
return $this; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* Load and returns a definition |
144
|
|
|
* |
145
|
|
|
* @param string $name Identifier |
146
|
|
|
* |
147
|
|
|
* @throws Exceptions\DefinitionNotFoundException if $name isn't a valid identifier |
148
|
|
|
* @return mixed |
149
|
|
|
*/ |
150
|
|
|
public function get($name) |
151
|
|
|
{ |
152
|
|
|
if ($name instanceof Reference) { |
153
|
|
|
$name = $name->getName(); |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
if (!$this->has($name)) { |
157
|
|
|
return $this->getFromDelegate($name); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
foreach ($this->store as $def) { |
161
|
|
|
if ($this->store->getInfo() === $name) { |
162
|
|
|
$definition = $def; |
163
|
|
|
break; |
164
|
|
|
} |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** @var DefinitionInterface $definition */ |
168
|
|
|
if ($definition->isShared()) { |
|
|
|
|
169
|
|
|
foreach ($this->_sharedInstances as $inst) { |
170
|
|
|
if ($this->_sharedInstances->getInfo() === $name) { |
171
|
|
|
return $inst; |
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
$event = new BeforeServiceLoadedEvent($this, $name, $definition); |
177
|
|
|
$this->notify($event); |
178
|
|
|
|
179
|
|
|
// the event has been stopped |
180
|
|
|
if ($event->isStopped()) { |
181
|
|
|
$return = $event->getReturnValue(); |
182
|
|
|
|
183
|
|
|
if ($definition->isShared()) { |
184
|
|
|
$this->_sharedInstances->attach($return, $name); |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
return $return; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
$return = $definition->invoke($this, $name); |
191
|
|
|
if ($definition->isShared()) { |
192
|
|
|
$this->_sharedInstances->attach($return, $name); |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
$afterEvent = new AfterServiceLoadedEvent($this, $name, $definition, $return); |
196
|
|
|
$this->notify($afterEvent); |
197
|
|
|
|
198
|
|
|
return $return; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Loads properties from an INI file as definitions. |
203
|
|
|
* Theses properties can then be referenced like @propName in other |
204
|
|
|
* definitions. |
205
|
|
|
* |
206
|
|
|
* @param string $iniFile Path/to/file.ini |
207
|
|
|
* @param null|string $category The INI category to be parsed |
208
|
|
|
* |
209
|
|
|
* @return Container |
210
|
|
|
* @throws Exception |
211
|
|
|
*/ |
212
|
|
|
public function iniProperties($iniFile, $category = null) |
213
|
|
|
{ |
214
|
|
|
if (!is_file($iniFile) || !is_readable($iniFile)) { |
215
|
|
|
throw new Exception('INI file not found/readable: '. $iniFile); |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
$props = parse_ini_file($iniFile, ($category !== null)); |
219
|
|
|
if ($category !== null) { |
220
|
|
|
$props = (isset($props[$category]) ? $props[$category] : false); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
if (!is_array($props)) { |
224
|
|
|
throw new Exception("No properties found in: $iniFile [$category]"); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
foreach ($props as $key => $prop) { |
228
|
|
|
$this->properties[$key] = str_replace(':packageDir', dirname($iniFile), $prop); |
229
|
|
|
$this->propertiesMap[$key] = ":". $key; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
return $this; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* Returns a property (or $default if not defined) |
237
|
|
|
* |
238
|
|
|
* @param string $propName The property name |
239
|
|
|
* @param mixed $default Default value if the property is not defined |
240
|
|
|
* |
241
|
|
|
* @return mixed |
|
|
|
|
242
|
|
|
*/ |
243
|
|
|
public function getProperty($propName, $default = null) |
244
|
|
|
{ |
245
|
|
|
return (array_key_exists($propName, $this->properties) ? |
246
|
|
|
$this->propertizeString($this->properties[$propName]) : |
247
|
|
|
(is_string($default) ? |
248
|
|
|
$this->propertizeString($default) : |
249
|
|
|
$default |
250
|
|
|
) |
251
|
|
|
); |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* Defines a property. |
256
|
|
|
* |
257
|
|
|
* If the $value is null, the property will be unset. |
258
|
|
|
* |
259
|
|
|
* It recommended to store only strings as property values. Register a |
260
|
|
|
* new Di definition for any other type. |
261
|
|
|
* |
262
|
|
|
* @param string $propName Property name |
263
|
|
|
* @param null|string $value The prop value |
264
|
|
|
* |
265
|
|
|
* @return Container |
266
|
|
|
*/ |
267
|
|
|
public function setProperty($propName, $value = null) |
268
|
|
|
{ |
269
|
|
|
if (array_key_exists($propName, $this->properties) && $value === null) { |
270
|
|
|
unset($this->properties[$propName]); |
271
|
|
|
unset($this->propertiesMap[$propName]); |
272
|
|
|
return $this; |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
$this->properties[(string)$propName] = (string)$value; |
276
|
|
|
$this->propertiesMap[(string)$propName] = ":". (string)$propName; |
277
|
|
|
|
278
|
|
|
return $this; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
|
282
|
|
|
/** |
283
|
|
|
* Transform properties references to their respective value |
284
|
|
|
* |
285
|
|
|
* @param string $str String to be transformed |
286
|
|
|
* |
287
|
|
|
* @return string |
288
|
|
|
*/ |
289
|
|
|
public function propertizeString($str) |
290
|
|
|
{ |
291
|
|
|
return str_replace( |
292
|
|
|
array_values($this->propertiesMap), |
293
|
|
|
array_values($this->properties), |
294
|
|
|
$str |
295
|
|
|
); |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
/** |
299
|
|
|
* Unregisters a definition |
300
|
|
|
* |
301
|
|
|
* @param string $name Identifier |
302
|
|
|
* |
303
|
|
|
* @throws Exceptions\DefinitionNotFoundException if $name isn't a valid identifier |
304
|
|
|
* @return boolean true on success |
305
|
|
|
*/ |
306
|
|
|
public function unregister($name) |
307
|
|
|
{ |
308
|
|
|
if (!$this->has($name)) { |
309
|
|
|
throw new Exceptions\DefinitionNotFoundException($name); |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
$this->store->detach($this->getDefinition($name)); |
313
|
|
|
foreach ($this->_sharedInstances as $inst => $defName) { |
314
|
|
|
if ($defName === $name) { |
315
|
|
|
$this->_sharedInstances->detach((object)$inst); |
316
|
|
|
break; |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
return true; |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
/** |
324
|
|
|
* Tells if a definition exists at $offset |
325
|
|
|
* |
326
|
|
|
* @param string $name Identifier |
327
|
|
|
* |
328
|
|
|
* @return boolean |
329
|
|
|
*/ |
330
|
|
|
public function has($name) |
331
|
|
|
{ |
332
|
|
|
foreach ($this->store as $def) { |
333
|
|
|
if ($this->store->getInfo() === $name) { |
334
|
|
|
return true; |
335
|
|
|
} |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
return false; |
339
|
|
|
} |
340
|
|
|
|
341
|
|
|
/** |
342
|
|
|
* Returns a Definition |
343
|
|
|
* |
344
|
|
|
* @param string $name Identifier |
345
|
|
|
* |
346
|
|
|
* @return DefinitionInterface |
347
|
|
|
* @throws Exceptions\DefinitionNotFoundException if $name isn't a valid identifier |
348
|
|
|
*/ |
349
|
|
|
public function getDefinition($name) |
350
|
|
|
{ |
351
|
|
|
foreach ($this->store as $def) { |
352
|
|
|
if ($this->store->getInfo() === $name) { |
353
|
|
|
return $def; |
354
|
|
|
} |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
throw new Exceptions\DefinitionNotFoundException($name); |
358
|
|
|
} |
359
|
|
|
|
360
|
|
|
/** |
361
|
|
|
* Tells if a definition is registered at $offset |
362
|
|
|
* |
363
|
|
|
* @param string $offset Identifier |
364
|
|
|
* |
365
|
|
|
* @return boolean |
366
|
|
|
*/ |
367
|
|
|
public function offsetExists($offset) |
368
|
|
|
{ |
369
|
|
|
return $this->has($offset); |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
/** |
373
|
|
|
* Loads and returns a definition |
374
|
|
|
* |
375
|
|
|
* @param string $offset Identifier |
376
|
|
|
* |
377
|
|
|
* @return mixed |
378
|
|
|
*/ |
379
|
|
|
public function offsetGet($offset) |
380
|
|
|
{ |
381
|
|
|
return $this->get($offset); |
382
|
|
|
} |
383
|
|
|
|
384
|
|
|
/** |
385
|
|
|
* Registers a definition |
386
|
|
|
* |
387
|
|
|
* @param string $offset Identifier |
388
|
|
|
* @param mixed $value Definition |
389
|
|
|
* |
390
|
|
|
* @return Container |
391
|
|
|
*/ |
392
|
|
|
public function offsetSet($offset, $value) |
393
|
|
|
{ |
394
|
|
|
return $this->set($offset, $value); |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
/** |
398
|
|
|
* Unregisters a Definition |
399
|
|
|
* |
400
|
|
|
* @param string $offset Identifier |
401
|
|
|
* |
402
|
|
|
* @return boolean |
403
|
|
|
*/ |
404
|
|
|
public function offsetUnset($offset) |
405
|
|
|
{ |
406
|
|
|
return $this->unregister($offset); |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
/** |
410
|
|
|
* Adds a delegate/backup Container. |
411
|
|
|
* |
412
|
|
|
* @param ContainerInterface $container |
413
|
|
|
* |
414
|
|
|
* @return ContainerInterface |
|
|
|
|
415
|
|
|
*/ |
416
|
|
|
public function delegate(ContainerInterface $container) |
417
|
|
|
{ |
418
|
|
|
if ($this->delegates->contains($container)) { |
419
|
|
|
return $this; |
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
$this->delegates->attach($container); |
423
|
|
|
|
424
|
|
|
return $this; |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
/** |
428
|
|
|
* Tells if a service is in a delegated Container |
429
|
|
|
* |
430
|
|
|
* @param string $name |
431
|
|
|
* |
432
|
|
|
* @return boolean |
433
|
|
|
*/ |
434
|
|
|
public function hasInDelegate($name) |
435
|
|
|
{ |
436
|
|
|
foreach ($this->delegates as $container) { |
437
|
|
|
/** @var ContainerInterface $container */ |
438
|
|
|
if ($container->has($name)) { |
439
|
|
|
return true; |
440
|
|
|
} |
441
|
|
|
} |
442
|
|
|
|
443
|
|
|
return false; |
444
|
|
|
} |
445
|
|
|
|
446
|
|
|
/** |
447
|
|
|
* Loads a definition from the first delegated Container having in (FIFO) |
448
|
|
|
* |
449
|
|
|
* @param string $name Service identifier |
450
|
|
|
* |
451
|
|
|
* @throws Exceptions\DefinitionNotFoundException when the service is not found |
452
|
|
|
* @return mixed |
453
|
|
|
*/ |
454
|
|
|
public function getFromDelegate($name) |
455
|
|
|
{ |
456
|
|
|
foreach ($this->delegates as $container) { |
457
|
|
|
/** @var ContainerInterface $container */ |
458
|
|
|
if ($container->has($name)) { |
459
|
|
|
return $container->get($name); |
460
|
|
|
} |
461
|
|
|
} |
462
|
|
|
|
463
|
|
|
throw new Exceptions\DefinitionNotFoundException($name); |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
/** |
467
|
|
|
* Search definitions |
468
|
|
|
* |
469
|
|
|
* @param array $query Search query |
470
|
|
|
* |
471
|
|
|
* @return array<DefinitionInterface> |
472
|
|
|
*/ |
473
|
|
|
public function search(array $query) |
474
|
|
|
{ |
475
|
|
|
$results = array(); |
476
|
|
|
foreach ($this->store as $def) { |
477
|
|
|
$name = $this->store->getInfo(); |
478
|
|
|
/** @var DefinitionInterface $def */ |
479
|
|
|
if ($def->match($query, $this)) { |
480
|
|
|
$results[$name] = $def; |
481
|
|
|
} |
482
|
|
|
} |
483
|
|
|
|
484
|
|
|
return $results; |
485
|
|
|
} |
486
|
|
|
} |
This check examines a number of code elements and verifies that they conform to the given naming conventions.
You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.