Factories for translator loaders

Many components in Zend Framework 2 following the adapter or plugin pattern, make use of the AbstractPluginManager. The abstract plugin manager is a service locator implementation which allows to define services as invokable or with factories. Many of these plugin managers are loaded automatically (see for example my earlier post about service managers). However, the manager for loaders of Zend\I18n\Translator is a stranger in our midst.

As of an IRC conversation today on #zftalk I, with a few others, found out how the plugin manager of Zend\I18n\Translator works. On the outside, it is an implementation of the abstract plugin manager. But if you look more closely, the plugin manager has no "parent" service locator. That means, if you have defined a reposity or service you need inside a loader, you don't have access to it!

In this blog post I will show how to fix this with an example loader, a database loader based on the EntityManager of Doctrine.

First, we have a Loader implementation:

<?php
namespace MyModule\Translator\Loader;

use Doctrine\ORM\EntityManager;
use Zend\I18n\Translator\Loader\RemoteLoaderInterface;

class DoctrineLoader implements RemoteLoaderInterface
{
   protected $em;

   public function __construct(EntityManager $em)
   {
       $this->em = $em;
   }

   /**
    * {@inheritdocs}
    */
   public function load($locale, $textDomain)
   {
       // Use $this->em to query the database
   }
   }

With the loader in place, we need a factory to instantiate this loader:

<?php
namespace MyModule\Service;

use Zend\ServiceManager\FactoryInterface;
use MyModule\Translator\Loader\DoctrineLoader;

class DoctrineLoaderFactory implements FactoryInterface
{
   /**
    * {@inheritdocs}
    */
   public function createService(ServiceLocatorInterface $serviceLocator)
   {
       /**
        * $serviceLocator is the translator plugin manager, to get into the
        * root service locator we need the getServiceLocator() call
        *
        * @see http://juriansluiman.nl/en/article/120
        */
       $sm = $serviceLocator->getServiceLocator();
       $em = $sm->get('Doctrine\ORM\EntityManager');

       return new DoctrineLoader($em);
   }
   }

As I said, the above should be standard work if you know how to handle dependency injection and to wire dependencies with factory classes of Zend\ServiceManager. The point I speak about in the introduction is to "fix" the plugin manager of the translator: if you don't fix the plugin manager, the call getServiceLocator() returns NULL!

This fix injects the root service manager from the application into the plugin manager of the Translator object. This must be done during bootstrap, so it's quite straightforward to do this in the Module class. Furthermore, because developers are getting used to the get*Config() methods in the Module class, I named the configuration of the translator plugin loader getTranslatorConfig().

<?php
namespace MyModule;

use Zend\ServiceManager\Config;

class Module
{
    public function getTranslatorConfig()
    {
        return array(
            'factories' => array(
                'MyModule\Translator\Loader\DoctrineLoader' => 'MyModule\Service\DoctrineLoaderFactory',
            ),
        );
    }

    public function onBootstrap($e)
    {
        $sm = $e->getApplication->getServiceManager();
        $this->configureTranslator($sm);
    }

    public function configureTranslator($sm)
    {
        $plugins = $sm->get('translator')->getPluginManager();
        $plugins->setServiceLocator($sm);

        $config  = new Config($this->getTranslatorConfig());
        $config->configureServiceManager($plugins);
    }
    }

Copy this code and your custom translation loader works!