NEW: Allow specifying a factory to use for creating services.

A service factory can be used for creating instances where a non-trivial
construction process is required. This is done by adding a `factory`
key to the service definition.
This commit is contained in:
Andrew Short 2014-02-03 11:30:22 +11:00
parent b7b041b435
commit 2f817ba177
6 changed files with 128 additions and 54 deletions

View File

@ -1,20 +1,16 @@
<?php <?php
use SilverStripe\Framework\Injector\Factory;
/** /**
* A class for creating new objects by the injector. * A class for creating new objects by the injector.
* *
* @package framework * @package framework
* @subpackage injector * @subpackage injector
*/ */
class InjectionCreator { class InjectionCreator implements Factory {
/** public function create($class, array $params = array()) {
* @param string $object
* A string representation of the class to create
* @param array $params
* An array of parameters to be passed to the constructor
*/
public function create($class, $params = array()) {
$reflector = new ReflectionClass($class); $reflector = new ReflectionClass($class);
if (count($params)) { if (count($params)) {
@ -23,4 +19,5 @@ class InjectionCreator {
return $reflector->newInstance(); return $reflector->newInstance();
} }
} }

View File

@ -1,9 +1,13 @@
<?php <?php
require_once dirname(__FILE__) . '/InjectionCreator.php'; require_once FRAMEWORK_PATH . '/src/SilverStripe/Framework/Injector/Factory.php';
require_once dirname(__FILE__) . '/SilverStripeInjectionCreator.php';
require_once dirname(__FILE__) . '/ServiceConfigurationLocator.php'; require_once __DIR__ . '/InjectionCreator.php';
require_once dirname(__FILE__) . '/SilverStripeServiceConfigurationLocator.php'; require_once __DIR__ . '/SilverStripeInjectionCreator.php';
require_once __DIR__ . '/ServiceConfigurationLocator.php';
require_once __DIR__ . '/SilverStripeServiceConfigurationLocator.php';
use SilverStripe\Framework\Injector\Factory;
/** /**
* A simple injection manager that manages creating objects and injecting * A simple injection manager that manages creating objects and injecting
@ -71,6 +75,7 @@ require_once dirname(__FILE__) . '/SilverStripeServiceConfigurationLocator.php';
* // type * // type
* // By default, singleton is assumed * // By default, singleton is assumed
* *
* 'factory' => 'FactoryService' // A factory service to use to create instances.
* 'construct' => array( // properties to set at construction * 'construct' => array( // properties to set at construction
* 'scalar', * 'scalar',
* '%$BeanId', * '%$BeanId',
@ -163,14 +168,16 @@ class Injector {
private $autoScanProperties = false; private $autoScanProperties = false;
/** /**
* The object used to create new class instances * The default factory used to create new instances.
* *
* Use a custom class here to change the way classes are created to use * The {@link InjectionCreator} is used by default, which simply directly
* a custom creation method. By default the InjectionCreator class is used, * creates objects. This can be changed to use a different default creation
* which simply creates a new class via 'new', however this could be overridden * method if desired.
* to use, for example, SilverStripe's Object::create() method.
* *
* @var InjectionCreator * Each individual component can also specify a custom factory to use by
* using the `factory` parameter.
*
* @var Factory
*/ */
protected $objectCreator; protected $objectCreator;
@ -216,6 +223,15 @@ class Injector {
return self::$instance; 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. * 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; $this->objectCreator = $obj;
} }
/** /**
* Accessor (for testing purposes) * @return Factory
* @return InjectionCreator
*/ */
public function getObjectCreator() { public function getObjectCreator() {
return $this->objectCreator; return $this->objectCreator;
@ -486,7 +501,8 @@ class Injector {
$constructorParams = $spec['constructor']; $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 // 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 // that we don't manage directly; we don't want to store these in the service cache below

View File

@ -1,22 +1,18 @@
<?php <?php
use SilverStripe\Framework\Injector\Factory;
/** /**
* @package framework * @package framework
* @subpackage injector * @subpackage injector
*/ */
class SilverStripeInjectionCreator implements Factory {
class SilverStripeInjectionCreator { public function create($class, array $params = array()) {
/**
*
* @param string $object
* A string representation of the class to create
* @param array $params
* An array of parameters to be passed to the constructor
*/
public function create($class, $params = array()) {
$class = Object::getCustomClass($class); $class = Object::getCustomClass($class);
$reflector = new ReflectionClass($class); $reflector = new ReflectionClass($class);
return $reflector->newInstanceArgs($params); return $params ? $reflector->newInstanceArgs($params) : $reflector->newInstance();
} }
} }

View File

@ -78,7 +78,6 @@ In this case, on creation of the MyController object, the injector will
automatically instantiate the PermissionService object and set it as automatically instantiate the PermissionService object and set it as
the **permissions** property. the **permissions** property.
## Configuring objects managed by the dependency injector ## Configuring objects managed by the dependency injector
The above declarative style of dependency management would cover a large The above declarative style of dependency management would cover a large
@ -90,6 +89,31 @@ Configuration can be specified for two areas of dependency management
* Defining dependency overrides for individual classes * Defining dependency overrides for individual classes
* Injector managed 'services' * 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 ### Dependency overrides
To override the **static $dependency;** declaration for a class, you could To override the **static $dependency;** declaration for a class, you could

View File

@ -0,0 +1,19 @@
<?php
namespace SilverStripe\Framework\Injector;
/**
* A factory which is used for creating service instances.
*/
interface Factory {
/**
* Creates a new service instance.
*
* @param string $service The class name of the service.
* @param array $params The constructor parameters.
* @return object The created service instances.
*/
public function create($service, array $params = array());
}

View File

@ -541,6 +541,28 @@ class InjectorTest extends SapphireTest {
$this->assertEquals($item->property, 'othervalue'); $this->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 { class InjectorTestConfigLocator extends SilverStripeServiceConfigurationLocator implements TestOnly {
@ -667,7 +689,7 @@ class SSObjectCreator extends InjectionCreator {
$this->injector = $injector; $this->injector = $injector;
} }
public function create($class, $params = array()) { public function create($class, array $params = array()) {
if (strpos($class, '(') === false) { if (strpos($class, '(') === false) {
return parent::create($class, $params); return parent::create($class, $params);
} else { } else {