Permalink
Browse files

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
  • Loading branch information...
1 parent e23a758 commit 56388ef1d8cf6db9a4dc9c025e82588c3b419fa1 Marcus Nyeholt committed Jun 14, 2012
Showing with 91 additions and 8 deletions.
  1. +34 −8 control/injector/Injector.php
  2. +57 −0 tests/injector/InjectorTest.php
View
42 control/injector/Injector.php
@@ -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 @@ public function convertServiceProperty($value) {
*
* @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 @@ protected function instantiate($spec, $id=null) {
// 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 @@ public function unregisterNamedObject($name) {
* 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 @@ public function get($name, $asSingleton = true, $constructorArgs = null) {
$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 @@ public function get($name, $asSingleton = true, $constructorArgs = null) {
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
57 tests/injector/InjectorTest.php
@@ -209,6 +209,42 @@ public function testInjectUsingConstructor() {
$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 @@ public function testInheritedConfig() {
$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 {

0 comments on commit 56388ef

Please sign in to comment.