The BoxUK-DI library enables you to easily handle dependency injection between components using annotations and type-hinting, similar to Guice and Spring.
- PHP 5.3+
- Addendum 0.4.0+
To include the library just include the bootstrap file in your application.
include '../path/to/boxuk-di/lib/bootstrap.php';
The main part of the library is the DI container, a simple example to fetch a class...
$libraryLoader = $injector->getClass( 'LibraryLoader' );
By default, the injector will create a new class each time it's asked for it. Its constructor parameters will be analysed to check types so that any dependencies can be injected into the new object (if these dependencies don't exist they will be created). It's methods will also be checked on creation for any that have been annotated for method injection (see below)
By default, new objects will be instantiated for each requested class. To use objects in a different scope, singleton for example, just annotate them as so:
/** * @ScopeSingleton */ class LibraryLoader { }
The singleton scope is defined by the annotation @ScopeSingleton, and lasts for the lifetime of a request. Objects annotated as singletons will only be created once by the injector, and then the same object is returned on each subsequent request.
/** * @ScopeSingleton */ class MyClass {}
The session scope will store objects in the users session, and these will be available for the lifetime of the session. This can be used for things like a logged in user, a shopping cart, etc... To give a class session scope just annotate it as so.
/** * @ScopeSession */ class MyShoppingCart {}
To use this scope you will first need to define a class that implements the SessionHandler interface and bind it to this name (SessionHandler).
If your class is implementing an interface which it is type hinted for then you can specify this by the above annotation:
/** * @ScopeSingleton(implements="SomeInterface") */ class MyClass implements SomeInterface, AnotherInterface {}
Then requests for that interface will return this singleton:
$oInjector->getClass( 'SomeInterface' );
To add 3rd party singletons to the injector just go through the getScope() method.
$this->injector->getScope( 'singleton' )->set( $doctrineManager );
You can also annotate methods to be injected:
/** * @InjectMethod */ public function setClassLoader( ClassLoader $oClassLoader ) {}
NB: When doing method injection there is no constraint on the name of the method, or the number of parameters injected.
If your method requires tweaking the injected parameter types then you can specify these with another annotation:
/** * @InjectMethod * @InjectParam(variable="class", class="ModuleRegistry") */ public function setSomething( SomeInterface $class ) { // will receive a ModuleRegistry }
This can also be used for constructors.
The final type of injection available is property injection. This can be used for public and private properties.
/** * @InjectProperty * @var SomeClass */ private $someClass;
The type of object injected is specified by the @var PHPDoc.
When doing method injection, the injector will ascend up the inheritance chain to also inject methods in parent classes. If you override a method in your child class though this method will only be injected (if annotated) in the child class.
When checking a class for scope, the injector will ascend up the inheritance chain and stop at the first scope annotation it encounters.
To ignore any scope annotations you can force fetching a new instance of the class you want:
$oInjector->getNewClass( 'SomeClass' );
The one requirement of the injector is that type hinting or @InjectParam annotations need to be used to identify dependencies, so only classes can be dependencies. This makes a clean seperation between class dependencies and class configuration. For classes created with the injector you will not be able to pass in strings or arrays to the constructor. You can think of this as...
- Objects are dependencies
- Anything else is configuration
So you will need to remove any configuration from your constructors and injected methods, this will be moved to initialisation time for your class:
$class = $injector->getClass( 'MyClass' ); $class->initialise( $port, array( 'some', 'values' ) );
NB: initialise() here is just an arbitrary method on the class being created.
So, your class has been injected with all it's dependencies, but what if you want to create more objects inside your class? Well just ask for the injector as one of your dependencies:
private $injector;
public function __construct( BoxUK\Inject\Injector $injector ) {
$this->injector = $injector;
}
private function myMethod() {
$class = $this->injector->getClass( 'SomethingElse' );
}
Don't use the injector as a service locator though inside your class, always specify your dependencies to be injected at construct time.
The injector also provides an inject() method which can be used to do method injection and property injection on arbitrary objects. These objects can have been created elsewhere but the injector will scan them for dependencies to inject.
$injector->inject( $someObject );
The easiest way to create an injector is to use the Helper class.
$helper = new BoxUK\Inject\Helper(); $injector = $helper->getInjector();
When you create the helper you can pass in a Config object. This example shows a config object generated from an .ini file.
$config = new BoxUK\Inject\Config\IniFile(); $config->initFromFile( 'path/to/file.ini' ); $helper = new BoxUK\Inject\Helper( $config );
The injector will be all set up and ready to go. There are also methods to create reflectors and caches.
The second part of the library, which the injector is built on is the reflector. You can use this class to access reflection and annotation information on classes.
$reflector = $helper->getReflector();
Reflection can be slow, so for your applications production mode it's reccomended to use the BoxUK\Reflect\Caching reflector instead. You can get this through configuration.
boxuk.reflector = caching
The complete list of configuration options is as follows:
Setting | Values | Default |
---|---|---|
boxuk.reflector | standard, caching | standard |
boxuk.reflector.cache | file, memcache, apc | file |
boxuk.reflector.filecache.dir | (path to cache directory) | (sys_get_temp_dir()) |
boxuk.reflector.filecache.filename | (name of cache file) | $CLASS.cache |
boxuk.reflector.memcache.host | (memcache host) | localhost |
boxuk.reflector.memcache.port | (memcache port) | 11211 |
boxuk.reflector.memcache.key | (memcache key) | $CLASS |
boxuk.reflector.apc.key | (APC key) | $CLASS |
($CLASS means the fully qualified name of the class concerned)
You can unit test these classes using:
phing test