1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace SilverStripe\Core\Injector; |
4
|
|
|
|
5
|
|
|
use ArrayObject; |
6
|
|
|
use InvalidArgumentException; |
7
|
|
|
use Psr\Container\ContainerInterface; |
8
|
|
|
use Psr\Container\NotFoundExceptionInterface; |
9
|
|
|
use ReflectionMethod; |
10
|
|
|
use ReflectionObject; |
11
|
|
|
use ReflectionProperty; |
12
|
|
|
use SilverStripe\Core\ClassInfo; |
13
|
|
|
use SilverStripe\Core\Config\Config; |
14
|
|
|
use SilverStripe\Core\Environment; |
15
|
|
|
use SilverStripe\Dev\Deprecation; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* A simple injection manager that manages creating objects and injecting |
19
|
|
|
* dependencies between them. It borrows quite a lot from ideas taken from |
20
|
|
|
* Spring's configuration, but is adapted to the stateless PHP way of doing |
21
|
|
|
* things. |
22
|
|
|
* |
23
|
|
|
* In its simplest form, the dependency injector can be used as a mechanism to |
24
|
|
|
* instantiate objects. Simply call |
25
|
|
|
* |
26
|
|
|
* Injector::inst()->get('ClassName') |
27
|
|
|
* |
28
|
|
|
* and a new instance of ClassName will be created and returned to you. |
29
|
|
|
* |
30
|
|
|
* Classes can have specific configuration defined for them to |
31
|
|
|
* indicate dependencies that should be injected. This takes the form of |
32
|
|
|
* a static variable $dependencies defined in the class (or configuration), |
33
|
|
|
* which indicates the name of a property that should be set. |
34
|
|
|
* |
35
|
|
|
* eg |
36
|
|
|
* |
37
|
|
|
* <code> |
38
|
|
|
* class MyController extends Controller { |
39
|
|
|
* |
40
|
|
|
* public $permissions; |
41
|
|
|
* public $defaultText; |
42
|
|
|
* |
43
|
|
|
* static $dependencies = array( |
44
|
|
|
* 'defaultText' => 'Override in configuration', |
45
|
|
|
* 'permissions' => '%$PermissionService', |
46
|
|
|
* ); |
47
|
|
|
* } |
48
|
|
|
* </code> |
49
|
|
|
* |
50
|
|
|
* will result in an object of type MyController having the defaultText property |
51
|
|
|
* set to 'Override in configuration', and an object identified |
52
|
|
|
* as PermissionService set into the property called 'permissions'. The %$ |
53
|
|
|
* syntax tells the injector to look the provided name up as an item to be created |
54
|
|
|
* by the Injector itself. |
55
|
|
|
* |
56
|
|
|
* A key concept of the injector is whether to manage the object as |
57
|
|
|
* |
58
|
|
|
* * A pseudo-singleton, in that only one item will be created for a particular |
59
|
|
|
* identifier (but the same class could be used for multiple identifiers) |
60
|
|
|
* * A prototype, where the same configuration is used, but a new object is |
61
|
|
|
* created each time |
62
|
|
|
* * unmanaged, in which case a new object is created and injected, but no |
63
|
|
|
* information about its state is managed. |
64
|
|
|
* |
65
|
|
|
* Additional configuration of items managed by the injector can be done by |
66
|
|
|
* providing configuration for the types, either by manually loading in an |
67
|
|
|
* array describing the configuration, or by specifying the configuration |
68
|
|
|
* for a type via SilverStripe's configuration mechanism. |
69
|
|
|
* |
70
|
|
|
* Specify a configuration array of the format |
71
|
|
|
* |
72
|
|
|
* <code> |
73
|
|
|
* array( |
74
|
|
|
* array( |
75
|
|
|
* 'id' => 'BeanId', // the name to be used if diff from the filename |
76
|
|
|
* 'priority' => 1, // priority. If another bean is defined with the same ID, |
77
|
|
|
* // but has a lower priority, it is NOT overridden |
78
|
|
|
* 'class' => 'ClassName', // the name of the PHP class |
79
|
|
|
* 'src' => '/path/to/file' // the location of the class |
80
|
|
|
* 'type' => 'singleton|prototype' // if you want prototype object generation, set it as the |
81
|
|
|
* // type |
82
|
|
|
* // By default, singleton is assumed |
83
|
|
|
* |
84
|
|
|
* 'factory' => 'FactoryService' // A factory service to use to create instances. |
85
|
|
|
* 'construct' => array( // properties to set at construction |
86
|
|
|
* 'scalar', |
87
|
|
|
* '%$BeanId', |
88
|
|
|
* ) |
89
|
|
|
* 'properties' => array( |
90
|
|
|
* 'name' => 'value' // scalar value |
91
|
|
|
* 'name' => '%$BeanId', // a reference to another bean |
92
|
|
|
* 'name' => array( |
93
|
|
|
* 'scalar', |
94
|
|
|
* '%$BeanId' |
95
|
|
|
* ) |
96
|
|
|
* ) |
97
|
|
|
* ) |
98
|
|
|
* // alternatively |
99
|
|
|
* 'MyBean' => array( |
100
|
|
|
* 'class' => 'ClassName', |
101
|
|
|
* ) |
102
|
|
|
* // or simply |
103
|
|
|
* 'OtherBean' => 'SomeClass', |
104
|
|
|
* ) |
105
|
|
|
* </code> |
106
|
|
|
* |
107
|
|
|
* In addition to specifying the bindings directly in the configuration, |
108
|
|
|
* you can simply create a publicly accessible property on the target |
109
|
|
|
* class which will automatically be injected if the autoScanProperties |
110
|
|
|
* option is set to true. This means a class defined as |
111
|
|
|
* |
112
|
|
|
* <code> |
113
|
|
|
* class MyController extends Controller { |
114
|
|
|
* |
115
|
|
|
* private $permissionService; |
116
|
|
|
* |
117
|
|
|
* public setPermissionService($p) { |
118
|
|
|
* $this->permissionService = $p; |
119
|
|
|
* } |
120
|
|
|
* } |
121
|
|
|
* </code> |
122
|
|
|
* |
123
|
|
|
* will have setPermissionService called if |
124
|
|
|
* |
125
|
|
|
* * Injector::inst()->setAutoScanProperties(true) is called and |
126
|
|
|
* * A service named 'PermissionService' has been configured |
127
|
|
|
* |
128
|
|
|
* @author [email protected] |
129
|
|
|
* @license BSD License http://silverstripe.org/bsd-license/ |
130
|
|
|
*/ |
131
|
|
|
class Injector implements ContainerInterface |
132
|
|
|
{ |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Local store of all services |
136
|
|
|
* |
137
|
|
|
* @var array |
138
|
|
|
*/ |
139
|
|
|
private $serviceCache; |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Cache of items that need to be mapped for each service that gets injected |
143
|
|
|
* |
144
|
|
|
* @var array |
145
|
|
|
*/ |
146
|
|
|
private $injectMap; |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* A store of all the service configurations that have been defined. |
150
|
|
|
* |
151
|
|
|
* @var array |
152
|
|
|
*/ |
153
|
|
|
private $specs; |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* A map of all the properties that should be automagically set on all |
157
|
|
|
* objects instantiated by the injector |
158
|
|
|
*/ |
159
|
|
|
private $autoProperties; |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* A singleton if you want to use it that way |
163
|
|
|
* |
164
|
|
|
* @var Injector |
165
|
|
|
*/ |
166
|
|
|
private static $instance; |
167
|
|
|
|
168
|
|
|
/** |
169
|
|
|
* Indicates whether or not to automatically scan properties in injected objects to auto inject |
170
|
|
|
* stuff, similar to the way grails does things. |
171
|
|
|
* |
172
|
|
|
* @var boolean |
173
|
|
|
*/ |
174
|
|
|
private $autoScanProperties = false; |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* The default factory used to create new instances. |
178
|
|
|
* |
179
|
|
|
* The {@link InjectionCreator} is used by default, which simply directly |
180
|
|
|
* creates objects. This can be changed to use a different default creation |
181
|
|
|
* method if desired. |
182
|
|
|
* |
183
|
|
|
* Each individual component can also specify a custom factory to use by |
184
|
|
|
* using the `factory` parameter. |
185
|
|
|
* |
186
|
|
|
* @var Factory |
187
|
|
|
*/ |
188
|
|
|
protected $objectCreator; |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* Locator for determining Config properties for services |
192
|
|
|
* |
193
|
|
|
* @var ServiceConfigurationLocator |
194
|
|
|
*/ |
195
|
|
|
protected $configLocator; |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* Specify a service type singleton |
199
|
|
|
*/ |
200
|
|
|
const SINGLETON = 'singleton'; |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Specif ya service type prototype |
204
|
|
|
*/ |
205
|
|
|
const PROTOTYPE = 'prototype'; |
206
|
|
|
|
207
|
|
|
/** |
208
|
|
|
* Create a new injector. |
209
|
|
|
* |
210
|
|
|
* @param array $config |
211
|
|
|
* Service configuration |
212
|
|
|
*/ |
213
|
|
|
public function __construct($config = null) |
214
|
|
|
{ |
215
|
|
|
$this->injectMap = array(); |
216
|
|
|
$this->serviceCache = array( |
217
|
|
|
'Injector' => $this, |
218
|
|
|
); |
219
|
|
|
$this->specs = [ |
220
|
|
|
'Injector' => ['class' => static::class] |
221
|
|
|
]; |
222
|
|
|
$this->autoProperties = array(); |
223
|
|
|
$creatorClass = isset($config['creator']) |
224
|
|
|
? $config['creator'] |
225
|
|
|
: InjectionCreator::class; |
226
|
|
|
$locatorClass = isset($config['locator']) |
227
|
|
|
? $config['locator'] |
228
|
|
|
: SilverStripeServiceConfigurationLocator::class; |
229
|
|
|
|
230
|
|
|
$this->objectCreator = new $creatorClass; |
231
|
|
|
$this->configLocator = new $locatorClass; |
232
|
|
|
|
233
|
|
|
if ($config) { |
234
|
|
|
$this->load($config); |
235
|
|
|
} |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* The injector instance this one was copied from when Injector::nest() was called. |
240
|
|
|
* |
241
|
|
|
* @var Injector |
242
|
|
|
*/ |
243
|
|
|
protected $nestedFrom = null; |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* @return Injector |
247
|
|
|
*/ |
248
|
|
|
public static function inst() |
249
|
|
|
{ |
250
|
|
|
return InjectorLoader::inst()->getManifest(); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* Make the newly active {@link Injector} be a copy of the current active |
255
|
|
|
* {@link Injector} instance. |
256
|
|
|
* |
257
|
|
|
* You can then make changes to the injector with methods such as |
258
|
|
|
* {@link Injector::inst()->registerService()} which will be discarded |
259
|
|
|
* upon a subsequent call to {@link Injector::unnest()} |
260
|
|
|
* |
261
|
|
|
* @return Injector Reference to new active Injector instance |
262
|
|
|
*/ |
263
|
|
|
public static function nest() |
264
|
|
|
{ |
265
|
|
|
// Clone current injector and nest |
266
|
|
|
$new = clone self::inst(); |
267
|
|
|
InjectorLoader::inst()->pushManifest($new); |
268
|
|
|
return $new; |
269
|
|
|
} |
270
|
|
|
|
271
|
|
|
/** |
272
|
|
|
* Change the active Injector back to the Injector instance the current active |
273
|
|
|
* Injector object was copied from. |
274
|
|
|
* |
275
|
|
|
* @return Injector Reference to restored active Injector instance |
276
|
|
|
*/ |
277
|
|
|
public static function unnest() |
278
|
|
|
{ |
279
|
|
|
// Unnest unless we would be left at 0 manifests |
280
|
|
|
$loader = InjectorLoader::inst(); |
281
|
|
|
if ($loader->countManifests() <= 1) { |
282
|
|
|
user_error( |
283
|
|
|
"Unable to unnest root Injector, please make sure you don't have mis-matched nest/unnest", |
284
|
|
|
E_USER_WARNING |
285
|
|
|
); |
286
|
|
|
} else { |
287
|
|
|
$loader->popManifest(); |
288
|
|
|
} |
289
|
|
|
return static::inst(); |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Indicate whether we auto scan injected objects for properties to set. |
294
|
|
|
* |
295
|
|
|
* @param boolean $val |
296
|
|
|
*/ |
297
|
|
|
public function setAutoScanProperties($val) |
298
|
|
|
{ |
299
|
|
|
$this->autoScanProperties = $val; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* Sets the default factory to use for creating new objects. |
304
|
|
|
* |
305
|
|
|
* @param \SilverStripe\Core\Injector\Factory $obj |
306
|
|
|
*/ |
307
|
|
|
public function setObjectCreator(Factory $obj) |
308
|
|
|
{ |
309
|
|
|
$this->objectCreator = $obj; |
310
|
|
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* @return Factory |
314
|
|
|
*/ |
315
|
|
|
public function getObjectCreator() |
316
|
|
|
{ |
317
|
|
|
return $this->objectCreator; |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
/** |
321
|
|
|
* Set the configuration locator |
322
|
|
|
* @param ServiceConfigurationLocator $configLocator |
323
|
|
|
*/ |
324
|
|
|
public function setConfigLocator($configLocator) |
325
|
|
|
{ |
326
|
|
|
$this->configLocator = $configLocator; |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* Retrieve the configuration locator |
331
|
|
|
* @return ServiceConfigurationLocator |
332
|
|
|
*/ |
333
|
|
|
public function getConfigLocator() |
334
|
|
|
{ |
335
|
|
|
return $this->configLocator; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* Add in a specific mapping that should be catered for on a type. |
340
|
|
|
* This allows configuration of what should occur when an object |
341
|
|
|
* of a particular type is injected, and what items should be injected |
342
|
|
|
* for those properties / methods. |
343
|
|
|
* |
344
|
|
|
* @param string $class The class to set a mapping for |
345
|
|
|
* @param string $property The property to set the mapping for |
346
|
|
|
* @param string $toInject The registered type that will be injected |
347
|
|
|
* @param string $injectVia Whether to inject by setting a property or calling a setter |
348
|
|
|
*/ |
349
|
|
|
public function setInjectMapping($class, $property, $toInject, $injectVia = 'property') |
350
|
|
|
{ |
351
|
|
|
$mapping = isset($this->injectMap[$class]) ? $this->injectMap[$class] : array(); |
352
|
|
|
|
353
|
|
|
$mapping[$property] = array('name' => $toInject, 'type' => $injectVia); |
354
|
|
|
|
355
|
|
|
$this->injectMap[$class] = $mapping; |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
/** |
359
|
|
|
* Add an object that should be automatically set on managed objects |
360
|
|
|
* |
361
|
|
|
* This allows you to specify, for example, that EVERY managed object |
362
|
|
|
* will be automatically inject with a log object by the following |
363
|
|
|
* |
364
|
|
|
* $injector->addAutoProperty('log', new Logger()); |
365
|
|
|
* |
366
|
|
|
* @param string $property |
367
|
|
|
* the name of the property |
368
|
|
|
* @param object $object |
369
|
|
|
* the object to be set |
370
|
|
|
* @return $this |
371
|
|
|
*/ |
372
|
|
|
public function addAutoProperty($property, $object) |
373
|
|
|
{ |
374
|
|
|
$this->autoProperties[$property] = $object; |
375
|
|
|
return $this; |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
/** |
379
|
|
|
* Load services using the passed in configuration for those services |
380
|
|
|
* |
381
|
|
|
* @param array $config |
382
|
|
|
* @return $this |
383
|
|
|
*/ |
384
|
|
|
public function load($config = array()) |
385
|
|
|
{ |
386
|
|
|
foreach ($config as $specId => $spec) { |
387
|
|
|
if (is_string($spec)) { |
388
|
|
|
$spec = array('class' => $spec); |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
$file = isset($spec['src']) ? $spec['src'] : null; |
392
|
|
|
|
393
|
|
|
// class is whatever's explicitly set, |
394
|
|
|
$class = isset($spec['class']) ? $spec['class'] : null; |
395
|
|
|
|
396
|
|
|
// or the specid if nothing else available. |
397
|
|
|
if (!$class && is_string($specId)) { |
398
|
|
|
$class = $specId; |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
// make sure the class is set... |
402
|
|
|
if (empty($class)) { |
403
|
|
|
throw new InvalidArgumentException('Missing spec class'); |
404
|
|
|
} |
405
|
|
|
$spec['class'] = $class; |
406
|
|
|
|
407
|
|
|
$id = is_string($specId) |
408
|
|
|
? $specId |
409
|
|
|
: (isset($spec['id']) ? $spec['id'] : $class); |
410
|
|
|
|
411
|
|
|
$priority = isset($spec['priority']) ? $spec['priority'] : 1; |
412
|
|
|
|
413
|
|
|
// see if we already have this defined. If so, check priority weighting |
414
|
|
|
if (isset($this->specs[$id]) && isset($this->specs[$id]['priority'])) { |
415
|
|
|
if ($this->specs[$id]['priority'] > $priority) { |
416
|
|
|
return $this; |
417
|
|
|
} |
418
|
|
|
} |
419
|
|
|
|
420
|
|
|
// okay, actually include it now we know we're going to use it |
421
|
|
|
if (file_exists($file)) { |
422
|
|
|
require_once $file; |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
// make sure to set the id for later when instantiating |
426
|
|
|
// to ensure we get cached |
427
|
|
|
$spec['id'] = $id; |
428
|
|
|
|
429
|
|
|
// We've removed this check because new functionality means that the 'class' field doesn't need to refer |
430
|
|
|
// specifically to a class anymore - it could be a compound statement, ala SilverStripe's old Object::create |
431
|
|
|
// functionality |
432
|
|
|
// |
433
|
|
|
// if (!class_exists($class)) { |
434
|
|
|
// throw new Exception("Failed to load '$class' from $file"); |
435
|
|
|
// } |
436
|
|
|
|
437
|
|
|
// store the specs for now - we lazy load on demand later on. |
438
|
|
|
$this->specs[$id] = $spec; |
439
|
|
|
|
440
|
|
|
// EXCEPT when there's already an existing instance at this id. |
441
|
|
|
// if so, we need to instantiate and replace immediately |
442
|
|
|
if (isset($this->serviceCache[$id])) { |
443
|
|
|
$this->updateSpecConstructor($spec); |
444
|
|
|
$this->instantiate($spec, $id); |
445
|
|
|
} |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
return $this; |
449
|
|
|
} |
450
|
|
|
|
451
|
|
|
/** |
452
|
|
|
* Update the configuration of an already defined service |
453
|
|
|
* |
454
|
|
|
* Use this if you don't want to register a complete new config, just append |
455
|
|
|
* to an existing configuration. Helpful to avoid overwriting someone else's changes |
456
|
|
|
* |
457
|
|
|
* updateSpec('RequestProcessor', 'filters', '%$MyFilter') |
458
|
|
|
* |
459
|
|
|
* @param string $id |
460
|
|
|
* The name of the service to update the definition for |
461
|
|
|
* @param string $property |
462
|
|
|
* The name of the property to update. |
463
|
|
|
* @param mixed $value |
464
|
|
|
* The value to set |
465
|
|
|
* @param boolean $append |
466
|
|
|
* Whether to append (the default) when the property is an array |
467
|
|
|
*/ |
468
|
|
|
public function updateSpec($id, $property, $value, $append = true) |
469
|
|
|
{ |
470
|
|
|
if (isset($this->specs[$id]['properties'][$property])) { |
471
|
|
|
// by ref so we're updating the actual value |
472
|
|
|
$current = &$this->specs[$id]['properties'][$property]; |
473
|
|
|
if (is_array($current) && $append) { |
474
|
|
|
$current[] = $value; |
475
|
|
|
} else { |
476
|
|
|
$this->specs[$id]['properties'][$property] = $value; |
477
|
|
|
} |
478
|
|
|
|
479
|
|
|
// and reload the object; existing bindings don't get |
480
|
|
|
// updated though! (for now...) |
|
|
|
|
481
|
|
|
if (isset($this->serviceCache[$id])) { |
482
|
|
|
$this->instantiate(array('class'=>$id), $id); |
483
|
|
|
} |
484
|
|
|
} |
485
|
|
|
} |
486
|
|
|
|
487
|
|
|
/** |
488
|
|
|
* Update a class specification to convert constructor configuration information if needed |
489
|
|
|
* |
490
|
|
|
* We do this as a separate process to avoid unneeded calls to convertServiceProperty |
491
|
|
|
* |
492
|
|
|
* @param array $spec |
493
|
|
|
* The class specification to update |
494
|
|
|
*/ |
495
|
|
|
protected function updateSpecConstructor(&$spec) |
496
|
|
|
{ |
497
|
|
|
if (isset($spec['constructor'])) { |
498
|
|
|
$spec['constructor'] = $this->convertServiceProperty($spec['constructor']); |
499
|
|
|
} |
500
|
|
|
} |
501
|
|
|
|
502
|
|
|
/** |
503
|
|
|
* Recursively convert a value into its proper representation with service references |
504
|
|
|
* resolved to actual objects |
505
|
|
|
* |
506
|
|
|
* @param string $value |
507
|
|
|
* @return array|mixed|string |
508
|
|
|
*/ |
509
|
|
|
public function convertServiceProperty($value) |
510
|
|
|
{ |
511
|
|
|
if (is_array($value)) { |
|
|
|
|
512
|
|
|
$newVal = array(); |
513
|
|
|
foreach ($value as $k => $v) { |
514
|
|
|
$newVal[$k] = $this->convertServiceProperty($v); |
515
|
|
|
} |
516
|
|
|
return $newVal; |
517
|
|
|
} |
518
|
|
|
|
519
|
|
|
// Evaluate service references |
520
|
|
|
if (is_string($value) && strpos($value, '%$') === 0) { |
521
|
|
|
$id = substr($value, 2); |
522
|
|
|
return $this->get($id); |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
// Evaluate constants surrounded by back ticks |
526
|
|
|
if (preg_match('/^`(?<name>[^`]+)`$/', $value, $matches)) { |
527
|
|
|
$envValue = Environment::getEnv($matches['name']); |
528
|
|
|
if ($envValue !== false) { |
529
|
|
|
$value = $envValue; |
530
|
|
|
} elseif (defined($matches['name'])) { |
531
|
|
|
$value = constant($matches['name']); |
532
|
|
|
} else { |
533
|
|
|
$value = null; |
534
|
|
|
} |
535
|
|
|
} |
536
|
|
|
|
537
|
|
|
return $value; |
538
|
|
|
} |
539
|
|
|
|
540
|
|
|
/** |
541
|
|
|
* Instantiate a managed object |
542
|
|
|
* |
543
|
|
|
* Given a specification of the form |
544
|
|
|
* |
545
|
|
|
* array( |
546
|
|
|
* 'class' => 'ClassName', |
547
|
|
|
* 'properties' => array('property' => 'scalar', 'other' => '%$BeanRef') |
548
|
|
|
* 'id' => 'ServiceId', |
549
|
|
|
* 'type' => 'singleton|prototype' |
550
|
|
|
* ) |
551
|
|
|
* |
552
|
|
|
* will create a new object, store it in the service registry, and |
553
|
|
|
* set any relevant properties |
554
|
|
|
* |
555
|
|
|
* Optionally, you can pass a class name directly for creation |
556
|
|
|
* |
557
|
|
|
* To access this from the outside, you should call ->get('Name') to ensure |
558
|
|
|
* the appropriate checks are made on the specific type. |
559
|
|
|
* |
560
|
|
|
* |
561
|
|
|
* @param array $spec |
562
|
|
|
* The specification of the class to instantiate |
563
|
|
|
* @param string $id |
564
|
|
|
* The name of the object being created. If not supplied, then the id will be inferred from the |
565
|
|
|
* object being created |
566
|
|
|
* @param string $type |
567
|
|
|
* Whether to create as a singleton or prototype object. Allows code to be explicit as to how it |
568
|
|
|
* wants the object to be returned |
569
|
|
|
* @return object |
570
|
|
|
*/ |
571
|
|
|
protected function instantiate($spec, $id = null, $type = null) |
572
|
|
|
{ |
573
|
|
|
if (is_string($spec)) { |
|
|
|
|
574
|
|
|
$spec = array('class' => $spec); |
575
|
|
|
} |
576
|
|
|
$class = $spec['class']; |
577
|
|
|
|
578
|
|
|
// create the object, using any constructor bindings |
579
|
|
|
$constructorParams = array(); |
580
|
|
|
if (isset($spec['constructor']) && is_array($spec['constructor'])) { |
581
|
|
|
$constructorParams = $spec['constructor']; |
582
|
|
|
} |
583
|
|
|
|
584
|
|
|
$factory = isset($spec['factory']) ? $this->get($spec['factory']) : $this->getObjectCreator(); |
585
|
|
|
$object = $factory->create($class, $constructorParams); |
586
|
|
|
|
587
|
|
|
// Handle empty factory responses |
588
|
|
|
if (!$object) { |
|
|
|
|
589
|
|
|
return null; |
590
|
|
|
} |
591
|
|
|
|
592
|
|
|
// figure out if we have a specific id set or not. In some cases, we might be instantiating objects |
593
|
|
|
// that we don't manage directly; we don't want to store these in the service cache below |
594
|
|
|
if (!$id) { |
595
|
|
|
$id = isset($spec['id']) ? $spec['id'] : null; |
596
|
|
|
} |
597
|
|
|
|
598
|
|
|
// now set the service in place if needbe. This is NOT done for prototype beans, as they're |
599
|
|
|
// created anew each time |
600
|
|
|
if (!$type) { |
601
|
|
|
$type = isset($spec['type']) ? $spec['type'] : null; |
602
|
|
|
} |
603
|
|
|
|
604
|
|
|
if ($id && (!$type || $type !== self::PROTOTYPE)) { |
605
|
|
|
// this ABSOLUTELY must be set before the object is injected. |
606
|
|
|
// This prevents circular reference errors down the line |
607
|
|
|
$this->serviceCache[$id] = $object; |
608
|
|
|
} |
609
|
|
|
|
610
|
|
|
// now inject safely |
611
|
|
|
$this->inject($object, $id); |
612
|
|
|
|
613
|
|
|
return $object; |
614
|
|
|
} |
615
|
|
|
|
616
|
|
|
/** |
617
|
|
|
* Inject $object with available objects from the service cache |
618
|
|
|
* |
619
|
|
|
* @todo Track all the existing objects that have had a service bound |
620
|
|
|
* into them, so we can update that binding at a later point if needbe (ie |
621
|
|
|
* if the managed service changes) |
622
|
|
|
* |
623
|
|
|
* @param object $object |
624
|
|
|
* The object to inject |
625
|
|
|
* @param string $asType |
626
|
|
|
* The ID this item was loaded as. This is so that the property configuration |
627
|
|
|
* for a type is referenced correctly in case $object is no longer the same |
628
|
|
|
* type as the loaded config specification had it as. |
629
|
|
|
*/ |
630
|
|
|
public function inject($object, $asType = null) |
631
|
|
|
{ |
632
|
|
|
$objtype = $asType ? $asType : get_class($object); |
633
|
|
|
$mapping = isset($this->injectMap[$objtype]) ? $this->injectMap[$objtype] : null; |
634
|
|
|
|
635
|
|
|
$spec = empty($this->specs[$objtype]) ? array() : $this->specs[$objtype]; |
636
|
|
|
|
637
|
|
|
// first off, set any properties defined in the service specification for this |
638
|
|
|
// object type |
639
|
|
|
if (!empty($spec['properties']) && is_array($spec['properties'])) { |
640
|
|
|
foreach ($this->specs[$objtype]['properties'] as $key => $value) { |
641
|
|
|
$val = $this->convertServiceProperty($value); |
642
|
|
|
$this->setObjectProperty($object, $key, $val); |
643
|
|
|
} |
644
|
|
|
} |
645
|
|
|
|
646
|
|
|
// Populate named methods |
647
|
|
|
if (!empty($spec['calls']) && is_array($spec['calls'])) { |
648
|
|
|
foreach ($spec['calls'] as $method) { |
649
|
|
|
// Ignore any blank entries from the array; these may be left in due to config system limitations |
650
|
|
|
if (!$method) { |
651
|
|
|
continue; |
652
|
|
|
} |
653
|
|
|
|
654
|
|
|
// Format validation |
655
|
|
|
if (!is_array($method) || !isset($method[0]) || isset($method[2])) { |
656
|
|
|
throw new InvalidArgumentException( |
657
|
|
|
"'calls' entries in service definition should be 1 or 2 element arrays." |
658
|
|
|
); |
659
|
|
|
} |
660
|
|
|
if (!is_string($method[0])) { |
661
|
|
|
throw new InvalidArgumentException("1st element of a 'calls' entry should be a string"); |
662
|
|
|
} |
663
|
|
|
if (isset($method[1]) && !is_array($method[1])) { |
664
|
|
|
throw new InvalidArgumentException("2nd element of a 'calls' entry should an arguments array"); |
665
|
|
|
} |
666
|
|
|
|
667
|
|
|
// Check that the method exists and is callable |
668
|
|
|
$objectMethod = array($object, $method[0]); |
669
|
|
|
if (!is_callable($objectMethod)) { |
670
|
|
|
throw new InvalidArgumentException("'$method[0]' in 'calls' entry is not a public method"); |
671
|
|
|
} |
672
|
|
|
|
673
|
|
|
// Call it |
674
|
|
|
call_user_func_array( |
675
|
|
|
$objectMethod, |
676
|
|
|
$this->convertServiceProperty( |
677
|
|
|
isset($method[1]) ? $method[1] : array() |
|
|
|
|
678
|
|
|
) |
679
|
|
|
); |
680
|
|
|
} |
681
|
|
|
} |
682
|
|
|
|
683
|
|
|
// now, use any cached information about what properties this object type has |
684
|
|
|
// and set based on name resolution |
685
|
|
|
if ($mapping === null) { |
686
|
|
|
// we use an object to prevent array copies if/when passed around |
687
|
|
|
$mapping = new ArrayObject(); |
688
|
|
|
|
689
|
|
|
if ($this->autoScanProperties) { |
690
|
|
|
// This performs public variable based injection |
691
|
|
|
$robj = new ReflectionObject($object); |
692
|
|
|
$properties = $robj->getProperties(); |
693
|
|
|
|
694
|
|
|
foreach ($properties as $propertyObject) { |
695
|
|
|
/* @var $propertyObject ReflectionProperty */ |
696
|
|
|
if ($propertyObject->isPublic() && !$propertyObject->getValue($object)) { |
697
|
|
|
$origName = $propertyObject->getName(); |
698
|
|
|
$name = ucfirst($origName); |
699
|
|
|
if ($this->has($name)) { |
700
|
|
|
// Pull the name out of the registry |
701
|
|
|
$value = $this->get($name); |
702
|
|
|
$propertyObject->setValue($object, $value); |
703
|
|
|
$mapping[$origName] = array('name' => $name, 'type' => 'property'); |
704
|
|
|
} |
705
|
|
|
} |
706
|
|
|
} |
707
|
|
|
|
708
|
|
|
// and this performs setter based injection |
709
|
|
|
$methods = $robj->getMethods(ReflectionMethod::IS_PUBLIC); |
710
|
|
|
|
711
|
|
|
foreach ($methods as $methodObj) { |
712
|
|
|
/* @var $methodObj ReflectionMethod */ |
713
|
|
|
$methName = $methodObj->getName(); |
714
|
|
|
if (strpos($methName, 'set') === 0) { |
715
|
|
|
$pname = substr($methName, 3); |
716
|
|
|
if ($this->has($pname)) { |
717
|
|
|
// Pull the name out of the registry |
718
|
|
|
$value = $this->get($pname); |
719
|
|
|
$methodObj->invoke($object, $value); |
720
|
|
|
$mapping[$methName] = array('name' => $pname, 'type' => 'method'); |
721
|
|
|
} |
722
|
|
|
} |
723
|
|
|
} |
724
|
|
|
} |
725
|
|
|
|
726
|
|
|
$injections = Config::inst()->get(get_class($object), 'dependencies'); |
727
|
|
|
// If the type defines some injections, set them here |
728
|
|
|
if ($injections && count($injections)) { |
729
|
|
|
foreach ($injections as $property => $value) { |
730
|
|
|
// we're checking empty in case it already has a property at this name |
731
|
|
|
// this doesn't catch privately set things, but they will only be set by a setter method, |
732
|
|
|
// which should be responsible for preventing further setting if it doesn't want it. |
733
|
|
|
if (empty($object->$property)) { |
734
|
|
|
$convertedValue = $this->convertServiceProperty($value); |
735
|
|
|
$this->setObjectProperty($object, $property, $convertedValue); |
736
|
|
|
$mapping[$property] = array('service' => $value, 'type' => 'service'); |
737
|
|
|
} |
738
|
|
|
} |
739
|
|
|
} |
740
|
|
|
|
741
|
|
|
// we store the information about what needs to be injected for objects of this |
742
|
|
|
// type here |
743
|
|
|
$this->injectMap[$objtype] = $mapping; |
744
|
|
|
} else { |
745
|
|
|
foreach ($mapping as $prop => $propSpec) { |
746
|
|
|
switch ($propSpec['type']) { |
747
|
|
|
case 'property': |
748
|
|
|
$value = $this->get($propSpec['name']); |
749
|
|
|
$object->$prop = $value; |
750
|
|
|
break; |
751
|
|
|
|
752
|
|
|
|
753
|
|
|
case 'method': |
754
|
|
|
$method = $prop; |
755
|
|
|
$value = $this->get($propSpec['name']); |
756
|
|
|
$object->$method($value); |
757
|
|
|
break; |
758
|
|
|
|
759
|
|
|
case 'service': |
760
|
|
|
if (empty($object->$prop)) { |
761
|
|
|
$value = $this->convertServiceProperty($propSpec['service']); |
762
|
|
|
$this->setObjectProperty($object, $prop, $value); |
763
|
|
|
} |
764
|
|
|
break; |
765
|
|
|
|
766
|
|
|
default: |
767
|
|
|
throw new \LogicException("Bad mapping type: " . $propSpec['type']); |
768
|
|
|
} |
769
|
|
|
} |
770
|
|
|
} |
771
|
|
|
|
772
|
|
|
foreach ($this->autoProperties as $property => $value) { |
773
|
|
|
if (!isset($object->$property)) { |
774
|
|
|
$value = $this->convertServiceProperty($value); |
775
|
|
|
$this->setObjectProperty($object, $property, $value); |
776
|
|
|
} |
777
|
|
|
} |
778
|
|
|
|
779
|
|
|
// Call the 'injected' method if it exists |
780
|
|
|
if (method_exists($object, 'injected')) { |
781
|
|
|
$object->injected(); |
782
|
|
|
} |
783
|
|
|
} |
784
|
|
|
|
785
|
|
|
/** |
786
|
|
|
* Helper to set a property's value |
787
|
|
|
* |
788
|
|
|
* @param object $object |
789
|
|
|
* Set an object's property to a specific value |
790
|
|
|
* @param string $name |
791
|
|
|
* The name of the property to set |
792
|
|
|
* @param mixed $value |
793
|
|
|
* The value to set |
794
|
|
|
*/ |
795
|
|
|
protected function setObjectProperty($object, $name, $value) |
796
|
|
|
{ |
797
|
|
|
if (ClassInfo::hasMethod($object, 'set' . $name)) { |
798
|
|
|
$object->{'set' . $name}($value); |
799
|
|
|
} else { |
800
|
|
|
$object->$name = $value; |
801
|
|
|
} |
802
|
|
|
} |
803
|
|
|
|
804
|
|
|
/** |
805
|
|
|
* @deprecated 4.0.0:5.0.0 Use Injector::has() instead |
806
|
|
|
* @param $name |
807
|
|
|
* @return string |
808
|
|
|
*/ |
809
|
|
|
public function hasService($name) |
810
|
|
|
{ |
811
|
|
|
Deprecation::notice('5.0', 'Use Injector::has() instead'); |
812
|
|
|
|
813
|
|
|
return $this->has($name); |
814
|
|
|
} |
815
|
|
|
|
816
|
|
|
/** |
817
|
|
|
* Does the given service exist? |
818
|
|
|
* |
819
|
|
|
* We do a special check here for services that are using compound names. For example, |
820
|
|
|
* we might want to say that a property should be injected with Log.File or Log.Memory, |
821
|
|
|
* but have only registered a 'Log' service, we'll instead return that. |
822
|
|
|
* |
823
|
|
|
* Will recursively call itself for each depth of dotting. |
824
|
|
|
* |
825
|
|
|
* @param string $name |
826
|
|
|
* @return boolean |
827
|
|
|
*/ |
828
|
|
|
public function has($name) |
829
|
|
|
{ |
830
|
|
|
return (bool)$this->getServiceName($name); |
831
|
|
|
} |
832
|
|
|
|
833
|
|
|
/** |
834
|
|
|
* Does the given service exist, and if so, what's the stored name for it? |
835
|
|
|
* |
836
|
|
|
* We do a special check here for services that are using compound names. For example, |
837
|
|
|
* we might want to say that a property should be injected with Log.File or Log.Memory, |
838
|
|
|
* but have only registered a 'Log' service, we'll instead return that. |
839
|
|
|
* |
840
|
|
|
* Will recursively call itself for each depth of dotting. |
841
|
|
|
* |
842
|
|
|
* @param string $name |
843
|
|
|
* @return string|null The name of the service (as it might be different from the one passed in) |
844
|
|
|
*/ |
845
|
|
|
public function getServiceName($name) |
846
|
|
|
{ |
847
|
|
|
// Lazy load in spec (disable inheritance to check exact service name) |
848
|
|
|
if ($this->getServiceSpec($name, false)) { |
849
|
|
|
return $name; |
850
|
|
|
} |
851
|
|
|
|
852
|
|
|
// okay, check whether we've got a compound name - don't worry about 0 index, cause that's an |
853
|
|
|
// invalid name |
854
|
|
|
if (!strpos($name, '.')) { |
855
|
|
|
return null; |
856
|
|
|
} |
857
|
|
|
|
858
|
|
|
return $this->getServiceName(substr($name, 0, strrpos($name, '.'))); |
859
|
|
|
} |
860
|
|
|
|
861
|
|
|
/** |
862
|
|
|
* Register a service object with an optional name to register it as the |
863
|
|
|
* service for |
864
|
|
|
* |
865
|
|
|
* @param object $service The object to register |
866
|
|
|
* @param string $replace The name of the object to replace (if different to the |
867
|
|
|
* class name of the object to register) |
868
|
|
|
* @return $this |
869
|
|
|
*/ |
870
|
|
|
public function registerService($service, $replace = null) |
871
|
|
|
{ |
872
|
|
|
$registerAt = get_class($service); |
873
|
|
|
if ($replace !== null) { |
874
|
|
|
$registerAt = $replace; |
875
|
|
|
} |
876
|
|
|
|
877
|
|
|
$this->specs[$registerAt] = array('class' => get_class($service)); |
878
|
|
|
$this->serviceCache[$registerAt] = $service; |
879
|
|
|
return $this; |
880
|
|
|
} |
881
|
|
|
|
882
|
|
|
/** |
883
|
|
|
* Removes a named object from the cached list of objects managed |
884
|
|
|
* by the inject |
885
|
|
|
* |
886
|
|
|
* @param string $name The name to unregister |
887
|
|
|
* @return $this |
888
|
|
|
*/ |
889
|
|
|
public function unregisterNamedObject($name) |
890
|
|
|
{ |
891
|
|
|
unset($this->serviceCache[$name]); |
892
|
|
|
unset($this->specs[$name]); |
893
|
|
|
return $this; |
894
|
|
|
} |
895
|
|
|
|
896
|
|
|
/** |
897
|
|
|
* Clear out objects of one or more types that are managed by the injetor. |
898
|
|
|
* |
899
|
|
|
* @param array|string $types Base class of object (not service name) to remove |
900
|
|
|
* @return $this |
901
|
|
|
*/ |
902
|
|
|
public function unregisterObjects($types) |
903
|
|
|
{ |
904
|
|
|
if (!is_array($types)) { |
905
|
|
|
$types = [ $types ]; |
906
|
|
|
} |
907
|
|
|
|
908
|
|
|
// Filter all objects |
909
|
|
|
foreach ($this->serviceCache as $key => $object) { |
910
|
|
|
foreach ($types as $filterClass) { |
911
|
|
|
// Prevent destructive flushing |
912
|
|
|
if (strcasecmp($filterClass, 'object') === 0) { |
913
|
|
|
throw new InvalidArgumentException("Global unregistration is not allowed"); |
914
|
|
|
} |
915
|
|
|
if ($object instanceof $filterClass) { |
916
|
|
|
$this->unregisterNamedObject($key); |
917
|
|
|
break; |
918
|
|
|
} |
919
|
|
|
} |
920
|
|
|
} |
921
|
|
|
return $this; |
922
|
|
|
} |
923
|
|
|
|
924
|
|
|
/** |
925
|
|
|
* Get a named managed object |
926
|
|
|
* |
927
|
|
|
* Will first check to see if the item has been registered as a configured service/bean |
928
|
|
|
* and return that if so. |
929
|
|
|
* |
930
|
|
|
* Next, will check to see if there's any registered configuration for the given type |
931
|
|
|
* and will then try and load that |
932
|
|
|
* |
933
|
|
|
* Failing all of that, will just return a new instance of the specified object. |
934
|
|
|
* |
935
|
|
|
* @throws NotFoundExceptionInterface No entry was found for **this** identifier. |
936
|
|
|
* |
937
|
|
|
* @param string $name The name of the service to retrieve. If not a registered |
938
|
|
|
* service, then a class of the given name is instantiated |
939
|
|
|
* @param bool $asSingleton If set to false a new instance will be returned. |
940
|
|
|
* If true a singleton will be returned unless the spec is type=prototype' |
941
|
|
|
* @param array $constructorArgs Args to pass in to the constructor. Note: Ignored for singletons |
942
|
|
|
* @return mixed Instance of the specified object |
943
|
|
|
*/ |
944
|
|
|
public function get($name, $asSingleton = true, $constructorArgs = []) |
945
|
|
|
{ |
946
|
|
|
$object = $this->getNamedService($name, $asSingleton, $constructorArgs); |
947
|
|
|
|
948
|
|
|
if (!$object) { |
949
|
|
|
throw new InjectorNotFoundException("The '{$name}' service could not be found"); |
950
|
|
|
} |
951
|
|
|
|
952
|
|
|
return $object; |
953
|
|
|
} |
954
|
|
|
|
955
|
|
|
/** |
956
|
|
|
* Returns the service, or `null` if it doesnt' exist. See {@link get()} for main usage. |
957
|
|
|
* |
958
|
|
|
* @param string $name The name of the service to retrieve. If not a registered |
959
|
|
|
* service, then a class of the given name is instantiated |
960
|
|
|
* @param bool $asSingleton If set to false a new instance will be returned. |
961
|
|
|
* If true a singleton will be returned unless the spec is type=prototype' |
962
|
|
|
* @param array $constructorArgs Args to pass in to the constructor. Note: Ignored for singletons |
963
|
|
|
* @return mixed Instance of the specified object |
964
|
|
|
*/ |
965
|
|
|
protected function getNamedService($name, $asSingleton = true, $constructorArgs = []) |
966
|
|
|
{ |
967
|
|
|
// Normalise service / args |
968
|
|
|
list($name, $constructorArgs) = $this->normaliseArguments($name, $constructorArgs); |
969
|
|
|
|
970
|
|
|
// Resolve name with the appropriate spec, or a suitable mock for new services |
971
|
|
|
list($name, $spec) = $this->getServiceNamedSpec($name, $constructorArgs); |
972
|
|
|
|
973
|
|
|
// Check if we are getting a prototype or singleton |
974
|
|
|
$type = $asSingleton |
975
|
|
|
? (isset($spec['type']) ? $spec['type'] : self::SINGLETON) |
976
|
|
|
: self::PROTOTYPE; |
977
|
|
|
|
978
|
|
|
// Return existing instance for singletons |
979
|
|
|
if ($type === self::SINGLETON && isset($this->serviceCache[$name])) { |
980
|
|
|
return $this->serviceCache[$name]; |
981
|
|
|
} |
982
|
|
|
|
983
|
|
|
// Update constructor args |
984
|
|
|
if ($type === self::PROTOTYPE && $constructorArgs) { |
985
|
|
|
// Passed in args are expected to already be normalised (no service references) |
986
|
|
|
$spec['constructor'] = $constructorArgs; |
987
|
|
|
} else { |
988
|
|
|
// Resolve references in constructor args |
989
|
|
|
$this->updateSpecConstructor($spec); |
|
|
|
|
990
|
|
|
} |
991
|
|
|
|
992
|
|
|
// Build instance |
993
|
|
|
return $this->instantiate($spec, $name, $type); |
994
|
|
|
} |
995
|
|
|
|
996
|
|
|
/** |
997
|
|
|
* Detect service references with constructor arguments included. |
998
|
|
|
* These will be split out of the service name reference and appended |
999
|
|
|
* to the $args |
1000
|
|
|
* |
1001
|
|
|
* @param string $name |
1002
|
|
|
* @param array $args |
1003
|
|
|
* @return array Two items with name and new args |
1004
|
|
|
*/ |
1005
|
|
|
protected function normaliseArguments($name, $args = []) |
1006
|
|
|
{ |
1007
|
|
|
// Allow service names of the form "%$ServiceName" |
1008
|
|
|
if (substr($name, 0, 2) == '%$') { |
1009
|
|
|
$name = substr($name, 2); |
1010
|
|
|
} |
1011
|
|
|
|
1012
|
|
|
if (strstr($name, '(')) { |
1013
|
|
|
list($name, $extraArgs) = ClassInfo::parse_class_spec($name); |
1014
|
|
|
if ($args) { |
|
|
|
|
1015
|
|
|
$args = array_merge($args, $extraArgs); |
1016
|
|
|
} else { |
1017
|
|
|
$args = $extraArgs; |
1018
|
|
|
} |
1019
|
|
|
} |
1020
|
|
|
$name = trim($name); |
1021
|
|
|
return [$name, $args]; |
1022
|
|
|
} |
1023
|
|
|
|
1024
|
|
|
/** |
1025
|
|
|
* Get or build a named service and specification |
1026
|
|
|
* |
1027
|
|
|
* @param string $name Service name |
1028
|
|
|
* @param array $constructorArgs Optional constructor args |
1029
|
|
|
* @return array |
1030
|
|
|
*/ |
1031
|
|
|
protected function getServiceNamedSpec($name, $constructorArgs = []) |
1032
|
|
|
{ |
1033
|
|
|
$spec = $this->getServiceSpec($name); |
1034
|
|
|
if ($spec) { |
1035
|
|
|
// Resolve to exact service name (in case inherited) |
1036
|
|
|
$name = $this->getServiceName($name); |
1037
|
|
|
} else { |
1038
|
|
|
// Late-generate config spec for non-configured spec |
1039
|
|
|
$spec = [ |
1040
|
|
|
'class' => $name, |
1041
|
|
|
'constructor' => $constructorArgs, |
1042
|
|
|
]; |
1043
|
|
|
} |
1044
|
|
|
return [$name, $spec]; |
1045
|
|
|
} |
1046
|
|
|
|
1047
|
|
|
/** |
1048
|
|
|
* Search for spec, lazy-loading in from config locator. |
1049
|
|
|
* Falls back to parent service name if unloaded |
1050
|
|
|
* |
1051
|
|
|
* @param string $name |
1052
|
|
|
* @param bool $inherit Set to true to inherit from parent service if `.` suffixed |
1053
|
|
|
* E.g. 'Psr/Log/LoggerInterface.custom' would fail over to 'Psr/Log/LoggerInterface' |
1054
|
|
|
* @return mixed|object |
1055
|
|
|
*/ |
1056
|
|
|
public function getServiceSpec($name, $inherit = true) |
1057
|
|
|
{ |
1058
|
|
|
if (isset($this->specs[$name])) { |
1059
|
|
|
return $this->specs[$name]; |
1060
|
|
|
} |
1061
|
|
|
|
1062
|
|
|
// Lazy load |
1063
|
|
|
$config = $this->configLocator->locateConfigFor($name); |
1064
|
|
|
if ($config) { |
1065
|
|
|
$this->load([$name => $config]); |
1066
|
|
|
if (isset($this->specs[$name])) { |
1067
|
|
|
return $this->specs[$name]; |
1068
|
|
|
} |
1069
|
|
|
} |
1070
|
|
|
|
1071
|
|
|
// Fail over to parent service if allowed |
1072
|
|
|
if (!$inherit || !strpos($name, '.')) { |
1073
|
|
|
return null; |
1074
|
|
|
} |
1075
|
|
|
|
1076
|
|
|
return $this->getServiceSpec(substr($name, 0, strrpos($name, '.'))); |
1077
|
|
|
} |
1078
|
|
|
|
1079
|
|
|
/** |
1080
|
|
|
* Magic method to return an item directly |
1081
|
|
|
* |
1082
|
|
|
* @param string $name |
1083
|
|
|
* The named object to retrieve |
1084
|
|
|
* @return mixed |
1085
|
|
|
*/ |
1086
|
|
|
public function __get($name) |
1087
|
|
|
{ |
1088
|
|
|
return $this->get($name); |
1089
|
|
|
} |
1090
|
|
|
|
1091
|
|
|
/** |
1092
|
|
|
* Similar to get() but always returns a new object of the given type |
1093
|
|
|
* |
1094
|
|
|
* Additional parameters are passed through as |
1095
|
|
|
* |
1096
|
|
|
* @param string $name |
1097
|
|
|
* @param mixed $argument,... arguments to pass to the constructor |
1098
|
|
|
* @return mixed A new instance of the specified object |
1099
|
|
|
*/ |
1100
|
|
|
public function create($name, $argument = null) |
1101
|
|
|
{ |
1102
|
|
|
$constructorArgs = func_get_args(); |
1103
|
|
|
array_shift($constructorArgs); |
1104
|
|
|
return $this->createWithArgs($name, $constructorArgs); |
1105
|
|
|
} |
1106
|
|
|
|
1107
|
|
|
/** |
1108
|
|
|
* Creates an object with the supplied argument array |
1109
|
|
|
* |
1110
|
|
|
* @param string $name Name of the class to create an object of |
1111
|
|
|
* @param array $constructorArgs Arguments to pass to the constructor |
1112
|
|
|
* @return mixed |
1113
|
|
|
*/ |
1114
|
|
|
public function createWithArgs($name, $constructorArgs) |
1115
|
|
|
{ |
1116
|
|
|
return $this->get($name, false, $constructorArgs); |
1117
|
|
|
} |
1118
|
|
|
} |
1119
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.