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!