Merge pull request #2700 from ajshort/injector-factory

Injector Factory
This commit is contained in:
Ingo Schommer 2014-02-03 16:50:15 -08:00
commit 457ec9446b
8 changed files with 133 additions and 59 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';
@ -201,8 +208,6 @@ class Injector {
if ($config) {
$this->load($config);
}
self::$instance = $this;
}
/**
@ -217,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.
*
@ -228,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;
@ -488,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

@ -84,8 +84,10 @@ require_once 'control/injector/Injector.php';
// Initialise the dependency injector as soon as possible, as it is
// subsequently used by some of the following code
$default_options = array('locator' => 'SilverStripeServiceConfigurationLocator');
Injector::inst($default_options);
$injector = new Injector(array('locator' => 'SilverStripeServiceConfigurationLocator'));
$injector->registerService(Config::inst());
Injector::set_inst($injector);
///////////////////////////////////////////////////////////////////////////////
// MANIFEST

View File

@ -550,7 +550,7 @@ abstract class Object {
Config::inst()->update($class, 'extensions', array($extension));
Config::inst()->extraConfigSourcesChanged($class);
Injector::inst()->unregisterAllObjects();
Injector::inst()->unregisterNamedObject($class);
// load statics now for DataObject classes
if(is_subclass_of($class, 'DataObject')) {

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 {