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
use SilverStripe\Framework\Injector\Factory;
/**
* A class for creating new objects by the injector.
*
* @package framework
* @subpackage injector
*/
class InjectionCreator {
class InjectionCreator implements Factory {
/**
* @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()) {
public function create($class, array $params = array()) {
$reflector = new ReflectionClass($class);
if (count($params)) {
@ -23,4 +19,5 @@ class InjectionCreator {
return $reflector->newInstance();
}
}
}

View File

@ -1,9 +1,13 @@
<?php
require_once dirname(__FILE__) . '/InjectionCreator.php';
require_once dirname(__FILE__) . '/SilverStripeInjectionCreator.php';
require_once dirname(__FILE__) . '/ServiceConfigurationLocator.php';
require_once dirname(__FILE__) . '/SilverStripeServiceConfigurationLocator.php';
require_once FRAMEWORK_PATH . '/src/SilverStripe/Framework/Injector/Factory.php';
require_once __DIR__ . '/InjectionCreator.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
@ -71,6 +75,7 @@ require_once dirname(__FILE__) . '/SilverStripeServiceConfigurationLocator.php';
* // type
* // By default, singleton is assumed
*
* 'factory' => '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
*
*
* <code>
* class MyController extends Controller {
*
*
* private $permissionService;
*
*
* public setPermissionService($p) {
* $this->permissionService = $p;
* }
* }
* }
* </code>
*
*
* 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) {

View File

@ -1,22 +1,18 @@
<?php
use SilverStripe\Framework\Injector\Factory;
/**
* @package framework
* @subpackage injector
*/
class SilverStripeInjectionCreator implements Factory {
class SilverStripeInjectionCreator {
/**
*
* @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()) {
public function create($class, array $params = array()) {
$class = Object::getCustomClass($class);
$reflector = new ReflectionClass($class);
return $reflector->newInstanceArgs($params);
return $params ? $reflector->newInstanceArgs($params) : $reflector->newInstance();
}
}
}

View File

@ -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

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');
}
/**
* 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 {