Injector

The Injector class is the central manager of inter-class dependencies in SilverStripe. It offers developers the ability to declare the dependencies a class type has, or to change the nature of the dependencies defined by other developers.

Some of the goals of dependency injection are:

  • Simplified instantiation of objects
  • Providing a uniform way of declaring and managing inter-object dependencies
  • Making class dependencies configurable
  • Simplifying the process of overriding or replacing core behaviour
  • Improve testability of code
  • Promoting abstraction of logic

The following sums up the simplest usage of the Injector it creates a new object of type MyClassName through create

	$object = Injector::inst()->create('MyClassName');

Configuration API by developers.

mysite/_config/app.yml

	Injector:
	  MyClassName:
	    class: MyBetterClassName
	$object = Injector::inst()->create('MyClassName');
	$object2 = Injector::inst()->create('MyClassName');

	echo $object !== $object2;

	// returns true;

The Injector API can be used for the singleton pattern through get(). Subsequent calls to get return the same object instance as the first call.

	// sets up MyClassName as a singleton
	$object = Injector::inst()->get('MyClassName');
	$object2 = Injector::inst()->get('MyClassName');

	echo ($object === $object2);

	// returns true;

The Injector API can be used to define the types of $dependencies that an object requires.

	<?php

	class MyController extends Controller {
	
		// both of these properties will be automatically
		// set by the injector on object creation
		public $permissions;
		public $textProperty;
	
		// we declare the types for each of the properties on the object. Anything we pass in via the Injector API must
		// match these data types.
		static $dependencies = array(
			'textProperty'		=> 'a string value',
			'permissions'		=> '%$PermissionService',
		);
	}
	$object = Injector::inst()->get('MyController');
	
	echo ($object->permissions instanceof PermissionService);
	// returns true;

	echo (is_string($object->textProperty));
	// returns true;

mysite/_config/app.yml

	Injector:
	  PermissionService:
	    class: MyCustomPermissionService
	  MyController
	    properties:
	      textProperty: 'My Text Value'
	$object = Injector::inst()->get('MyController');
	
	echo ($object->permissions instanceof MyCustomPermissionService);
	// returns true;

	echo ($object->textProperty == 'My Text Value');
	// returns true;

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

mysite/_config/app.yml

	Injector:
	  MyService:
	    factory: MyFactory
	<?php

	class MyFactory 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');

To override the $dependency declaration for a class, define the following configuration file.

mysite/_config/app.yml

	MyController:
	  dependencies:
		textProperty: a string value
		permissions: %$PermissionService

Simple dependencies can be specified by the $dependencies, but more complex configurations are possible by specifying constructor arguments, or by specifying more complex properties such as lists.

These more complex configurations are defined in Injector configuration blocks and are read by the Injector at runtime.

Assuming a class structure such as

	<?php

	class RestrictivePermissionService {
		private $database;

		public function setDatabase($d) {	
			$this->database = $d;
		}
	}
	
	class MySQLDatabase {
		private $username;
		private $password;
		
		public function __construct($username, $password) {
			$this->username = $username;
			$this->password = $password;
		}
	}
	name: MyController
	MyController:
	  dependencies:
	    permissions: %$PermissionService
	Injector:
	  PermissionService:
	    class: RestrictivePermissionService
	    properties:
	      database: %$MySQLDatabase
	  MySQLDatabase
	    constructor:
	      0: 'dbusername'
	      1: 'dbpassword'
	// sets up ClassName as a singleton
	$controller = Injector::inst()->get('MyController');
  • Create an object of type MyController
  • Look through the dependencies and call get('PermissionService')
  • Load the configuration for PermissionService, and create an object of type RestrictivePermissionService
  • Look at the properties to be injected and look for the config for MySQLDatabase
  • Create a MySQLDatabase class, passing dbusername and dbpassword as the parameters to the constructor.

Testing with Injector

In situations where injector states must be temporarily overridden, it is possible to create nested Injector instances which may be later discarded, reverting the application to the original state. This is done through nest and unnest.

This is useful when writing test cases, as certain services may be necessary to override for a single method call.

	// Setup default service
	Injector::inst()->registerService(new LiveService(), 'ServiceName');

	// Test substitute service temporarily
	Injector::nest();

	Injector::inst()->registerService(new TestingService(), 'ServiceName');
	$service = Injector::inst()->get('ServiceName');
	// ... do something with $service

	// revert changes
	Injector::unnest();

API Documentation