Injecting ZF2 Service Manager into Doctrine Entities

Making ZF2 services available within Doctrine entities is not as trivial as with objects that are created via the ZF2 factory methods. The problem is that Doctrine Entities are not generated via regular ZF2 factory methods but by the Doctrine Entity Manager. Nevertheless it’s possible to inject dependencies into Doctrine entities.

Doctrine’s event manager offers a postLoad event which is called after the entity has been loaded. We can add a new listener to this event during our regular bootstrapping process.
Continue reading “Injecting ZF2 Service Manager into Doctrine Entities”

Injecting ZF2 Service Manager into Doctrine Entities

ZF2 Service Manager Aware Objects

In one of my previous posts I explained how to set up Dependency Injection in Zend Framework 2. Even though this approach is highly flexible it doesn’t always fit your project needs. Sometimes it is more suitable to fetch object instances from the service manager directly within the object. This post will explain how to implement this.

ZF2 automatically calls the setServiceManager method for objects implementing the ServiceManagerAwareInterface interface when instanciating these objects via the service manager. To set this up 2 steps are necessary:

  1. The class which needs to have access to the service manager needs to implement ServiceManagerAwareInterface
  2. The service manager needs to have the factory methods for the object that consumes the service manager and for the objects which are to be instanciated by the service manager

To implement the ServiceManagerAwareInterface the class needs to simply have a setServiceManager method which takes a ServiceManager object as an argument. This class needs to populate a property which gives other functions access to the service manager.

module/User/src/User/Model/User.php

<?php
namespace User\Model;

use Zend\ServiceManager\ServiceManager;
use Zend\ServiceManager\ServiceManagerAwareInterface;

abstract class User implements ServiceManagerAwareInterface {
    protected $serviceManager;

    /*
     * Populate service manager property
     */
    public function setServiceManager(ServiceManager $serviceManager)
    {
        $this->serviceManager = $serviceManager;
    }

    /*
     * Do some foo
     */
    protected function foo()
    {
        $foo = $this->serviceManager->get('Foo');
        $foo->bar();
    }
}

Now when creating instances of objects which implement the ServiceManagerAwareInterface using service manager factory methods, the service manager will be automatically made available.

module/User/Module.php

<?php
namespace User;

class Module
{
    ...

    public function getServiceConfig()
    {
        return array(
            'factories' => array(
                'User' => function($sm)
                {
                    $user = new Model\User();
                    return $user;
                },
                'Foo' => function($sm)
                {
                    $foo = new \Foo();
                    return $foo;                    
                },
            )
        );
    }

    ...
}
ZF2 Service Manager Aware Objects

ZF2 Dependency Injection

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'
    ),
    ...
ZF2 Dependency Injection

Zend JSON Server

I implemented Zend JSON RPC Server today. The documentation on the Zend website left a lot of room for guesswork and none of the online resources I could find provided the complete picture. Here a step by step guide on how to set it up.

Most users probably use this API to perform JS calls from their front-end to their own back-end. For this use case you need 3 components.

  • An API controller
  • A class to handle the API calls
  • and some JS code to perform the call

The API controller (application/controllers/ApiController.php):

<?php
/*
 * Controller for JSON API calls
 *
 * API methods are defined in library/Test/Api.php
 */
class ApiController extends Zend_Controller_Action
{
    private $server;
    
    /*
     * Initilaize
     */
    public function init()
    {
        // Initialize Zend_Json_Server
        $this->server = new Zend_Json_Server();
        $this->server->setClass('Test_Api');

        // Disable layout
        $this->_helper->layout->disableLayout();
    }

    /*
     * Handle the API request
     */
    public function indexAction()
    {
        $request = $this->getRequest();

        // Return the service mapping description for GET requests
        if ($request->isGet()) {
            $controller = $request->getControllerName();
            $module = $request->getModuleName();

            // Set the API url and envelope
            $this->server
                ->setTarget('/' . $module . '/' . $controller)
                ->setEnvelope(Zend_Json_Server_Smd::ENV_JSONRPC_2);
         
            // Return the SMD to the client
            header('Content-Type: application/json');
            echo $this->server->getServiceMap();
        } 
    
        // Call the API
        else {
            // Handle the request
            $this->server->handle();
        }
    }
}

This basically returns the RPC service mapping description for GET requests and dispatches POST requests to the class which handles the API calls.

The class which handles the API calls (library/Test/Api.php):

<?php
/*
 * JSON API for requests to /api/
 */
class Test_Api {
    /*
     * @param int $bar
     */
    public function foo($bar) {
        return $bar;
    }

    // Add more functions here
}

Make sure the have the Test namespace registered in your application.ini and your library path points to the correct place.

includePaths.library = APPLICATION_PATH "/../library"
autoloaderNamespaces[] = "Test_"

For the frontend JS you need to download Douglas Crockford’s JSON-js library. Copy this to public/js/libs/json2.js.

In whatever view script you want to use the API add:

$this->inlineScript()->appendFile('/js/libs/json2.js'); 

You can also add this to the layout if you want to be able to access the API on every page.

Once you got this in place you can call the API from your JS:

/*
 * JS API Test 
 */
function JsApi() {
    /*
     * Test call
     */
    this.testApi = function() {
        // Request parameters
        var request = {};
        request.method = 'foo';
        request.params = { 'bar': 'baz' };
        request.jsonrpc = '2.0';
        
        // Perform API call
        $.post(
            '/api/',
            JSON.stringify(request), 
            function(response) {
                
                if (!response.result.length) {
                    // API returned nothing; do something
                    return; 
                }

                // Process the response
                $.each(response.result, function () {
                    // Do something
                });
            }
        );
    }

}

$(document).ready(function() {
    // Perform a test call
    var jsApi = new JsApi();
    jsApi.testApi();
});

This relies on the jQuery library. If you are using other JS libraries you need to adjust this accordingly.

Zend JSON Server