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',
@ -163,14 +168,16 @@ class Injector {
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
* 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 {@link InjectionCreator} is used by default, which simply directly
* creates objects. This can be changed to use a different default creation
* method if desired.
*
* @var InjectionCreator
* Each individual component can also specify a custom factory to use by
* using the `factory` parameter.
*
* @var Factory
*/
protected $objectCreator;
@ -216,6 +223,15 @@ 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,7 +501,8 @@ 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

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

@ -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
the **permissions** property.
## Configuring objects managed by the dependency injector
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
* 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 {