Merge pull request #530 from nyeholt/injector_updates

A resubmission of a previous pull request. Contains a couple of bugfixes (including ticket #7448) and minor usage enhancements
This commit is contained in:
Sam Minnée 2012-06-14 16:42:30 -07:00
commit dda9683758
3 changed files with 127 additions and 11 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';
@ -224,6 +230,30 @@ class Injector {
$this->objectCreator = $obj;
}
/**
* Accessor (for testing purposes)
* @return InjectionCreator
*/
public function getObjectCreator() {
return $this->objectCreator;
}
/**
* Set the configuration locator
* @param ServiceConfigurationLocator $configLocator
*/
public function setConfigLocator($configLocator) {
$this->configLocator = $configLocator;
}
/**
* Retrieve the configuration locator
* @return ServiceConfigurationLocator
*/
public function getConfigLocator() {
return $this->configLocator;
}
/**
* Add in a specific mapping that should be catered for on a type.
* This allows configuration of what should occur when an object
@ -418,8 +448,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 +477,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 +695,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 +730,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 +765,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

@ -249,6 +249,12 @@ require_once 'core/manifest/ManifestFileFinder.php';
require_once 'core/manifest/TemplateLoader.php';
require_once 'core/manifest/TemplateManifest.php';
require_once 'core/manifest/TokenisedRegularExpression.php';
require_once 'control/injector/Injector.php';
// Initialise the dependency injector as soon as possible, as it is
// subsequently used by some of the following code
$default_options = array('locator' => 'SilverStripeServiceConfigurationLocator');
Injector::inst($default_options);
///////////////////////////////////////////////////////////////////////////////
// MANIFEST
@ -286,9 +292,6 @@ if(Director::isLive()) {
*/
Debug::loadErrorHandlers();
// initialise the dependency injector
$default_options = array('locator' => 'SilverStripeServiceConfigurationLocator');
Injector::inst($default_options);
///////////////////////////////////////////////////////////////////////////////
// HELPER FUNCTIONS

View File

@ -12,6 +12,12 @@ define('TEST_SERVICES', dirname(__FILE__) . '/testservices');
*/
class InjectorTest extends SapphireTest {
public function testCorrectlyInitialised() {
$injector = Injector::inst();
$this->assertTrue($injector->getConfigLocator() instanceof SilverStripeServiceConfigurationLocator,
'If this fails, it is likely because the injector has been referenced BEFORE being initialised in Core.php');
}
public function testBasicInjector() {
$injector = new Injector();
$injector->setAutoScanProperties(true);
@ -209,6 +215,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 +463,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 {