mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-08-28 21:46:10 +02:00
680 lines
18 KiB
PHP
680 lines
18 KiB
PHP
<?php
|
|
|
|
define('TEST_SERVICES', dirname(__FILE__) . '/testservices');
|
|
|
|
/**
|
|
* Tests for the dependency injector
|
|
*
|
|
* Note that these are SS conversions of the existing Simpletest unit tests
|
|
*
|
|
* @author marcus@silverstripe.com.au
|
|
* @license BSD License http://silverstripe.org/bsd-license/
|
|
*/
|
|
class InjectorTest extends SapphireTest {
|
|
|
|
public function testCorrectlyInitialised() {
|
|
$injector = Injector::inst();
|
|
$this->assertTrue($injector->getConfigLocator() instanceof SilverStripeServiceConfigurationLocator,
|
|
'Failure most likely because the injector has been referenced BEFORE being initialised in Core.php');
|
|
}
|
|
|
|
public function testBasicInjector() {
|
|
$injector = new Injector();
|
|
$injector->setAutoScanProperties(true);
|
|
$config = array(array('src' => TEST_SERVICES . '/SampleService.php',));
|
|
|
|
$injector->load($config);
|
|
$this->assertTrue($injector->hasService('SampleService') == 'SampleService');
|
|
|
|
$myObject = new TestObject();
|
|
$injector->inject($myObject);
|
|
|
|
$this->assertEquals(get_class($myObject->sampleService), 'SampleService');
|
|
}
|
|
|
|
public function testConfiguredInjector() {
|
|
$injector = new Injector();
|
|
$services = array(
|
|
array(
|
|
'src' => TEST_SERVICES . '/AnotherService.php',
|
|
'properties' => array('config_property' => 'Value'),
|
|
),
|
|
array(
|
|
'src' => TEST_SERVICES . '/SampleService.php',
|
|
)
|
|
);
|
|
|
|
$injector->load($services);
|
|
$this->assertTrue($injector->hasService('SampleService') == 'SampleService');
|
|
// We expect a false because the 'AnotherService' is actually
|
|
// just a replacement of the SampleService
|
|
$this->assertTrue($injector->hasService('AnotherService') == 'AnotherService');
|
|
|
|
$item = $injector->get('AnotherService');
|
|
|
|
$this->assertEquals('Value', $item->config_property);
|
|
}
|
|
|
|
public function testIdToNameMap() {
|
|
$injector = new Injector();
|
|
$services = array(
|
|
'FirstId' => 'AnotherService',
|
|
'SecondId' => 'SampleService',
|
|
);
|
|
|
|
$injector->load($services);
|
|
|
|
$this->assertTrue($injector->hasService('FirstId') == 'FirstId');
|
|
$this->assertTrue($injector->hasService('SecondId') == 'SecondId');
|
|
|
|
$this->assertTrue($injector->get('FirstId') instanceof AnotherService);
|
|
$this->assertTrue($injector->get('SecondId') instanceof SampleService);
|
|
}
|
|
|
|
public function testReplaceService() {
|
|
$injector = new Injector();
|
|
$injector->setAutoScanProperties(true);
|
|
|
|
$config = array(array('src' => TEST_SERVICES . '/SampleService.php'));
|
|
|
|
// load
|
|
$injector->load($config);
|
|
|
|
// inject
|
|
$myObject = new TestObject();
|
|
$injector->inject($myObject);
|
|
|
|
$this->assertEquals(get_class($myObject->sampleService), 'SampleService');
|
|
|
|
// also tests that ID can be the key in the array
|
|
$config = array('SampleService' => array('src' => TEST_SERVICES . '/AnotherService.php'));
|
|
// , 'id' => 'SampleService'));
|
|
// load
|
|
$injector->load($config);
|
|
|
|
$injector->inject($myObject);
|
|
$this->assertEquals('AnotherService', get_class($myObject->sampleService));
|
|
}
|
|
|
|
public function testUpdateSpec() {
|
|
$injector = new Injector();
|
|
$services = array(
|
|
'AnotherService' => array(
|
|
'src' => TEST_SERVICES . '/AnotherService.php',
|
|
'properties' => array(
|
|
'filters' => array(
|
|
'One',
|
|
'Two',
|
|
)
|
|
),
|
|
)
|
|
);
|
|
|
|
$injector->load($services);
|
|
|
|
$injector->updateSpec('AnotherService', 'filters', 'Three');
|
|
$another = $injector->get('AnotherService');
|
|
|
|
$this->assertEquals(3, count($another->filters));
|
|
$this->assertEquals('Three', $another->filters[2]);
|
|
}
|
|
|
|
public function testAutoSetInjector() {
|
|
$injector = new Injector();
|
|
$injector->setAutoScanProperties(true);
|
|
$injector->addAutoProperty('auto', 'somevalue');
|
|
$config = array(array('src' => TEST_SERVICES . '/SampleService.php',));
|
|
$injector->load($config);
|
|
|
|
$this->assertTrue($injector->hasService('SampleService') == 'SampleService');
|
|
// We expect a false because the 'AnotherService' is actually
|
|
// just a replacement of the SampleService
|
|
|
|
$myObject = new TestObject();
|
|
|
|
$injector->inject($myObject);
|
|
|
|
$this->assertEquals(get_class($myObject->sampleService), 'SampleService');
|
|
$this->assertEquals($myObject->auto, 'somevalue');
|
|
}
|
|
|
|
public function testSettingSpecificProperty() {
|
|
$injector = new Injector();
|
|
$config = array('AnotherService');
|
|
$injector->load($config);
|
|
$injector->setInjectMapping('TestObject', 'sampleService', 'AnotherService');
|
|
$testObject = $injector->get('TestObject');
|
|
|
|
$this->assertEquals(get_class($testObject->sampleService), 'AnotherService');
|
|
}
|
|
|
|
public function testSettingSpecificMethod() {
|
|
$injector = new Injector();
|
|
$config = array('AnotherService');
|
|
$injector->load($config);
|
|
$injector->setInjectMapping('TestObject', 'setSomething', 'AnotherService', 'method');
|
|
|
|
$testObject = $injector->get('TestObject');
|
|
|
|
$this->assertEquals(get_class($testObject->sampleService), 'AnotherService');
|
|
}
|
|
|
|
public function testInjectingScopedService() {
|
|
$injector = new Injector();
|
|
|
|
$config = array(
|
|
'AnotherService',
|
|
'AnotherService.DottedChild' => 'SampleService',
|
|
);
|
|
|
|
$injector->load($config);
|
|
|
|
$service = $injector->get('AnotherService.DottedChild');
|
|
$this->assertEquals(get_class($service), 'SampleService');
|
|
|
|
$service = $injector->get('AnotherService.Subset');
|
|
$this->assertEquals(get_class($service), 'AnotherService');
|
|
|
|
$injector->setInjectMapping('TestObject', 'sampleService', 'AnotherService.Geronimo');
|
|
$testObject = $injector->create('TestObject');
|
|
$this->assertEquals(get_class($testObject->sampleService), 'AnotherService');
|
|
|
|
$injector->setInjectMapping('TestObject', 'sampleService', 'AnotherService.DottedChild.AnotherDown');
|
|
$testObject = $injector->create('TestObject');
|
|
$this->assertEquals(get_class($testObject->sampleService), 'SampleService');
|
|
|
|
}
|
|
|
|
public function testInjectUsingConstructor() {
|
|
$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');
|
|
|
|
$injector = new Injector();
|
|
$config = array(
|
|
'AnotherService',
|
|
array(
|
|
'src' => TEST_SERVICES . '/SampleService.php',
|
|
'constructor' => array(
|
|
'val1',
|
|
'%$AnotherService',
|
|
)
|
|
)
|
|
);
|
|
|
|
$injector->load($config);
|
|
$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() {
|
|
$injector = new Injector();
|
|
$injector->setAutoScanProperties(true);
|
|
$config = array(array('src' => TEST_SERVICES . '/SampleService.php',));
|
|
|
|
$injector->load($config);
|
|
$this->assertTrue($injector->hasService('SampleService') == 'SampleService');
|
|
|
|
$myObject = new OtherTestObject();
|
|
$injector->inject($myObject);
|
|
|
|
$this->assertEquals(get_class($myObject->s()), 'SampleService');
|
|
|
|
// and again because it goes down a different code path when setting things
|
|
// based on the inject map
|
|
$myObject = new OtherTestObject();
|
|
$injector->inject($myObject);
|
|
|
|
$this->assertEquals(get_class($myObject->s()), 'SampleService');
|
|
}
|
|
|
|
// make sure we can just get any arbitrary object - it should be created for us
|
|
public function testInstantiateAnObjectViaGet() {
|
|
$injector = new Injector();
|
|
$injector->setAutoScanProperties(true);
|
|
$config = array(array('src' => TEST_SERVICES . '/SampleService.php',));
|
|
|
|
$injector->load($config);
|
|
$this->assertTrue($injector->hasService('SampleService') == 'SampleService');
|
|
|
|
$myObject = $injector->get('OtherTestObject');
|
|
$this->assertEquals(get_class($myObject->s()), 'SampleService');
|
|
|
|
// and again because it goes down a different code path when setting things
|
|
// based on the inject map
|
|
$myObject = $injector->get('OtherTestObject');
|
|
$this->assertEquals(get_class($myObject->s()), 'SampleService');
|
|
}
|
|
|
|
public function testCircularReference() {
|
|
$services = array('CircularOne', 'CircularTwo');
|
|
$injector = new Injector($services);
|
|
$injector->setAutoScanProperties(true);
|
|
|
|
$obj = $injector->get('NeedsBothCirculars');
|
|
|
|
$this->assertTrue($obj->circularOne instanceof CircularOne);
|
|
$this->assertTrue($obj->circularTwo instanceof CircularTwo);
|
|
}
|
|
|
|
public function testPrototypeObjects() {
|
|
$services = array('CircularOne', 'CircularTwo', array('class' => 'NeedsBothCirculars', 'type' => 'prototype'));
|
|
$injector = new Injector($services);
|
|
$injector->setAutoScanProperties(true);
|
|
$obj1 = $injector->get('NeedsBothCirculars');
|
|
$obj2 = $injector->get('NeedsBothCirculars');
|
|
|
|
// if this was the same object, then $obj1->var would now be two
|
|
$obj1->var = 'one';
|
|
$obj2->var = 'two';
|
|
|
|
$this->assertTrue($obj1->circularOne instanceof CircularOne);
|
|
$this->assertTrue($obj1->circularTwo instanceof CircularTwo);
|
|
|
|
$this->assertEquals($obj1->circularOne, $obj2->circularOne);
|
|
$this->assertNotEquals($obj1, $obj2);
|
|
}
|
|
|
|
public function testSimpleInstantiation() {
|
|
$services = array('CircularOne', 'CircularTwo');
|
|
$injector = new Injector($services);
|
|
|
|
// similar to the above, but explicitly instantiating this object here
|
|
$obj1 = $injector->create('NeedsBothCirculars');
|
|
$obj2 = $injector->create('NeedsBothCirculars');
|
|
|
|
// if this was the same object, then $obj1->var would now be two
|
|
$obj1->var = 'one';
|
|
$obj2->var = 'two';
|
|
|
|
$this->assertEquals($obj1->circularOne, $obj2->circularOne);
|
|
$this->assertNotEquals($obj1, $obj2);
|
|
}
|
|
|
|
public function testCreateWithConstructor() {
|
|
$injector = new Injector();
|
|
$obj = $injector->create('CircularTwo', 'param');
|
|
$this->assertEquals($obj->otherVar, 'param');
|
|
}
|
|
|
|
public function testSimpleSingleton() {
|
|
$injector = new Injector();
|
|
|
|
$one = $injector->create('CircularOne');
|
|
$two = $injector->create('CircularOne');
|
|
|
|
$this->assertFalse($one === $two);
|
|
|
|
$one = $injector->get('CircularTwo');
|
|
$two = $injector->get('CircularTwo');
|
|
|
|
$this->assertTrue($one === $two);
|
|
}
|
|
|
|
public function testOverridePriority() {
|
|
$injector = new Injector();
|
|
$injector->setAutoScanProperties(true);
|
|
$config = array(
|
|
array(
|
|
'src' => TEST_SERVICES . '/SampleService.php',
|
|
'priority' => 10,
|
|
)
|
|
);
|
|
|
|
// load
|
|
$injector->load($config);
|
|
|
|
// inject
|
|
$myObject = new TestObject();
|
|
$injector->inject($myObject);
|
|
|
|
$this->assertEquals(get_class($myObject->sampleService), 'SampleService');
|
|
|
|
$config = array(
|
|
array(
|
|
'src' => TEST_SERVICES . '/AnotherService.php',
|
|
'id' => 'SampleService',
|
|
'priority' => 1,
|
|
)
|
|
);
|
|
// load
|
|
$injector->load($config);
|
|
|
|
$injector->inject($myObject);
|
|
$this->assertEquals('SampleService', get_class($myObject->sampleService));
|
|
}
|
|
|
|
/**
|
|
* Specific test method to illustrate various ways of setting a requirements backend
|
|
*/
|
|
public function testRequirementsSettingOptions() {
|
|
$injector = new Injector();
|
|
$config = array(
|
|
'OriginalRequirementsBackend',
|
|
'NewRequirementsBackend',
|
|
'DummyRequirements' => array(
|
|
'constructor' => array(
|
|
'%$OriginalRequirementsBackend'
|
|
)
|
|
)
|
|
);
|
|
|
|
$injector->load($config);
|
|
|
|
$requirements = $injector->get('DummyRequirements');
|
|
$this->assertEquals('OriginalRequirementsBackend', get_class($requirements->backend));
|
|
|
|
// just overriding the definition here
|
|
$injector->load(array(
|
|
'DummyRequirements' => array(
|
|
'constructor' => array(
|
|
'%$NewRequirementsBackend'
|
|
)
|
|
)
|
|
));
|
|
|
|
// requirements should have been reinstantiated with the new bean setting
|
|
$requirements = $injector->get('DummyRequirements');
|
|
$this->assertEquals('NewRequirementsBackend', get_class($requirements->backend));
|
|
}
|
|
|
|
/**
|
|
* disabled for now
|
|
*/
|
|
public function testStaticInjections() {
|
|
$injector = new Injector();
|
|
$config = array(
|
|
'NewRequirementsBackend',
|
|
);
|
|
|
|
$injector->load($config);
|
|
|
|
$si = $injector->get('TestStaticInjections');
|
|
$this->assertEquals('NewRequirementsBackend', get_class($si->backend));
|
|
}
|
|
|
|
public function testCustomObjectCreator() {
|
|
$injector = new Injector();
|
|
$injector->setObjectCreator(new SSObjectCreator($injector));
|
|
$config = array(
|
|
'OriginalRequirementsBackend',
|
|
'DummyRequirements' => array(
|
|
'class' => 'DummyRequirements(\'%$OriginalRequirementsBackend\')'
|
|
)
|
|
);
|
|
$injector->load($config);
|
|
|
|
$requirements = $injector->get('DummyRequirements');
|
|
$this->assertEquals('OriginalRequirementsBackend', get_class($requirements->backend));
|
|
}
|
|
|
|
public function testInheritedConfig() {
|
|
$injector = new Injector(array('locator' => 'SilverStripeServiceConfigurationLocator'));
|
|
Config::inst()->update('Injector', 'MyParentClass', array('properties' => array('one' => 'the one')));
|
|
$obj = $injector->get('MyParentClass');
|
|
$this->assertEquals($obj->one, 'the one');
|
|
|
|
$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');
|
|
}
|
|
|
|
public function testConvertServicePropertyOnCreate() {
|
|
// make sure convert service property is not called on direct calls to create, only on configured
|
|
// declarations to avoid un-needed function calls
|
|
$injector = new Injector();
|
|
$item = $injector->create('ConstructableObject', '%$TestObject');
|
|
$this->assertEquals('%$TestObject', $item->property);
|
|
|
|
// do it again but have test object configured as a constructor dependency
|
|
$injector = new Injector();
|
|
$config = array(
|
|
'ConstructableObject' => array(
|
|
'constructor' => array(
|
|
'%$TestObject'
|
|
)
|
|
)
|
|
);
|
|
|
|
$injector->load($config);
|
|
$item = $injector->get('ConstructableObject');
|
|
$this->assertTrue($item->property instanceof TestObject);
|
|
|
|
// and with a configured object defining TestObject to be something else!
|
|
$injector = new Injector(array('locator' => 'InjectorTestConfigLocator'));
|
|
$config = array(
|
|
'ConstructableObject' => array(
|
|
'constructor' => array(
|
|
'%$TestObject'
|
|
)
|
|
),
|
|
);
|
|
|
|
$injector->load($config);
|
|
$item = $injector->get('ConstructableObject');
|
|
$this->assertTrue($item->property instanceof ConstructableObject);
|
|
|
|
$this->assertInstanceOf('OtherTestObject', $item->property->property);
|
|
}
|
|
|
|
public function testNamedServices() {
|
|
$injector = new Injector();
|
|
$service = new stdClass();
|
|
|
|
$injector->registerNamedService('NamedService', $service);
|
|
$this->assertEquals($service, $injector->get('NamedService'));
|
|
}
|
|
|
|
public function testCreateConfiggedObjectWithCustomConstructorArgs() {
|
|
// need to make sure that even if the config defines some constructor params,
|
|
// that we take our passed in constructor args instead
|
|
$injector = new Injector(array('locator' => 'InjectorTestConfigLocator'));
|
|
|
|
$item = $injector->create('ConfigConstructor', 'othervalue');
|
|
$this->assertEquals($item->property, 'othervalue');
|
|
}
|
|
|
|
}
|
|
|
|
class InjectorTestConfigLocator extends SilverStripeServiceConfigurationLocator implements TestOnly {
|
|
public function locateConfigFor($name) {
|
|
if ($name == 'TestObject') {
|
|
return array('class' => 'ConstructableObject', 'constructor' => array('%$OtherTestObject'));
|
|
}
|
|
|
|
if ($name == 'ConfigConstructor') {
|
|
return array('class' => 'ConstructableObject', 'constructor' => array('value'));
|
|
}
|
|
|
|
return parent::locateConfigFor($name);
|
|
}
|
|
}
|
|
|
|
class ConstructableObject implements TestOnly {
|
|
public $property;
|
|
|
|
public function __construct($prop) {
|
|
$this->property = $prop;
|
|
}
|
|
}
|
|
|
|
class TestObject implements TestOnly {
|
|
|
|
public $sampleService;
|
|
|
|
public function setSomething($v) {
|
|
$this->sampleService = $v;
|
|
}
|
|
|
|
}
|
|
|
|
class OtherTestObject implements TestOnly {
|
|
|
|
private $sampleService;
|
|
|
|
public function setSampleService($s) {
|
|
$this->sampleService = $s;
|
|
}
|
|
|
|
public function s() {
|
|
return $this->sampleService;
|
|
}
|
|
|
|
}
|
|
|
|
class CircularOne implements TestOnly {
|
|
|
|
public $circularTwo;
|
|
|
|
}
|
|
|
|
class CircularTwo implements TestOnly {
|
|
|
|
public $circularOne;
|
|
|
|
public $otherVar;
|
|
|
|
public function __construct($value = null) {
|
|
$this->otherVar = $value;
|
|
}
|
|
}
|
|
|
|
class NeedsBothCirculars implements TestOnly{
|
|
|
|
public $circularOne;
|
|
public $circularTwo;
|
|
public $var;
|
|
|
|
}
|
|
|
|
class MyParentClass implements TestOnly {
|
|
public $one;
|
|
}
|
|
|
|
class MyChildClass extends MyParentClass implements TestOnly {
|
|
|
|
}
|
|
|
|
class DummyRequirements implements TestOnly {
|
|
|
|
public $backend;
|
|
|
|
public function __construct($backend) {
|
|
$this->backend = $backend;
|
|
}
|
|
|
|
public function setBackend($backend) {
|
|
$this->backend = $backend;
|
|
}
|
|
|
|
}
|
|
|
|
class OriginalRequirementsBackend implements TestOnly {
|
|
|
|
}
|
|
|
|
class NewRequirementsBackend implements TestOnly {
|
|
|
|
}
|
|
|
|
class TestStaticInjections implements TestOnly {
|
|
|
|
public $backend;
|
|
/** @config */
|
|
private static $dependencies = array(
|
|
'backend' => '%$NewRequirementsBackend'
|
|
);
|
|
|
|
}
|
|
|
|
/**
|
|
* An example object creator that uses the SilverStripe class(arguments) mechanism for
|
|
* creating new objects
|
|
*
|
|
* @see https://github.com/silverstripe/sapphire
|
|
*/
|
|
class SSObjectCreator extends InjectionCreator {
|
|
private $injector;
|
|
|
|
public function __construct($injector) {
|
|
$this->injector = $injector;
|
|
}
|
|
|
|
public function create($class, $params = array()) {
|
|
if (strpos($class, '(') === false) {
|
|
return parent::create($class, $params);
|
|
} else {
|
|
list($class, $params) = Object::parse_class_spec($class);
|
|
$params = $this->injector->convertServiceProperty($params);
|
|
return parent::create($class, $params);
|
|
}
|
|
}
|
|
}
|