Zend Framework 2.0 Dependency Injection
Dearest Zend Framework and PHP Addict,
You know I always try to keep a close watch on the developments of Zend Framework 1.* and Zend Framework 2.0. Today I will blog about the Dependency Injection Component that is going to ship with ZF2.0.
What is dependency Injection?
You have been using dependency injection from the first time you started using classes. After this article you will have an ah-aaaah moment and you will finally be able to name the practice you have been doing all along. Almost each class has dependencies to other classes. You could instantiate these dependencies inside the class itself, making sure the dependency is always met, also avoiding the overhead of injecting it each time. While this seems a good idea in the beginning, it also tightly couples your application units. Also the quality is harder to guarantee because during unit testing your code has dependencies which are instantiated from inside the class to be tested. This makes it very hard to manipulate the state the dependency might have. The solution is to never instantiate the class from inside the class itself but from outside and let it be set as a parameter.
A good PHP resource on why you need dependency injection in PHP can be found at Ralph Schindlers blog article: Learning about Dependency Injection and PHP
What is a dependency Container?
Having to instantiate and pass the dependency all the time can be troublesome to say the least.
The first PHP solutions were to have a registry which would hold all the instantiated objects and only this registry would be tightly coupled in your architecture. This went a long way but still tightly coupled your architecture.
To remedy this people started looking into a good component which could really hold dependency definitions – a configuration which holds all classes / methods and their dependencies. And would also hold an instantiator, and container. The Instantiator would instantiate the objects for you according to the definition and a container would hold previous instantiated objects.
A good article written by Padraic Brady and published in 2008 pens down this idea and proposes a Zend Framework Dependency Injection Component, or simply Zend_Di: The Zend Framework Dependency Injection And Zend_Di
The Current Day: Zend\Di
Zend Framework 2.0 is currently under full development, several milestones have been completed. Probably the most important milestone of ZF2.0 is the MVC. To be fully compliant to current standards a good Dependency Injection (DI) component had to be developed.
_Why_ does ZF2 need DI for the MVC?
The reason is that it’s a good potential answer to the question of “how do controllers gain access to their dependencies?” In ZF1, the answer was either indirectly via Zend_Registry or the Bootstrap together with getInvokeArg()), or via direct instantiation within the controller. The latter is problematic as you can’t easily provide substitutions, and if the class instantiated needs to interact with resources — say, a web API, the database, etc. — this becomes difficult to mock when testing. In the former cases, you’re introducing undocumented dependencies in the object — looking simply at the controller’s API, you cannot easily guess what other objects it uses.
DI helps answer this. The prototype Zend Framework Core Team started building assumes that a DI container is composed into the front controller, and controllers are themselves defined in the DI container. When a controller is matched by the router, the front controller then pulls the matched controller from the DI container — along with all of its dependencies. This helps solve the initial question, but also answers another one: how to reduce the number of objects necessary for each given request. In ZF1, every single application resource is instantiated on every request, regardless of whether or not a controller uses it; the DI approach largely removes this, though at the expense of an in-memory graph of object definitions. However, the latter is usually smaller over-all, and easily cached in memory.
Ralph Schindler (Part of the Zend Framework Core Team) takes the lead on this component and listened to the community. He poured all those ideas in a component that can take prior configured definitions, detect dependencies at runtime and a tool for static dependencies analysis.
The approach that has been taken is that different environments will behave differently from a DI perspective. The solution is to consider anything but your local development machine production. Application in production should be stable from a code perspective and unchanging code means the deploy tool should be able to statically parse your code and create a definition file from it. This can be done automatically on each deploy. Once a definition file is ready – this will be in PHP format for maximum efficiency – your application doesn’t need to analyze your code on every request, thus gaining in huge performance benefits and without the hassle of always manually configuring all dependencies. Of course some dependencies will have to be manually configured, but Zend\Di offers tools for that.
Let’s take a closer look at the tools Zend\Di offers.
This class will be instantiated most of the time. It will be used to retrieve instances configured by a definition.
For this you will need to set the definition object or manually configure everything piece per piece.
$di = new Zend\Di\DependencyInjector(); $di->getInstanceManager()->setParameters( 'My\A', array( 'password' =>'bar', 'username' =>'foo', ) ); $c = $di->get('My\C');
In this example class C is instantiated. Class C has a dependency on class A, This is automatically behind the scenes detected by Zend\Di\Definition\RuntimeDefinition. Yet class A has constructor dependencies also, which can never be automatically injected so for this we need to set these parameters. This means that we need to aggregate all dependencies which cannot be automatically instantiated into a definition file. Even automatically detectable dependencies at runtime would benefit from a definition because compiling shouldn’t be done at runtime — it’s expensive. You will typically compile once, and then create a class extending ArrayDefinition that uses the results of compilation.
Creating a definition file asks a lot from a developer so it would be best that it could be automated as much as possible. For this a static compiler is implemented. Which has stated before should not be done at runtime during production.
$compiler = new Zend\Di\Definition\Compiler(); $compiler->addCodeScannerDirectory(new Zend\Code\Scanner\DirectoryScanner(__DIR__ . '/Game/')); $definition = $compiler->compile(); $di->setDefinition($definition); $commandParser = $di->get('Game\CommandParser');
$definition will be a complete Zend\Di\Definition\ArrayDefinition which accepts and outputs a PHP array based definition.
You can only have one definition at a time – hence the setDefinition and not an addDefinition api – so calling setDefinition() and passing an ArrayDefinition will result in no more Runtime lookups. If you want to combine the two strategies, you need to use an AggregateDefinition. An AggregateDefinition acts as a queue, querying each attached definition until it finds a match (or doesn’t!). As such, you would attach any compiled ArrayDefinition objects first, and a RuntimeDefinition object last.
Combinations of a definition and setParameters are also possible:
$di->setDefinition($definition); $di->getInstanceManager()->setParameters( 'Game\Grid', array( 'gridSizeX' => 2, 'gridSizeY' => 5, ) ); $commandParser = $di->get('Game\CommandParser');
Parameters set through options or through the setParameters method are not only used for the __construct method but also for the available setters. This makes your code a bit hard to understand because you have no idea which parameter is used for what method. So parameters are nice, but the current incarnation also allows specifying per-method configuration for parameters that should be called at instantation (i.e. setter injection). In fact, it’s the preferred method for specifying configuration even for the constructor.
$di->getInstanceManager()->setMethods( 'Game\Grid', array( '__construct' => array( 'gridSizeX' => 2, 'gridSizeY' => 5, ), 'setPosition' => array( 'x' => 1, 'y' => 3, ), ) ); $commandParser = $di->get('Game\CommandParser');
In the case where CommandParser needs to automatically accept a Grid object as a constructor parameter, you can even pass the constructor params for Grid directly to the get method of the main class:
$commandParser = $di->get( 'Game\CommandParser', array( 'grid' => array( 'gridSizeX' => 2, 'gridSizeY' => 5, ) ) );
The Injector will inspect the constructor params for CommandParser and if it doesn’t have a $grid param, than it will save the param for creating a params instance that might need it. In this case the Game\Grid $grid. This way you can pass params all the way down to the last dependency needed for instantiating the class you asked.
Or to make it even a bit more flexible you can even create aliasses and pass them as the second parameter.
$di->getInstanceManager()->addAlias( 'my-Grid', 'Game\Grid', array( 'gridSizeX' => 2, 'gridSizeY' => 5, ) ); $commandParser = $di->get('Game\CommandParser', array('grid' => 'my-Grid'));
Aliases are intended to make it simpler to specify dependencies and/or substitutions. As an example, I might alias Game\Grid to “my-Grid“, allowing me to call $di->get(’my-Grid’) and retrieve that class. This means that I could potentially configure the container differently, and get an object of a different class later — which is very useful for testing, or having different adapters for different environments.
So this is basically the standard usage of the Dependency Injector. Of course the DI is still BETA, and it shows.
Zend\Di leans heavily on Zend\Code\Scanner and here are still some caveats. At the time of writing the MethodScanner or ParameterScanner needs to be fixed to allow for the method signature:
public function __construct(Request $request, Response $response, Grid $grid, Inventory $personalInventory)
At the moment this signature breaks because of the start of the tokens for each Parameter.
It does however accept:
public function __construct(Request $request,Response $response,Grid $grid,Inventory $personalInventory)
As you can see the whitespaces between the parameters have been removed. Bottom line the Scanner should not be so error prone.
For my own testing I have forked ZF2 and fixed this issue in the branch https://github.com/NickBelhomme/zf2/tree/bugfix_tokentrim
As you can see all is still under major development, but that doesn’t mean you cannot play with it. So if you are interested in learning something about Dependency Injection and maybe even ZF2 in general I suggest you take a look at it.
I had a lot of fun inspecting the component and a lot of banging my head into the walls for comprehending the stuff described above. And honestly I couldn’t have completely grasped it without the excellent help and feedback of two special people deeply involved in this project: Matthew Weier O’Phinney and Ralph Schindler. Thank you both for the feedback which helped me in writing this blog post and spreading the knowledge.
A good code resource to mention is a forked and brought up to date repo with the current ZF state: https://github.com/NickBelhomme/ZF2ByExample
Sunny Greetings From Belgium and happy Zend Frameworking!