Skip to content

Commit

Permalink
NEW: Allow specifying a factory to use for creating services.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ajshort committed Feb 3, 2014
1 parent b7b041b commit 2f817ba
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 54 deletions.
15 changes: 6 additions & 9 deletions control/injector/InjectionCreator.php
Original file line number Diff line number Diff line change
@@ -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)) {
Expand All @@ -23,4 +19,5 @@ public function create($class, $params = array()) {

return $reflector->newInstance();
}
}

}
76 changes: 46 additions & 30 deletions control/injector/Injector.php
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -71,6 +75,7 @@
* // 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',
Expand All @@ -94,25 +99,25 @@
*
* 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 [email protected]
* @package framework
* @subpackage injector
Expand Down Expand Up @@ -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.
*
* 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 InjectionCreator
* @var Factory
*/
protected $objectCreator;

Expand All @@ -190,7 +197,7 @@ public function __construct($config = null) {
);

$this->autoProperties = array();


$creatorClass = isset($config['creator']) ? $config['creator'] : 'InjectionCreator';
$locatorClass = isset($config['locator']) ? $config['locator'] : 'ServiceConfigurationLocator';
Expand All @@ -215,7 +222,16 @@ public static function inst($config=null) {
}
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.
*
Expand All @@ -226,17 +242,16 @@ public function setAutoScanProperties($val) {
}

/**
* 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;
Expand Down Expand Up @@ -486,8 +501,9 @@ protected function instantiate($spec, $id=null, $type = null) {
$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) {
Expand Down
20 changes: 8 additions & 12 deletions control/injector/SilverStripeInjectionCreator.php
Original file line number Diff line number Diff line change
@@ -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();
}
}

}
28 changes: 26 additions & 2 deletions docs/en/reference/injector.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
19 changes: 19 additions & 0 deletions src/SilverStripe/Framework/Injector/Factory.php
Original file line number Diff line number Diff line change
@@ -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());

}
24 changes: 23 additions & 1 deletion tests/injector/InjectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,28 @@ public function testCreateConfiggedObjectWithCustomConstructorArgs() {
$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 {
Expand Down Expand Up @@ -667,7 +689,7 @@ public function __construct($injector) {
$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 {
Expand Down

0 comments on commit 2f817ba

Please sign in to comment.