Features
Modular applications
Zend Framework 2+ applications have a concept of modules, independent units that can provide configuration, services, and hooks into its MVC lifecycle. This functionality is provided by zend-modulemanager.
Expressive provides similar functionality by incorporating two packages within the default skeleton application:
- zendframework/zend-config-aggregator,
  which provides features for aggregating configuration from a variety of
  sources, including:
- PHP files globbed from the filesystem that return an array of configuration.
 - zend-config-compatible configuration files globbed from the filesystem.
 - Configuration provider classes; these are invokable classes which return an array of configuration.
 
 - zendframework/zend-component-installer,
  a Composer plugin that looks for an 
extra.zf.config-providerentry in a package to install, and, if found, adds an entry for that provider to theconfig/config.phpfile (if it uses zend-config-aggregator). 
These features allow you to install packages via composer and expose their configuration — which may include dependency information — to your application.
Making your application modular
When using the Expressive installer via the skeleton application, the first question asked is the installation type, which includes the options:
- Minimal (no default middleware, templates, or assets; configuration only)
 - Flat (flat source code structure; default selection)
 - Modular (modular source code structure; recommended)
 
We recommend choosing the "Modular" option from the outset.
If you do not, you can still create and use modules in your application; however, the initial "App" module will not be modular.
Module structure
Expressive does not force you to use any particular structure for your module; its only requirement is to expose default configuration using a "config provider", which is simply an invokable class that returns a configuration array.
We generally recommend that a module have a PSR-4
structure, and that the module contain a src/ directory at the minimum, along
with directories for other module-specific content, such as templates, tests, and
assets:
src/
  Acme/
    src/
      ConfigProvider.php
      Helper/
        AuthorizationHelper.php
      Middleware/
        VerifyUser.php
        VerifyUserFactory.php
    templates/
      verify-user.php
    test/
      Helper/
        AuthorizationHelperTest.php
      Middleware/
        VerifyUserTest.php
If you use the above structure, you would then add an entry in your
composer.json file to provide autoloading:
"autoload": {
    "psr-4": {
        "Acme\\": "src/Acme/src/"
    }
}
Don't forget to execute composer dump-autoload after making the change!
Creating and enabling a module
The only requirement for creating a module is that you define a "config provider", which is simply an invokable class that returns a configuration array.
Generally, a config provider will return dependency information, and module-specific configuration:
namespace Acme;
class ConfigProvider
{
    public function __invoke()
    {
        return [
            'dependencies' => $this->getDependencies(),
            'acme' => [
                'some-setting' => 'default value',
            ],
            'templates' => [
                'paths' => [
                    'acme' => [__DIR__ . '/../templates'],
                ],
            ]
        ];
    }
    public function getDependencies()
    {
        return [
            'invokables' => [
                Helper\AuthorizationHelper::class => Helper\AuthorizationHelper::class,
            ],
            'factories' => [
                Middleware\VerifyUser::class => Container\VerifyUserFactory::class,
            ],
        ];
    }
}
You would then add the config provider to the top (or towards the top) of your
config/config.php:
$aggregator = new ConfigAggregator([
    Acme\ConfigProvider::class,
    /* ... */
This approach allows your config/autoload/* files to take precedence over the
module configuration, allowing you to override the values.
Caching configuration
In order to provide configuration caching, two things must occur:
- First, you must define a 
config_cache_enabledkey in your configuration somewhere. - Second, you must pass a second argument to the 
ConfigManager, the location of the cache file to use. 
The config_cache_enabled key can be defined in any of your configuration
providers, including the autoloaded configuration files. We recommend defining
them in two locations:
config/autoload/global.phpshould define the value totrue, as the production setting.config/autoload/local.phpshould also define the setting, and use a value appropriate to the current environment. In development, for instance, this would befalse.
// config/autoload/global.php
return [
    'config_cache_enabled' => true,
    /* ... */
];
// config/autoload/local.php
return [
    'config_cache_enabled' => false, // <- development!
    /* ... */
];
You would then alter your config/config.php file to add the second argument.
The following example builds on the previous, and demonstrates having the
AppConfig entry enabled. The configuration will be cached to
data/config-cache.php in the application root:
$configManager = new ConfigManager([
    App\AppConfig::class,
    new PhpFileProvider('config/autoload/{{,*.}global,{,*.}local}.php'),
], 'data/config-cache.php');
When the configuration cache path is present, if the config_cache_enabled flag
is enabled, then configuration will be read from the cached configuration,
instead of parsing and merging the various configuration sources.
Tooling support
The skeleton ships with zend-expressive-tooling by default, which allows you to execute the following command in order to create a module skeleton, add and enable autoloading rules for it, and register it with your application:
$ composer expressive module:create {ModuleName}
We recommend using this tool when creating new modules.
Final notes
This approach may look simple, but it is flexible and powerful:
- You pass a list of config providers to the 
ConfigAggregatorconstructor. - Configuration is merged in the same order as it is passed, with later entries having precedence.
 - You can override module configuration using 
*.global.phpand*.local.phpfiles. - If cached config is found, 
ConfigAggregatordoes not iterate over provider list. 
For more details, please refer to the zend-config-aggregator documentation.
Found a mistake or want to contribute to the documentation? Edit this page on GitHub!