Note: This post has been superseded. Check out the Part 1 and Part 2. The post you are reading now is outdated, and there is a much better way of doing this, in my opinion.
In Zend Framework 1, it was difficult to follow best practices when it came to writing testable code. Sure, you could make testable models, but once you need those models in a controller, what do you do? Zend Framework 2 makes it much easier. In this post, I'll cover the basics of injecting a model into a controller.
The main goal here is to be able to wire up and configure your application from the highest level possible. Constructor injection + inversion of control makes it easy to determine which classes are dependent on other classes. The Getting Started guide uses the ServiceManager in the Controller to pull in the model, which creates "soft dependencies", so you can't completely tell which classes depend on other ones unless you look at the code on the lower levels. For actual testable/maintainable code, avoid this as much as possible.
- Basic understanding of what we are trying to accomplish, namely, dependency injection using the ServiceManager as an inversion of control container
- Looked through/tested ZF2's Skeleton Application and Getting Started guide.
- Cursory knowledge of closures/callbacks
Zend Framework 2's ServiceManager allows you to programmatically configure your dependencies. You should already be at least vaguely familiar with this from the ZF2 Getting Started guide. The Application's ServiceManager is responsible for creating services. Internally, when tasked with creating a service with a particular name, "Building\Controller\Building" for example, it runs canCreate("Building\Controller\Building"), which checks all of your aggregated configs. So one of the places it checks by default is the array returned by the method getControllerConfig() in Module.php. This is where we can add the factory closure for the model that the controller needs access to.
Creating controllers is actually handled by the ControllerManager, a (sub)subclass of the main ServiceManager, and $usePeeringServiceManagers is set to false, which is why here we need $sm->getServiceLocator()->get() instead of just $sm->get(); it needs to retrieve the main ServiceManager to have access to the rest of the application's services.
It is important to note that if the name of controller configuration (in this case Building\Controller\Building) is listed here, it cannot be defined somewhere else as well. For example, if it is in the $config['controllers']['invokables'] section in your module.config.php, the ServiceManager will try to 'invoke' it, that is, construct it with no arguments, and will fail. Check for this situation now.
Let's create the Building model as a simple class with no dependencies for now.
Since there are no dependencies or other required constructor arguments, we can define Building\Model\Building as an invokable in Module.php. The getServiceConfig() method is one of the aggregated configs that the ServiceManager checks to see which services it can create, and you should recognize it from the Getting Started guide.
That's it. Control has now been inverted. You are injecting a model into a controller using the ServiceManager, and all of your wiring and configuration is in one place. It is possible to separate the configuration into multiple files, which might be advisable once your wiring starts getting complicated. The factories can be their own classes which implement 'FactoryInterface' instead of closures defined in the config array.
If you want to try it out now, you'll need to replace the line in the controller
Then just go to http://localhost/building, and if you set your routing up, it will just be an empty page with the line echo'd in the indexAction. Hint: don't forget the getConfig() and getAutoloaderConfig() methods in your Module.php
Check out Part 2 to learn how to inject a dependency where each instance needs to be distinct and have its own configuration.
Please leave me some comments, especially if you see something I did wrong, could do better, needs clarification, etc.