Michaël Gallego

This is my blog. What can you expect here? Well... Zend Framework 2, Amazon AWS...

Twitter

Google+

LinkedIn

Github

Last.fm

Some tips to write better Zend Framework 2 modules

While working on various Zend Framework 2 modules (currently, my biggest project is working on ZfrRest, which aims to provide a powerful REST module) with many very talented developers (Matthew Weier O’Phinney, Marco Pivetta…), I really learnt a lot of things about PHP and how to write better, safer and more flexible code.

This article is more targeted to ZF 2 beginners who would like to spread to the world their super-über-fantastic module. However, if you would like people to use it (and hence, make it better !), you really need to ship something that is good. I hope that those few advices (some are really obvious, but I see too many modules that do not respect even the simplest rules).

Once again thanks to the amazing Oleg, this article is available in Russian. This article is also available in Italian thanks to Stefano Corallo. Grazie mille!

There’s a module for that !

Before writing your module, check if somebody did not write it before ! Instead of doing your own module, just make the existing ones better by sending pull requests. You can check ZF2.modules and find for a module with keywords !

For more information about this topic, check Martin Shwalbe’s blog post about modules.

Follow conventions

Zend Framework 2, as any other modern frameworks, has strict code conventions. Of course, the framework respect PSR-0/1/2 conventions, and you should must too. But we also have some standard conventions about where should we put some files.

For instance, many modules are configurable. Once you have many options, it’s a good idea to centralize those options into an Options class (that extends Zend\Stdlib\AbstractOptions). It’s common practice to save all those files into an “Options” folder so that each options classes have their own namespaces. Of course, you could put those classes wherever you want, but as it is how we do it, it is much more easier for developers to read the code if you save those classes in predictable places.

Furthermore, I’ve seen many people that put a lot of files into the same folder just to avoid create new one. Seriously, just create new folders. It won’t hurt anyone and will simply help people to more easily discover the files. For instance, if you create a Paginator adapter, just mimic ZF 2 structure and save it into YourModule\Paginator\Adapter\MySuperAdapter, not into YourModule\Form\Adapter\MySuperAdapter, just because you’re lazy to create a new folder :-).

For example, see the DoctrineModule options classes.

Allow to configure everything that needs to be configurable

Modules are meant to be shared and reused by other people. And people don’t always have exactly the same needs as you are. Whenever a thing “could” be modified by other people, add options to allow to modify it. For instance, if your module generate some HTML page with some inline style that is copied from a CSS file shipped in the module… just set a way so that people can set the path to their own CSS file (this way they won’t need to manually tweak the CSS you ship in the module).

When you ship your module, you should ship two files into the config folder. One file called “module.config.php” that provides “standard” configuration and configuration that should not be modified by the user AND another file called “my_module.global.php.dist” that exposes all the options that can be overridable by the user (this file is typically copy/pasted to the config/autoload folder of your application).

A great example of that is the ZfcUser config file. As you can see, pretty much everything can be overridden, so the module is really flexible.

Set your hard dependencies in constructor, avoid initializers and setters/getters

The service locator of Zend Framework 2 really allows to cleanly handle complex dependencies, mocking… it’s really a powerful tool that is not that hard to use once you understand it. However, ZF 2 shipped with something called initializers. I’m pretty sure you’ve encountered them already. For instance, they are ServiceLocatorAwareInterface, or EventManagerAwareInterface… Whenever a class implements those interfaces, the service manager will automatically inject (through setters) the dependencies.

While this is nice, it suffers from one problem that can lead to a lot of pain and bad use from your module’s users. For instance, in ZfrRest, we have classes that are used to parse HTTP request/response object. All those parsers extends an abstract class called AbstractParser. You can see the class here.

As an example, here is the constructor :

public function __construct(DecoderPluginManager $pluginManager)
{
    $this->decoderPluginManager = $pluginManager;
}

Instead of the constructor, we could have written a setter/getter to set the DecoderPluginManager, and inject the dependency through the service manager or using an initializer. BUT this assumes that your user will effectively use the service manager to create your object.

Until the day someone create the parser like this:

$parser = new BodyParser();
$parser->parse($request);

And badaboom. He didn’t call setDecoderPluginManager nor didn’t create the instance through the service manager. The dependency is not set correctly and this code will throw an exception.

BECAUSE the DecoderPluginManager is an essential dependency of this object (it cannot work without this dependency), just ask to set the dependency directly through the constructor. So that people can’t use your object badly. Even if they want to.

Reduce your closure usage for factories

It’s common usage in ZF 2 to handle dependencies through the use of closures as factories in the getServiceConfig of your Module.php class. Instead, you’d better use explicit class factories, and set define the factories in the module.config.php file (in the service_manager key). This is slightly more efficient (because closures are instantiated, it’s faster to create a string than a closure), allow you to cache config file (closure are not cacheable), and remove the unreadeable Module.php with tons of closures.

return array(
    'service_manager' => array(
        'factories' => array(
            'MyModule\My\Service' => 'MyModule\Service\MyServiceFactory'
        )
    )
);

Use service manager as much as possible to put dependencies

This advice closely follows the one on “allow to configure everything”. Sometimes, people really want really weird things. Trust me. You sometimes ask yourself “why the hell do you want this feature ?”. But they want. Of course, you won’t add this feature to the core of your module because you still want to kick this guy’s ass for suggesting such a strange thing, but at least you must make it easy for him to override everything.

And with ZF 2 architecture, this is really simple : create most of your objects through the service manager. For instance, in ZfrRest, we add some listeners that provide useful features in REST context. The first way to do it is simply this:

public function onBootstrap(EventInterface $e)
{
    $application     = $e->getTarget();
    $serviceManager  = $application->getServiceManager();
    $eventManager    = $application->getEventManager();

    $eventManager->attach(new ZfrRest\Mvc\HttpExceptionListener());
}

However, think about this strange guy who want to also send a mail to his grand-mother whenever an exception is thrown. How can he do it currently ? Well… he can modify your module code, but this is not really good as whenever he will update your module, he will need to make the changes again.

Fortunately, using the Service Manager solves this:

public function onBootstrap(EventInterface $e)
{
    $application     = $e->getTarget();
    $serviceManager  = $application->getServiceManager();
    $eventManager    = $application->getEventManager();

    $eventManager->attach($serviceManager->get('ZfrRest\Mvc\HttpExceptionListener'));
}

Now, the listener is pulled from the service manager. Of course, ZfrRest has a default and sane implementation of this listener, by adding an invokable to the service manager config:

// in the module.config.php
return array(
    'service_manager' => array(
        'invokables' => array(
           'ZfrRest\Mvc\HttpExceptionListener' => 'ZfrRest\Mvc\HttpExceptionListener'
        )
    )
);

Now, if someone want to override the listener, he just needs to override the invokables in his own config file:

// in the module.config.php
return array(
    'service_manager' => array(
        'invokables' => array(
           'ZfrRest\Mvc\HttpExceptionListener' => 'MyModule\Mvc\SendBananaListener'
        )
    )
);

Write unit tests

Write unit tests for your module (yes, it’s boring). I’ve already use untested modules and it’s really pain in the ass. Whenever I updated it, my whole application failed because an upgrade to the module break something. This is even more important for modules that people want to use professionally. If they are untested, it’s nearly a no-go.

Once again, write unit tests for your module. Full stop.

Host your module on Packagist (for Composer use) and zf.modules

Many people use Composer to handle dependencies between modules. While you may hate it, just remember that many people love it. So please post your module to Packagist if you think it’s good enough.

Furthermore, you can add your module to ZF2.modules page.

Only set dependencies on what you require

Zend Framework 2 is a modular framework. This means that people don’t need to download the full framework to be able to use it. For instance, some people may be interested only about the filter and validator components (and using the Symfony 2 Mvc component). All of this thanks to Composer.

Of course, people will be angry if using your module means downloading the full ZF 2 framework. This is why instead of setting the whole framework as a dependency, just set the specific components you need. For instance, let’s say that your module uses the ZF 2 InputFilter component, nothing else. Just replace this in the composer.json of your module :

"require": {
    "php": ">=5.3.3",
    "zendframework/zendframework": "2.*"
}

by :

"require": {
    "php": ">=5.3.3",
    "zendframework/zend-inputfilter": "2.*"
}

Now, it will download ONLY the Zend Input Filter (well, not exactly, because the InputFilter component itself has some other dependencies, but this is not your problem and this is handled by composer, because each ZF 2 component has its own composer.json file !).

If you have any other advices, don’t hesitate !