diff --git a/control/injector/InjectionCreator.php b/control/injector/InjectionCreator.php index c521a68d1..e6021d8a9 100644 --- a/control/injector/InjectionCreator.php +++ b/control/injector/InjectionCreator.php @@ -1,20 +1,16 @@ newInstance(); } -} \ No newline at end of file + +} diff --git a/control/injector/Injector.php b/control/injector/Injector.php index b50c5ed8c..4dca8204f 100644 --- a/control/injector/Injector.php +++ b/control/injector/Injector.php @@ -1,9 +1,13 @@ 'FactoryService' // A factory service to use to create instances. * 'construct' => array( // properties to set at construction * 'scalar', * '%$BeanId', @@ -94,25 +99,25 @@ require_once dirname(__FILE__) . '/SilverStripeServiceConfigurationLocator.php'; * * In addition to specifying the bindings directly in the configuration, * you can simply create a publicly accessible property on the target - * class which will automatically be injected if the autoScanProperties + * class which will automatically be injected if the autoScanProperties * option is set to true. This means a class defined as - * + * * * class MyController extends Controller { - * + * * private $permissionService; - * + * * public setPermissionService($p) { * $this->permissionService = $p; - * } + * } * } * - * + * * will have setPermissionService called if - * + * * * Injector::inst()->setAutoScanProperties(true) is called and - * * A service named 'PermissionService' has been configured - * + * * A service named 'PermissionService' has been configured + * * @author marcus@silverstripe.com.au * @package framework * @subpackage injector @@ -161,16 +166,18 @@ class Injector { * @var boolean */ private $autoScanProperties = false; - + /** - * The object used to create new class instances - * - * Use a custom class here to change the way classes are created to use - * a custom creation method. By default the InjectionCreator class is used, - * which simply creates a new class via 'new', however this could be overridden - * to use, for example, SilverStripe's Object::create() method. + * The default factory used to create new instances. * - * @var InjectionCreator + * The {@link InjectionCreator} is used by default, which simply directly + * creates objects. This can be changed to use a different default creation + * method if desired. + * + * Each individual component can also specify a custom factory to use by + * using the `factory` parameter. + * + * @var Factory */ protected $objectCreator; @@ -190,7 +197,7 @@ class Injector { ); $this->autoProperties = array(); - + $creatorClass = isset($config['creator']) ? $config['creator'] : 'InjectionCreator'; $locatorClass = isset($config['locator']) ? $config['locator'] : 'ServiceConfigurationLocator'; @@ -215,7 +222,16 @@ class Injector { } return self::$instance; } - + + /** + * Sets the default global injector instance. + * + * @param Injector $instance + */ + public static function set_inst(Injector $instance) { + self::$instance = $instance; + } + /** * Indicate whether we auto scan injected objects for properties to set. * @@ -226,17 +242,16 @@ class Injector { } /** - * Sets the object to use for creating new objects + * Sets the default factory to use for creating new objects. * - * @param InjectionCreator $obj + * @param Factory $obj */ - public function setObjectCreator($obj) { + public function setObjectCreator(Factory $obj) { $this->objectCreator = $obj; } /** - * Accessor (for testing purposes) - * @return InjectionCreator + * @return Factory */ public function getObjectCreator() { return $this->objectCreator; @@ -486,8 +501,9 @@ class Injector { $constructorParams = $spec['constructor']; } - $object = $this->objectCreator->create($class, $constructorParams); - + $factory = isset($spec['factory']) ? $this->get($spec['factory']) : $this->getObjectCreator(); + $object = $factory->create($class, $constructorParams); + // figure out if we have a specific id set or not. In some cases, we might be instantiating objects // that we don't manage directly; we don't want to store these in the service cache below if (!$id) { diff --git a/control/injector/SilverStripeInjectionCreator.php b/control/injector/SilverStripeInjectionCreator.php index 809d61142..f4e454960 100644 --- a/control/injector/SilverStripeInjectionCreator.php +++ b/control/injector/SilverStripeInjectionCreator.php @@ -1,22 +1,18 @@ newInstanceArgs($params); + + return $params ? $reflector->newInstanceArgs($params) : $reflector->newInstance(); } -} \ No newline at end of file + +} diff --git a/docs/en/reference/injector.md b/docs/en/reference/injector.md index 98ce9c4b2..20b8b792b 100644 --- a/docs/en/reference/injector.md +++ b/docs/en/reference/injector.md @@ -76,8 +76,7 @@ The subsequent call returns the SAME object as the first call. In this case, on creation of the MyController object, the injector will automatically instantiate the PermissionService object and set it as -the **permissions** property. - +the **permissions** property. ## Configuring objects managed by the dependency injector @@ -90,6 +89,31 @@ Configuration can be specified for two areas of dependency management * Defining dependency overrides for individual classes * Injector managed 'services' +### Factories + +Some services require non-trivial construction which means they must be created by a factory class. To do this, create +a factory class which implements the `[api:SilverStripe\Framework\Injector\Factory]` interface. You can then specify +the `factory` key in the service definition, and the factory service will be used. + +An example using the `MyFactory` service to create instances of the `MyService` service is shown below: + + :::yml + Injector: + MyService: + factory: MyFactory + MyFactory: + class: MyFactoryImplementation + + :::php + class MyFactoryImplementation implements SilverStripe\Framework\Injector\Factory { + public function create($service, array $params = array()) { + return new MyServiceImplementation(); + } + } + + // Will use MyFactoryImplementation::create() to create the service instance. + $instance = Injector::inst()->get('MyService'); + ### Dependency overrides To override the **static $dependency;** declaration for a class, you could diff --git a/src/SilverStripe/Framework/Injector/Factory.php b/src/SilverStripe/Framework/Injector/Factory.php new file mode 100644 index 000000000..ffc61ddf3 --- /dev/null +++ b/src/SilverStripe/Framework/Injector/Factory.php @@ -0,0 +1,19 @@ +assertEquals($item->property, 'othervalue'); } + /** + * Tests creating a service with a custom factory. + */ + public function testCustomFactory() { + $injector = new Injector(array( + 'service' => array('factory' => 'factory', 'constructor' => array(1, 2, 3)) + )); + + $factory = $this->getMock('SilverStripe\\Framework\\Injector\\Factory'); + $factory + ->expects($this->once()) + ->method('create') + ->with($this->equalTo('service'), $this->equalTo(array(1, 2, 3))) + ->will($this->returnCallback(function($args) { + return new TestObject(); + })); + + $injector->registerService($factory, 'factory'); + + $this->assertInstanceOf('TestObject', $injector->get('service')); + } + } class InjectorTestConfigLocator extends SilverStripeServiceConfigurationLocator implements TestOnly { @@ -667,7 +689,7 @@ class SSObjectCreator extends InjectionCreator { $this->injector = $injector; } - public function create($class, $params = array()) { + public function create($class, array $params = array()) { if (strpos($class, '(') === false) { return parent::create($class, $params); } else {