Skip to content
nyeholt edited this page Oct 18, 2011 · 19 revisions

Overview

Injector provides some basic PHP dependency injection. Many of the ideas are loosely based on a very small subset of what spring provides, based on the following key ideas

  • No XML or external configuration files. All configuration done via PHP arrays
  • Automatic binding of dependencies for configured objects, as well as objects _instantiate()_ed via the injector or explicitly _inject()_ed.
  • Late binding of configured dependencies (ie dependent objects aren't created until needed)
  • Overriding the provider of a named 'service'. For example, calling $injector->get('ServiceName') might normally be configured to return an instance of ServiceName, but can be overridden to return an instance of another type
  • Allow for configuration to bind complex lists of references via the #$ServiceName syntax.
  • Allow for specification of 'prototype' class specifications

Examples

For full working examples, see the testcases

Given the following

NOTE Automatic resolution of public properties to injection points is disabled by default now. To enable it, just call $injector->setAutoScanProperties(true);.

	// assuming these classes are defined in their own files

	class SomeService {
		public $remoteService;
	}

	class RemoteService {

	}

	class LocalService {
		public $remoteService;
		
		private $someService;
	
		public function setSomeService($s) {
			$this->someService = $s;
		}
	}

	$config = array(
		array('src' => '/path/to/SomeService.php'),
		array('src' => '/path/to/other/RemoteService.php'),
		array('src' => '/path/to/LocalService.php'),
	);

	$injector = new Injector($config);

Each of the given service classes will have their appropriate services automatically linked up. Additional configuration can be loaded later by explicitly calling $injector->load($config);

If your classes have already been included by some other means, all you need for configuration is

	$config = array('SomeService', 'AnotherService');
	$injector = new Injector($config);

The service registry shouldn't be thought of as a straight singleton container. You can provide any number of named services, all of which can be instances of the same class.

	$config = array(
		'SomeService' => 'SomeService',
		'AlternativeService' => 'SomeService', 
	);

	// which is the equivalent of 
	$config = array(
		array('id' => 'SomeService', 'class' => 'SomeService'),
		array('id' => 'AlternativeService', 'class' => 'SomeService'),
	);

And use it to automatically instantiate objects and automatically inject them with dependencies

	class MyObject {
		public $someService;
	}

	$config = array('SomeService', 'AnotherService');
	$injector = new Injector($config);
	$injector->setAutoScanProperties(true);

	$myobject = $injector->instantiate('MyObject');
	$myobject->someService; // this is now the singleton instance of someService
	
	// calling instantiate again creates a completely new instance of MyObject
	$myobject = $injector->instantiate('MyObject');
	

Additionally, you can tell the injector what to inject for a particular class's properties

	class MyObject {
		public $someService;
	}

	$config = array('AnotherService');
	$injector = new Injector($config);
	$injector->setInjectMapping('MyObject', 'someService', 'AnotherService');

	$myobject = $injector->instantiate('MyObject');
	$myobject->someService; // this is now the singleton instance of AnotherService

And provide hints for the injector using the injections static. This is an alternative to using autoScanProperties

	class MyObject {
		public $someService;

		public static $injections = array(
			'someService'	=> 'AnotherService'
		);
	}

	$config = array('AnotherService');
	$injector = new Injector($config);

	$myobject = $injector->get('MyObject');
	$myobject->someService; // this is now the singleton instance of AnotherService

Managed classes can be preconfigured to act as singletons, or as prototype definitions, whereby each call to 'get' returns a new instance of the type.

	class OtherService {
	}

	$config = array(
		array('class' => 'OtherService', 'type' => 'prototype'),
	);

	$injector = new Injector($config);
	$obj1 = $injector->get('OtherService');
	$obj2 = $injector->get('OtherService');

	// obj1 and obj2 are now completely different objects
	$obj1 !== $obj2;

Another one of the more useful features of dependency injection - overriding the default implementation for a named service

	class SomeService {
		
	}

	
	class LocalService {
		public $someService;
	}

	class OtherService {

	}

	$config = array(
		array('class' => 'SomeService'),
		array('class' => 'OtherService', 'id' => 'SomeService'),
		array('class' => 'LocalService'),
	);

	$injector = new Injector($config);

In this case, LocalService is written to the interface of SomeService. However, via configuration, we've changed it to use OtherService instead.

Note that as part of the spec for a given service, you can specify a 'priority' level, which means that a service can only be overridden if the subsequently loaded service has a higher priority value.

Lastly, you can provide configuration properties to managed objects. As well as scalar values, you can pass in lists, normal php objects, and references to other managed objects. In this way, you can bind arbitrary objects to each other.

	$config = array(
		array('class' => 'TestServiceForProxy', 'id' => 'TestService'),
		array('class' => 'TestAspectBean', 'id' => 'PreCallTest'),
		array('class' => 'TestAspectBean', 'id' => 'PostCallTest'),
		array(
			'src' => dirname(dirname(dirname(__FILE__))).'/services/AopProxyService.php',
			'id' => 'TestService',
			'properties' => array(
				'proxied' => '\#$TestService',
				'beforeCall' => array(
					'calculate' => '\#$PreCallTest',
				),
				'afterCall' => array(
					'calculate' => '\#$PostCallTest',
				)
			)
		),
	);

In this case, all #$Names (\ characters are there for escaping data) are resolved to appropriate beans, and linked as expected (see the TestAopProxy for the complete code).

Clone this wiki locally