ZF2, Zend Di and Controllers for fast SOA development
What is Zend\Di
Zend\Di
is a component introduced in early Beta versions of ZendFramework 2. Its job is to
provide you with instances of a requested object populated with all the dependencies required for it to work
correctly. If you're not familiar with it, you can read more about Zend\Di
on the
ZF2 Zend\Di manual pages and eventually try
out Ralphschindler's examples.
Why Zend\Di?
Zend\Di
has often been accused of being slow and complex to follow. It is if you don't have any
experience with it. I will try to make a simple example of correct usage of it, directly taking my examples
from how I develop with ZF2 daily. We will also cover the performance problem later in this blogpost.
The reason why I use Zend\Di despite of its slowness is development time and clean IOC. I am also quite experienced with it, so I learned to tame it and to take advantage of it over time, now it almost works as a linting tool for my code.
When working with complex application structures with dozens of different services, repositories, controllers, caches and more you tend to do some mistakes (caused by lazyness) that will penalize you on the long run. Such mistakes are things like avoiding IOC to speed up development, or using setters for your hard dependencies because you don't know how to retrieve all dependencies at instantiation time. Even by following best practices, you will often be slowed down by having to maintain all the factories that are responsible for generating your controllers! Here's how I do it.
Let's get started!
We will be developing a simple "greeting" action controller with a helloAction
. It will consume
a GreetingService, which produces greeting messages by fetching them from a given message container.
First, let's get a skeleton application to work. Please mind that we're going to work within the Application module provided by ZendSkeletonApplication, but you should apply these concepts only for your own modules!
~$ git clone git://github.com/zendframework/ZendSkeletonApplication.git
~$ cd ZendSkeletonApplication
~$ ./composer.phar install
For the lazy: If you just prefer to look at the code and run it without having to reproduce my example, you can just look at the already modified skeleton at Ocramius/ZendSkeletonApplication - branch demo/zf2-controllers-from-zend-di and see what I did in the diff.
You should now be able to browse to http://localhost/path-to-skeleton-application/
and see the
ZendSkeletonApplication default intro page.
The code
Here are our service, greeting container and controller: that's the core of our application. I will already start with IOC, since I don't think I need to explain why I like it (nor am I qualified to do so!), and you already have read this far.
Greetings repository:
A simple repository that fetches a random greeting message from an array.
<?php
// module/Application/src/Application/Repository/StaticGreetingRepository.php
namespace Application\Repository;
class StaticGreetingRepository
{
protected $availableGreetings = array('Hi', 'Hello', 'Hey', 'What\'s up');
/** @return string */
public function getRandomGreeting()
{
return $this->availableGreetings[array_rand($this->availableGreetings)];
}
}
Greetings service (consumes repository):
A service that assembles the entire message by picking a random greeting from a given repository and a provided name.
<?php
// module/Application/src/Application/Service/GreetingService.php
namespace Application\Service;
use Application\Repository\StaticGreetingRepository;
class GreetingService
{
protected $repository;
/** @var StaticGreetingRepository $repository */
public function __construct(StaticGreetingRepository $repository)
{
$this->repository = $repository;
}
/**
* this is an example method. It could perform operations such as discovering
* the gender of the given name to customize the reply
*
* @var string $name
* @return string
*/
public function greet($name)
{
return $this->repository->getRandomGreeting() . ' ' . $name . '!';
}
}
Greetings controller (consumes service):
A controller we use to collect a GET
request and return the message to the end user.
<?php
// module/Application/src/Application/Controller/GreetingController.php
namespace Application\Controller;
use Application\Service\GreetingService;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class GreetingController extends AbstractActionController
{
/**
* @var GreetingService
*/
protected $greetingService;
/**
* @var GreetingService $greetingService
*/
public function __construct(GreetingService $greetingService)
{
$this->greetingService = $greetingService;
}
public function helloAction()
{
$name = $this->getRequest()->getQuery('name', 'anonymous');
return new ViewModel(array('greeting' => $this->greetingService->greet($name)));
}
}
And then a view to see some output:
<?php
// module/Application/view/application/greeting/hello.phtml
echo '<h1>' . $this->escapeHtml($this->greeting) . '</h1>';
Wiring it together
To allow Zend\Di
to work correctly with our application, we now need to allow our application
to access the controller somehow. To do so, we have to define config.di.allowed_controllers
and
a route to allow access to our controller (and a couple of fixes required to inject inherited dependencies).
<?php
return array(
'di' => array(
'allowed_controllers' => array(
// this config is required, otherwise the MVC won't even attempt to ask Di for the controller!
'Application\Controller\GreetingController',
),
'instance' => array(
'preference' => array(
// these allow injecting correct EventManager and ServiceManager
// (taken from the main ServiceManager) into the controller,
// because Di doesn't know how to retrieve abstract types. These
// dependencies are inherited from Zend\Mvc\Controller\AbstractController
'Zend\EventManager\EventManagerInterface' => 'EventManager',
'Zend\ServiceManager\ServiceLocatorInterface' => 'ServiceManager',
),
),
),
'router' => array(
'routes' => array(
'hello' => array(
'type' => 'Zend\Mvc\Router\Http\Literal',
'options' => array(
'route' => '/hello',
'defaults' => array(
'controller' => 'Application\Controller\GreetingController',
'action' => 'hello',
),
),
),
),
),
// remaining config
);
Running it!
Seriously? Was that all? The answer is yes!
This will give you a basic example that renders a web page like the following:
Considerations
Ok, what did just happen? Zend\Di
recursively discovered all hard dependencies and built a
fully operational controller for us! And that with very clean and simple code.
Some may argue that this is "dark magic". It is not, it is just another way of wiring things together, and I am not suggesting it for your production environment nor as a definitive solution to solve all your dependency injection problems: just for development.
Performance issues
Careful! Performance impact of using Zend\Di
over a
ServiceManager factory is around 15%, and that overhead increases with the number and recursion of the
dependencies.
Enabling complex Di based modules such as
ocramius/zf-phpcr-odm may affect performance by an order of 200% or more!
But that is perfectly fine in a development environment, since we want to get working as fast as possible and keeping our code clean and simple. This is simply not possible in a context where dependencies continue to change because of architectural changes in your application. You must be free to do your decisions before freezing everything into a Service Factory!
If you want to remove the performance overhead and still keep the benefits of using Zend\Di, you can try my ocramius/ocra-di-compiler, which simply does the work you should do when you want to compile your Di container into a series of service factories/PHP closures.
Otherwise, simply do following in your configuration once you are sure your dependencies won't change much:
<?php
return array(
'controllers' => array(
'factories' => array(
'Application\Controller\GreetingController' => function($sm) {
return new \Application\Controller\GreetingController(
new \Application\Service\GreetingService(
new \Application\Repository\StaticGreetingRepository()
),
);
},
),
),
// remaining config
);
This basically removes all the overhead, but also removes the flexibility of Zend\Di
, since
you won't be able to swap the implementation of either the GreetingService
or the
StaticGreetingRepository
used to dispatch your request. I personally see this as one of the
last steps before shipping your code for production, since OcraDiCompiler can handle these operations for
you.
Note: Please also note that in this example, all dependency injection
are based on type hints of concret implementations. I cleaned up my code and invite you to check
Ocramius/ZendSkeletonApplication - demo/zf2-controllers-from-zend-di-cleanup
and see what I did in the
diff.
In this case I simply exchanged the type hints with abstract types (which allow more flexibility) and taught
Zend\Di
how to handle injections for them.
To check how I improved performance using ocramius/ocra-di-compiler
, please refer to branch
Ocramius/ZendSkeletonApplication - demo/zf2-controllers-from-zend-di-with-compiled-di
and to the related diff.
Conclusions
As I've stated from the beginning, this is a development process, not something you want to have happening
at runtime at every request. Zend\Di
is a very powerful tool, especially when it comes to
testing, exchenging deeply nested dependencies and keeping IOC as clean as possible. Use it wisely and it
may become your best ally while handling your usual impossible customer, but maybe this time without
screwing up as much code as usual ;-)