Now that Zend Framework 2 (ZF2) has been released for more than 6 months and most of the early issues have been fixed I decided to switch from ZF1 to ZF2 for my latest project.
ZF2’s MVC layer has been completely rewritten and is now event driven. ZF2 also adds functionality to handle dependency injection (DI) allowing for a more modular structure by removing hard coded dependencies. All those new changes represent a bit of a learning curve for anyone getting started with ZF2. If DI is new to you the Zend DI Quickstart is a good place to familiarize yourself with the concept.
This post will explain how to set up dependency injection on top of the ZF2 Skeleton Application.
In this simple example we will assume that the User controller depends on having an instance of the User object. Further the User object needs to consume an instance of Zend\Log\Logger. In the first step we simply need to set up the constructors of class User and class UserController to take the Logger object instance and the User object instance as arguments and assign the objects to a class property.
module/User/src/User/Model/User.php
<?php
namespace User\Model;
use Zend\Log\Logger;
class User {
protected $logger;
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
...
}
module/User/src/User/Controller/UserController.php
<?php
namespace User\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use User\Model\User;
class UserController extends AbstractActionController {
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
...
}
Next we need to set up the controller config and the service manager config for DI. To retrieve objects from the service manager the factory pattern is implemented. The individual factory configuration sections are closures which instantiate, configure and return the requested objects. As you can see the objects we defined as dependencies in the first step are fetched from the service manager and passed to the constructors when instantiating the objects.
module/User/Module.php
<?php
namespace User;
class Module
{
public function getConfig()
{
return include __DIR__ . '/config/module.config.php';
}
public function getAutoloaderConfig()
{
...
}
public function getServiceConfig()
{
return array(
'factories' => array(
'User' => function($sm)
{
$logger = $sm->get('Logger');
$user = new Model\User($logger);
return $user;
},
'Logger' => function($sm)
{
$config = $sm->get('config');
$logger = new \Zend\Log\Logger;
$writer = new \Zend\Log\Writer\Stream($config['log']['file']);
$logger->addWriter($writer);
return $logger;
},
)
);
}
public function getControllerConfig()
{
return array('factories' => array(
'User\Controller\User' => function($cm)
{
$sm = $cm->getServiceLocator();
$user = $sm->get('User');
$controller = new Controller\UserController($user);
return $controller;
}
));
}
}
A better place to place the Logger object factory configuration is in the Module.php of the Application module. In this example it is placed in the Module.php of the User module for illustration purposes.
If you read the Zend Framework 2 tutorial you probably configured your User controller as an invokable in your module.config.php. If this is the case you need to remove this section, otherwise the service manager is not used to instantiate the UserController object and dependency injection won’t work.
module/User/config/module.config.php
'controllers' => array(
'invokables' => array( // REMOVE ME!!!
'User\Controller\User' => 'User\Controller\UserController'
),
...