jurian sluiman

{
Wissel naar

Using Zend Framework service managers in your application

Zend Framework 2 uses a ServiceManager component (in short, SM) to easily apply inversion of control. I notice there are good resources about the background of service managers (I recommend this blog post from Evan or this post from Reese Wilson) but many people still have problems to tune the SM to their needs. In this post I will try to explain the reason why the framework uses multiple service managers and how you can use these. I address the following topics:

  1. What are the different service managers?
  2. For what reason are different managers used?
  3. How does the service locator relate to the service manager?
  4. How can you define services for all those service managers?
  5. How can you retrieve services from one manager inside a second one?

Attach a view strategy for specific modules

There have been some questions around in the IRC channel and on Twitter how you could enable the Json view strategy for only a specific module in Zend Framework 2. Because I was requiring this feature too in the near future, I started exploring the code behind Zend\View and its strategies, to make this happen.

In fact the code is really simple, but like others who struggled with it, it took a while before I got to the result. If you're not interested in the working, simply copy the example code below to your module class and it should work.

namespace MyModule;

use Zend\ModuleManager\Feature;
use Zend\EventManager\EventInterface;
use Zend\Mvc\MvcEvent;

class Module implements
    Feature\BootstrapListenerInterface
{
    public function onBootstrap(EventInterface $e)
    {
        $app = $e->getApplication();
        $em  = $app->getEventManager()->getSharedManager();
        $sm  = $app->getServiceManager();

        $em->attach(__NAMESPACE__, MvcEvent::EVENT_DISPATCH, function($e) use ($sm) {
            $strategy = $sm->get('ViewJsonStrategy');
            $view     = $sm->get('ViewManager')->getView();
            $strategy->attach($view->getEventManager());
        });
    }
}

In this example, I start injecting the JsonStrategy only when controllers inside my module are dispatched. Therefore I listen to the "dispatch" event only for event managers having the identifier equal to my root namespace. Then I am sure every MyModule\Controller\SomeController will cause the event to fire, but any other AnotherModule\Controller\SomeController will not.

The strategy is retrieved from the service locator, where it's directly injected with a Json renderer too (so we don't need to care about that). The service manager has also a registered ViewManager, which is a Zend\Mvc\View\Http\ViewManager, connecting the MVC parts of the framework to the Zend\View component. This manager is obviously aware of a Zend\View\View responsible for rendering view scripts and so on. The view has its own event manager, triggering events to get a renderer and inject responses into the Response object. This is where the JsonStrategy wants to listen for, so the strategy attaches some listeners to the event manager of the view.

It took some time to figure this out, but hopefully others will benefit from this article and do not get stuck in exploring the MVC layers of Zend Framework 2.

Auto detect user locale with Zend\Http\Request headers and ext/intl

With the recent beta5 release of Zend Framework 2, a completely new Zend\I18n component is shipped. The component is (without any misunderstanding about the work to get this done) a kind-of helper layer on top of ext/intl, a php extension for all kind of i18n tasks.

Since this Zend\I18n release I was thinking about using the Accept-Language header a browser sends with a request. When there is no Accept-Language header, or all the accepted languages are not in a list of supported languages, a default locale is set. It is really useful here to use the Zend\Http\Header\AcceptLanguage object here, as it returns a Zend\Stdlib\PriorityQueue. You can iterate the queue, having the best-choice language first until you get to the least-best match for a language.

To make this happen, I created a listener in a module for the bootstrap event, to register a default locale as fast as possible. For example the Module class of your Application module:

namespace Application;

use Locale;
use Zend\EventManager\EventInterface;
use Zend\ModuleManager\Feature;

class Module implements
    Feature\BootstrapListenerInterface
{
    public function onBootstrap(Event $e)
    {
        $default   = 'en';
        $supported = array('en-GB', 'en-US', 'en', 'nl-NL', 'nl');
        $app       = $e->getApplication();
        $headers   = $app->getRequest()->getHeaders();

        if ($headers->has('Accept-Language')) {
            $locales = $headers->get('Accept-Language')->getPrioritized();

            // Loop through all locales, highest priority first
            foreach ($locales as $locale) {
                if (!!($match = Locale::lookup($supported, $locale))) {
                    // The locale is one of our supported list
                    Locale::setDefault($match);
                    break;
                }
            }

            if (!$match) {
                // Nothing from the supported list is a match
                Locale::setDefault($default);
            }
        } else {
            Locale::setDefault($default);
        }
    }
}

Perhaps later on I will update it to a decent listener in a separate class. I could then use a factory to inject a default locale and a list of supported locales from the configuration. But for now, it is simple and "good enough".

Use 3rd party modules in Zend Framework 2

The release of the first RC (release candidate) of Zend Framework 2 is getting close. One last beta (beta5) and then the RC will be announced! With the current pace of modules spawning on GitHub, I think it is a good idea to give some insights in how you can use 3rd party modules. In this blog post I will focus on MVC modules: modules with routes pointing to controllers and view scripts for rendering.

Because using a 3rd party MVC module does not mean you are enforced to follow their routing scheme, use their view scripts or use the predefined forms, I will explain how you can modify those options to your needs. In short, this post contains three different topics:

  1. Change a route
  2. Change a view script
  3. Change a form

To give you a concrete example, I will use the ZfcUser module as use case, currently available on GitHub and one of the best examples of a generic 3rd party MVC module. It provides domain models (a User), controllers (for login/logout etcetera) and views. There are also some forms for ZfcUser, to be able to login and register.

Use the Gedmo Doctrine Extensions in Zend Framework 2

Since October 2011 I maintained a small module for the Gedmo Doctrine extensions. These extensions were quite useful and with the Doctrine modules from Marco Pivetta (Ocramius) it was fairly easy to build a module configuring and autoloading the Gedmo extensions.

However, the Zend Framework changed drastically a couple of days back: a service manager, Composer integration and many more features which are not relevant for this blog post. This change made my module unneccessary and because maintaining it is unneccessary as well, I will drop the module.

When you use Composer, it is easy to integrate the extensions in your application. First you need to create a dependency on the extensions. If you haven't a composer.json file in your project, create one with the following contents:

{
  "require": {
    "gedmo/doctrine-extensions": "dev-master"
  }
}

Then you need to fetch composer and resolve the dependencies. This is done by the command (on Linux and OS X, you need to Google for the Windows commands): wget http://getcomposer.org/composer.phar && php composer.phar install. If you have already a composer.json file and just want to update, add the gedmo/doctrine-extensions requirement and run php composer.phar update.

The second part is to register the Gedmo annotations to the Doctrine annotations registry. This should just happen anywhere, for example in the init() function of your Application module. You need to import the registry:

use Doctrine\Common\Annotations\AnnotationRegistry;

Then you add this in your init() method:

$namespace = 'Gedmo\Mapping\Annotation';
$lib = 'vendor/gedmo/doctrine-extensions/lib';
AnnotationRegistry::registerAutoloadNamespace($namespace, $lib);

This is almost everything you need to do. Make sure you include the autoload.php file inside the vendor directory, because that file makes sure the Gedmo files are loaded properly. That's all, you don't need my module to integrate the Gedmo DoctrineExtensions in your Zend Framework 2 project anymore!

Update (configuration):

As Mike pointed out in the comments below, you still need some configuration per module to get your entities connected to the Gedmo listeners. This is something you have to do yourself. As an example, the ModuleName module registers its entities to Doctrine like this, together with a configuration option to load the Gedmo tree listener: 

return array(
    'di' => array(
        'instance' => array(
            // Set Doctrine annotations in driver chain
            // orm_driver_chain is an alias for DoctrineORMModule\Doctrine\ORM\DriverChain
            'orm_driver_chain' => array(
                'parameters' => array(
                    'drivers' => array(
                        'a_unique_key_here' => array(
                            'class'     => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
                            'namespace' => 'ModuleName\Entity',
                            'paths'     => array(__DIR__ . '/../src/ModuleName/Entity')
                        ),
                    ),
                ),
            ),

            // Set Gedmo tree subscriber
            'orm_evm' => array(
                'parameters' => array(
                    'opts' => array(
                        'subscribers' => array('Gedmo\Tree\TreeListener')
                    )
                )
            ),
        ),
    ),
);

Keep in mind to replace ModuleName by your module name and the a_unique_key_here as well (lowercase, underscore separated is generally a good idea).

< Previous | 1 | 2 | 3 | 4 | 5 | Next >