There is a module for that
Contributor
Hounddog
@mshwalbe
what is a Module
list of what a module is
What can Modules do?
list of what a module can do
modules.zendframework.com
Currently over 300 modules
Future of modules.zendframework.com
Please check modules.zendframework.com before writing a new Module!
Keep the ``init()`` and ``onBootstrap()`` methods lightweight.
Be conservative with the actions you perform in the init() and onBootstrap() methods of your Module class. These methods are run for every page request, and should not perform anything heavy. As a rule of thumb, registering event listeners is an appropriate task to perform in these methods. Such lightweight tasks will generally not have a measurable impact on the performance of your application, even with many modules enabled. It is considered bad practice to utilize these methods for setting up or configuring instances of application resources such as a database connection, application logger, or mailer. Tasks such as these are better served through the ServiceManager capabilities of Zend Framework 2.
Do not perform writes within a module.
Do not perform writes within a module. You should never code your module to perform or expect any writes within the module’s directory. Once installed, the files within a module’s directory should always match the distribution verbatim. Any user-provided configuration should be performed via overrides in the Application module or via application-level configuration files. Any other required filesystem writes should be performed in some writeable path that is outside of the module’s directory.
Use a vendor prefix for module names
Utilize a vendor prefix for module names. To avoid module naming conflicts, you are encouraged to prefix your module namespace with a vendor prefix. As an example, the (incomplete) developer tools module distributed by Zend is named “ZendDeveloperTools” instead of simply “DeveloperTools”
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
return array(
'service_manager' => array(
'factories' => array(
'MyModule\My\Service' => 'MyModule\Service\MyServiceFactory'
)
)
);
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.
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'
)
)
);
travis ci
- Create a .travis.yml file
- Enable Github hook to run tests automatically
language: php
php:
- "5.3"
before_script:
- cd ..
- git clone git://github.com/zendframework/ZendSkeletonApplication.git
- cd ZendSkeletonApplication
- php composer.phar self-update
- php composer.phar install --dev
- cp -r ../EdpGithub module/EdpGithub
- cd module/EdpGithub
- wget http://cs.sensiolabs.org/get/php-cs-fixer.phar
script:
- phpunit
- php coverage-checker.php clover.xml 76
- output=
$(php php-cs-fixer.phar fix -v --dry-run --level=psr2 .);
if [[ $output ]]; then while read -r line;
do echo -e "\e[00;31m$line\e[00m";
done <<< "$output"; false; fi;
Custom Module
Config:
'view_manager' => array(
'template_path_stack' => array(
__DIR__ . '/../view',
),
),
Folder Structure:
view/
index/
index/
index.phtml //Overrides the Original index.phtml
Override a Controller/Service
Controllers and services should be registered with the servicemanager so they can be overriden by the key
Original Module
'controllers' => array(
'invokables' => array(
'App\Ctrl\Index' => 'App\Controller\IndexController',
),
),
Custom Module
'controllers' => array(
'invokables' => array(
'App\Ctrl\Index' => 'MyModule\Controller\IndexController',
),
),
Just by assigning my Custom controller to the Key Application\Controller\Index i have now replaced it. Same goes for the services or anything else that was configured using the service configuration
Extending Forms in ZfcUser
public function onBootstrap($e)
{
$events = $e->getApplication()->getEventManager()->getSharedManager();
$events->attach('ZfcUser\Form\Register','init', function($e) {
$form = $e->getTarget();
// Do what you please with the form instance ($form)
});
$events->attach('ZfcUser\Form\RegisterFilter','init', function($e) {
$filter = $e->getTarget();
// Do what you please with the filter instance ($filter)
});
}
I am taking this as an example as there is a nice way of extending forms in ZfcUser just by using an event.
Just place the following code as also explained in the wiki into your Module.php
As you can see you can attach anything to the formobject here and modify it to do exactly what you require
Use Composer
{
"name": "zf-module/my-module",
"type": "library",
"description": "Example Module",
"license": "MIT",
"authors": [
{
"name": "John Doe",
"email": "john@doe.com"
}
],
"require": {
"php": ">=5.3.3",
}
}
Only set required dependencies
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 :
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 !
Do not download the full ZF2 Framework
"require": {
"php": ">=5.3.3",
"zendframework/zendframework": "2.*"
}
Only require the componentes you need
"require": {
"php": ">=5.3.3",
"zendframework/zend-inputfilter": "2.*"
}
You can nearly override anything with in a module
return array(
'modules' => array(
'OriginalModule',
'MyModule',//this will override OriginalModule with any custom configurations you require
),
'module_listener_options' => array(
'config_glob_paths' => array(
'config/autoload/{,*.}{global,local}.php',
),
'module_paths' => array(
'./module',
'./vendor',
),
),
);
<aside class="notes">from views to controller over services and mappers
Create your own module which can extend/modify the original module. All you need to do is register it after the original Module in application.config.php</aside>
Create the same view structure as the OriginalModule and place your custom templates there.
Original Module
Config:
'view_manager' => array(
'template_path_stack' => array(
__DIR__ . '/../view',
),
),
Folder Structure:
view/
index/
index/
index.phtml
Generate Classmap
$ php path/to/zf2/bin/classmap_generator.php -w
Creating class file map for library in 'project/library'...
Wrote classmap file to 'project/library/autoload_classmap.php'
Generated Classmap
// Generated by ZF2's ./bin/classmap_generator.php
return array(
'Namespace\Module' => __DIR__ . '/Module.php',
'Namespace\Controller\IndexController' =>
__DIR__ . '/src/Namespace/Controller/IndexController.php',
);
Include Classmap in Module.php
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\ClassMapAutoloader' => array(
__DIR__ . '/autoload_classmap.php',
),
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
Generate Template Map
$ php path/to/zf2/bin/templatemap_generator.php -w
Creating template file map for library in 'project/library'...
Wrote templatemap file to 'project/library/template_map.php'
Include Templatemap in Module config
'view_manager' => array(
'template_path_stack' => array(
__DIR__ . '/../view',
),
'template_map' => include __DIR__ . '../template_map.php',
),
Thank you
Contributor
Hounddog
@mshwalbe