BUGFIX Make sure to only construct args for prototype object creation if

there are actually args passed through to prevent overwriting with null
args if they're passed

MINOR Added __get alias to remove need for explicit ->get() call

MINOR Added the injector instance as an object that can be injected into other classes

BUGFIX Fixed issue described in http://open.silverstripe.org/ticket/7448 whereby using the injector to create an object of a type already registered as a singleton would actually overwrite the stored singleton object
This commit is contained in:
Marcus Nyeholt 2012-06-14 17:11:36 +10:00
parent e23a7585a7
commit 56388ef1d8
2 changed files with 91 additions and 8 deletions

View File

@ -176,9 +176,15 @@ class Injector {
*/
public function __construct($config = null) {
$this->injectMap = array();
$this->serviceCache = array();
$this->serviceCache = array(
'Injector' => $this,
);
$this->specs = array(
'Injector' => array('class' => 'Injector')
);
$this->autoProperties = array();
$this->specs = array();
$creatorClass = isset($config['creator']) ? $config['creator'] : 'InjectionCreator';
$locatorClass = isset($config['locator']) ? $config['locator'] : 'ServiceConfigurationLocator';
@ -418,8 +424,14 @@ class Injector {
*
* @param array $spec
* The specification of the class to instantiate
* @param string $id
* The name of the object being created. If not supplied, then the id will be inferred from the
* object being created
* @param string $type
* Whether to create as a singleton or prototype object. Allows code to be explicit as to how it
* wants the object to be returned
*/
protected function instantiate($spec, $id=null) {
protected function instantiate($spec, $id=null, $type = null) {
if (is_string($spec)) {
$spec = array('class' => $spec);
}
@ -441,7 +453,10 @@ class Injector {
// now set the service in place if needbe. This is NOT done for prototype beans, as they're
// created anew each time
$type = isset($spec['type']) ? $spec['type'] : null;
if (!$type) {
$type = isset($spec['type']) ? $spec['type'] : null;
}
if ($id && (!$type || $type != 'prototype')) {
// this ABSOLUTELY must be set before the object is injected.
// This prevents circular reference errors down the line
@ -656,7 +671,7 @@ class Injector {
* Clear out all objects that are managed by the injetor.
*/
public function unregisterAllObjects() {
$this->serviceCache = array();
$this->serviceCache = array('Injector' => $this);
}
/**
@ -691,12 +706,12 @@ class Injector {
$spec = $this->specs[$serviceName];
$type = isset($spec['type']) ? $spec['type'] : null;
// if we're a prototype OR we're not wanting a singleton
// if we're explicitly a prototype OR we're not wanting a singleton
if (($type && $type == 'prototype') || !$asSingleton) {
if ($spec) {
if ($spec && $constructorArgs) {
$spec['constructor'] = $constructorArgs;
}
return $this->instantiate($spec, $serviceName);
return $this->instantiate($spec, $serviceName, !$type ? 'prototype' : $type);
} else {
if (!isset($this->serviceCache[$serviceName])) {
$this->instantiate($spec, $serviceName);
@ -726,6 +741,17 @@ class Injector {
return $this->instantiate($spec);
}
/**
* Magic method to return an item directly
*
* @param string $name
* The named object to retrieve
* @return mixed
*/
public function __get($name) {
return $this->get($name);
}
/**
* Similar to get() but always returns a new object of the given type

View File

@ -209,6 +209,42 @@ class InjectorTest extends SapphireTest {
$sample = $injector->get('SampleService');
$this->assertEquals($sample->constructorVarOne, 'val1');
$this->assertEquals(get_class($sample->constructorVarTwo), 'AnotherService');
$injector = new Injector();
$config = array(array(
'src' => TEST_SERVICES . '/SampleService.php',
'constructor' => array(
'val1',
'val2',
)
));
$injector->load($config);
$sample = $injector->get('SampleService');
$this->assertEquals($sample->constructorVarOne, 'val1');
$this->assertEquals($sample->constructorVarTwo, 'val2');
// test constructors on prototype
$injector = new Injector();
$config = array(array(
'type' => 'prototype',
'src' => TEST_SERVICES . '/SampleService.php',
'constructor' => array(
'val1',
'val2',
)
));
$injector->load($config);
$sample = $injector->get('SampleService');
$this->assertEquals($sample->constructorVarOne, 'val1');
$this->assertEquals($sample->constructorVarTwo, 'val2');
$again = $injector->get('SampleService');
$this->assertFalse($sample === $again);
$this->assertEquals($sample->constructorVarOne, 'val1');
$this->assertEquals($sample->constructorVarTwo, 'val2');
}
public function testInjectUsingSetter() {
@ -421,6 +457,27 @@ class InjectorTest extends SapphireTest {
$obj = $injector->get('MyChildClass');
$this->assertEquals($obj->one, 'the one');
}
public function testSameNamedSingeltonPrototype() {
$injector = new Injector();
// get a singleton object
$object = $injector->get('NeedsBothCirculars');
$object->var = 'One';
$again = $injector->get('NeedsBothCirculars');
$this->assertEquals($again->var, 'One');
// create a NEW instance object
$new = $injector->create('NeedsBothCirculars');
$this->assertNull($new->var);
// this will trigger a problem below
$new->var = 'Two';
$again = $injector->get('NeedsBothCirculars');
$this->assertEquals($again->var, 'One');
}
}
class TestObject {