diff --git a/control/injector/Injector.php b/control/injector/Injector.php index f8694dbe2..1e1343cdb 100644 --- a/control/injector/Injector.php +++ b/control/injector/Injector.php @@ -585,15 +585,52 @@ class Injector { $objtype = $asType ? $asType : get_class($object); $mapping = isset($this->injectMap[$objtype]) ? $this->injectMap[$objtype] : null; + $spec = empty($this->specs[$objtype]) ? array() : $this->specs[$objtype]; + // first off, set any properties defined in the service specification for this // object type - if (isset($this->specs[$objtype]) && isset($this->specs[$objtype]['properties'])) { + if(!empty($spec['properties']) && is_array($spec['properties'])) { foreach ($this->specs[$objtype]['properties'] as $key => $value) { $val = $this->convertServiceProperty($value); $this->setObjectProperty($object, $key, $val); } } + // Populate named methods + if (!empty($spec['calls']) && is_array($spec['calls'])) { + foreach ($spec['calls'] as $method) { + // Ignore any blank entries from the array; these may be left in due to config system limitations + if(!$method) continue; + + // Format validation + if(!is_array($method) || !isset($method[0]) || isset($method[2])) { + throw new \InvalidArgumentException( + "'calls' entries in service definition should be 1 or 2 element arrays." + ); + } + if(!is_string($method[0])) { + throw new \InvalidArgumentException("1st element of a 'calls' entry should be a string"); + } + if(isset($method[1]) && !is_array($method[1])) { + throw new \InvalidArgumentException("2nd element of a 'calls' entry should an arguments array"); + } + + // Check that the method exists and is callable + $objectMethod = array($object, $method[0]); + if (!is_callable($objectMethod)) { + throw new \InvalidArgumentException("'$method[0]' in 'calls' entry is not a public method"); + } + + // Call it + call_user_func_array( + $objectMethod, + $this->convertServiceProperty( + isset($method[1]) ? $method[1] : array() + ) + ); + } + } + // now, use any cached information about what properties this object type has // and set based on name resolution if (!$mapping) { diff --git a/docs/en/02_Developer_Guides/05_Extending/05_Injector.md b/docs/en/02_Developer_Guides/05_Extending/05_Injector.md index d17603362..56112fc61 100644 --- a/docs/en/02_Developer_Guides/05_Extending/05_Injector.md +++ b/docs/en/02_Developer_Guides/05_Extending/05_Injector.md @@ -111,6 +111,16 @@ Now the dependencies will be replaced with our configuration. echo ($object->textProperty == 'My Text Value'); // returns true; +As well as properties, method calls can also be specified: + + :::yml + Injector: + Logger: + class: Monolog\Logger + calls: + - [ pushHandler, [ %$DefaultHandler ] ] + + ## Factories Some services require non-trivial construction which means they must be created by a factory class. To do this, create diff --git a/tests/injector/InjectorTest.php b/tests/injector/InjectorTest.php index 71dbddf8c..3ba317828 100644 --- a/tests/injector/InjectorTest.php +++ b/tests/injector/InjectorTest.php @@ -619,6 +619,91 @@ class InjectorTest extends SapphireTest { $this->assertInstanceOf('TestObject', $injector->get('service')); } + public function testMethods() { + // do it again but have test object configured as a constructor dependency + $injector = new Injector(); + $config = array( + 'A' => array( + 'class' => 'TestObject', + ), + 'B' => array( + 'class' => 'TestObject', + ), + 'TestService' => array( + 'class' => 'TestObject', + 'calls' => array( + array('myMethod', array('%$A')), + array('myMethod', array('%$B')), + array('noArgMethod') + ) + ) + ); + + $injector->load($config); + $item = $injector->get('TestService'); + $this->assertTrue($item instanceof TestObject); + $this->assertEquals( + array($injector->get('A'), $injector->get('B'), 'noArgMethod called'), + $item->methodCalls + ); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testNonExistentMethods() { + $injector = new Injector(); + $config = array( + 'TestService' => array( + 'class' => 'TestObject', + 'calls' => array( + array('thisDoesntExist') + ) + ) + ); + + $injector->load($config); + $item = $injector->get('TestService'); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testProtectedMethods() { + $injector = new Injector(); + $config = array( + 'TestService' => array( + 'class' => 'TestObject', + 'calls' => array( + array('protectedMethod') + ) + ) + ); + + $injector->load($config); + $item = $injector->get('TestService'); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testTooManyArrayValues() { + $injector = new Injector(); + $config = array( + 'TestService' => array( + 'class' => 'TestObject', + 'calls' => array( + array('method', array('args'), 'what is this?') + ) + ) + ); + + $injector->load($config); + $item = $injector->get('TestService'); + } + + + /** * Test nesting of injector */ @@ -713,10 +798,23 @@ class TestObject implements TestOnly { public $sampleService; + public $methodCalls = array(); + public function setSomething($v) { $this->sampleService = $v; } + public function myMethod($arg) { + $this->methodCalls[] = $arg; + } + + public function noArgMethod() { + $this->methodCalls[] = 'noArgMethod called'; + } + + protected function protectedMethod() { + $this->methodCalls[] = 'protectedMethod called'; + } } class OtherTestObject implements TestOnly { @@ -848,4 +946,4 @@ class SSObjectCreator extends InjectionCreator { return parent::create($class, $params); } } -} +} \ No newline at end of file