API Injector dependencies no longer inherit from parent classes automatically

API Allow classes to inherit service definitions explicitly
This commit is contained in:
Damian Mooyman 2016-05-03 18:38:49 +12:00
parent ca1716b9f3
commit 8ce3d90e18
4 changed files with 47 additions and 54 deletions

View File

@ -19,53 +19,36 @@ class SilverStripeServiceConfigurationLocator extends ServiceConfigurationLocato
protected $configs = array();
public function locateConfigFor($name) {
// Check direct or cached result
$config = $this->configFor($name);
if($config !== null) return $config;
// do parent lookup if it's a class
if (class_exists($name)) {
$parents = array_reverse(array_values(ClassInfo::ancestry($name)));
array_shift($parents);
foreach ($parents as $parent) {
// have we already got for this?
$config = $this->configFor($parent);
if($config !== null) {
// Cache this result
$this->configs[$name] = $config;
return $config;
}
}
if(!$config) {
return null;
}
// there is no parent config, so we'll record that as false so we don't do the expensive
// lookup through parents again
$this->configs[$name] = false;
// If config is in `%$Source` format then inherit from the named config
if(is_string($config) && stripos($config, '%$') === 0) {
$name = substr($config, 2);
return $this->locateConfigFor($name);
}
// Return the located config
return $config;
}
/**
* Retrieves the config for a named service without performing a hierarchy walk
*
* @param string $name Name of service
* @return mixed Returns either the configuration data, if there is any. A missing config is denoted
* by a value of either null (there is no direct config assigned and a hierarchy walk is necessary)
* or false (there is no config for this class, nor within the hierarchy for this class).
* @return mixed Get config for this service
*/
protected function configFor($name) {
// Return cached result
if (isset($this->configs[$name])) {
return $this->configs[$name]; // Potentially false
if (array_key_exists($name, $this->configs)) {
return $this->configs[$name];
}
$config = Config::inst()->get('Injector', $name);
if ($config) {
$this->configs[$name] = $config;
return $config;
} else {
return null;
}
$this->configs[$name] = $config;
return $config;
}
}

View File

@ -225,6 +225,22 @@ Would setup the following
* Look at the properties to be injected and look for the config for `MySQLDatabase`
* Create a MySQLDatabase class, passing dbusername and dbpassword as the parameters to the constructor.
## Service inheritance
By default, services registered with Injector do not inherit from one another; This is because it registers
named services, which may not be actual classes, and thus should not behave as though they were.
Thus if you want an object to have the injected dependencies of a service of another name, you must
assign a reference to that service.
:::yaml
Injector:
JSONServiceDefinition:
properties:
Serialiser: JSONSerialiser
GZIPJSONProvider: %$JSONServiceDefinition
## Testing with Injector

View File

@ -42,6 +42,7 @@
* `CMSMain.enabled_legacy_actions` config is removed.
* `DataObject::can` has new method signature with `$context` parameter.
* `SiteTree.alternatePreviewLink` is deprecated. Use `updatePreviewLink` instead.
* `Injector` dependencies no longer automatically inherit from parent classes.
## New API

View File

@ -487,38 +487,30 @@ class InjectorTest extends SapphireTest {
public function testInheritedConfig() {
// Test top-down caching of config inheritance
// Test that child class does not automatically inherit config
$injector = new Injector(array('locator' => 'SilverStripeServiceConfigurationLocator'));
Config::inst()->update('Injector', 'MyParentClass', array('properties' => array('one' => 'the one')));
Config::inst()->update('Injector', 'MyChildClass', array('properties' => array('one' => 'the two')));
Config::inst()->update('Injector', 'MyParentClass', [
'properties' => ['one' => 'the one'],
'class' => 'MyParentClass',
]);
$obj = $injector->get('MyParentClass');
$this->assertInstanceOf('MyParentClass', $obj);
$this->assertEquals($obj->one, 'the one');
// Class isn't inherited and parent properties are ignored
$obj = $injector->get('MyChildClass');
$this->assertEquals($obj->one, 'the two');
$this->assertInstanceOf('MyChildClass', $obj);
$this->assertNotEquals($obj->one, 'the one');
$obj = $injector->get('MyGrandChildClass');
$this->assertEquals($obj->one, 'the two');
$obj = $injector->get('MyGreatGrandChildClass');
$this->assertEquals($obj->one, 'the two');
// Test bottom-up caching of config inheritance
// Set child class as alias
$injector = new Injector(array('locator' => 'SilverStripeServiceConfigurationLocator'));
Config::inst()->update('Injector', 'MyParentClass', array('properties' => array('one' => 'the three')));
Config::inst()->update('Injector', 'MyChildClass', array('properties' => array('one' => 'the four')));
$obj = $injector->get('MyGreatGrandChildClass');
$this->assertEquals($obj->one, 'the four');
$obj = $injector->get('MyGrandChildClass');
$this->assertEquals($obj->one, 'the four');
Config::inst()->update('Injector', 'MyChildClass', '%$MyParentClass');
// Class isn't inherited and parent properties are ignored
$obj = $injector->get('MyChildClass');
$this->assertEquals($obj->one, 'the four');
$this->assertInstanceOf('MyParentClass', $obj);
$this->assertEquals($obj->one, 'the one');
$obj = $injector->get('MyParentClass');
$this->assertEquals($obj->one, 'the three');
}
public function testSameNamedSingeltonPrototype() {
@ -794,6 +786,7 @@ class ConstructableObject implements TestOnly {
}
}
class TestObject implements TestOnly {
public $sampleService;