VGTech is a blog where the developers and devops of Norways most visited website share code and tricks of the trade… Read more



Are you brilliant? We're hiring. Read more

Inheriting configuration in Zend Framework 2 applications

PHP

When working on Zend Framework 2 applications you might come across situations where you need to differentiate the application configuration for the different application environments, be it development, staging, testing and/or production. This can be cache TTL‘s, Memcached hosts, Redis hosts, debugging levels and more.

Instead of copy/pasting the complete configuration across multiple “environment”-configuration files or having switches in the code, like for instance:

Show code
if ($_SERVER['APPLICATION_ENV'] === 'development' || 
    $_SERVER['APPLICATION_ENV'] === 'testing']) {
    // ...
}

We wanted to use inheritance so that for instance the staging environment would inherit the complete production environment, with the exception of some caching configuration which we want to override in the staging configuration.

This post will explain how we solved this problem in one of our latest Zend Framework 2 applications.

This post requires some basic understanding of how ZF2 applications work. If you have never worked with ZF2 before you might want to head over to the Getting Started with Zend Framework 2 guide over at framework.zend.com before continuing.

For this post I will use a fictive application with 2 modules, called Application and MyModule respectively. The way we usually do this is to develop the modules by themselves (with the exception of the “main” module, if you can call it that), and then install the modules using Composer.

All modules have its default configuration in the config/module.config.php file relative to the root of the module directory. When adding the module to our application we also add a config/autoload/<module name>.php file for application specific module configuration. If we use our fictive application this leads to the following configuration files:

Main application configuration:

  • ./config/application.config.php

Default configuration for the modules:

  • ./module/Application/config/module.config.php
  • ./vendor/vgno/mymodule/config/module.config.php

Application specific configuration for the modules:

  • ./config/autoload/Application.php
  • ./config/autoload/MyModule.php

Environment specific configuration for the modules:

  • ./config/development/Application.php
  • ./config/development/MyModule.php
  • ./config/staging/Application.php
  • ./config/staging/MyModule.php
  • ./config/production/Application.php
  • ./config/production/MyModule.php

Un-versioned local configuration file:

  • ./config/local.php

Before we continue adding even more configuration files let’s have a look at the config/application.config.php file which contains configuration related to how the rest of the files are loaded.

Show code
<?php
return array(
    'modules' => array(
        'Application',
        'MyModule',
    ),
    'module_listener_options' => array(
        'config_glob_paths' => array(
            __DIR__ . '/autoload/*.php',
            __DIR__ . '/current.php',
            __DIR__ . '/local.php',
        ),
        'module_paths' => array(
            __DIR__ . '/../module',
            __DIR__ . '/../vendor',
        ),
    ),
);

The config_glob_paths array defines the order of how the configuration files are loaded. First ZF2 will load the modules themselves (along with the default module configuration). Then it continues to load all .php files in the config/autoload directory which contains the application-specific module configuration. We also add a pattern for a file we have not yet mentioned: config/current.php. Last we add a glob for an un-versioned configuration file that can be used when testing some experimental configuration without having to edit versioned files.

The config/current.php file mentioned above is a symlink that is generated on deployment/installation. The link points to what we call an environment configuration loader. And what is this? Why, another configuration file of course! We have such loaders for all the different environments, and this is where we do the inheritance part as mentioned in the post title. Say we have three environments for our fictive application: development, staging and production. This maps to the following three files:

  • ./config/development.php
  • ./config/staging.php
  • ./config/production.php

The development configuration does not really need to inherit any of the other environments, so the file can look like this:

Show code
<?php
$config = array();

foreach (glob(__DIR__ . '/development/*.php') as $file) {
    $config = array_replace_recursive($config, require $file);
}

return $config;

The production environment does not need to inherit any configuration either, so that file is pretty similar:

Show code
<?php
$config = array();

foreach (glob(__DIR__ . '/production/*.php') as $file) {
    $config = array_replace_recursive($config, require $file);
}

return $config;

Lastly, we have the staging environment, which could inherit the production configuration:

Show code
<?php
$config = require __DIR__ . '/production.php';

foreach (glob(__DIR__ . '/staging/*.php') as $file) {
    $config = array_replace_recursive($config, require $file);
}

return $config;

The only difference in the staging.php file is that the initial configuration array is set to the production configuration, which it can override in the environment specific configuration files placed in the config/staging directory. If we don’t want to override any configuration we can simply omit that directory.

As mentioned earlier the config/current.php symlink is pretty crucial for this method to work. We use Capistrano for deployment, so we create the symlink during the deployment process. Since we don’t use Capistrano for deploying on the development server for all our developers we have an “install/prepare” task in ant/rake/phing/whatever that links the config/current.php symlink to config/development.php.

Since this method involves loading a rather huge amount of configuration files we cache the merged configuration. You can read more about how to achieve this over at Rob Allen’s blog: Caching your ZF2 merged configuration.

If anyone have any feedback to this method other than “Ermahgerd that’s a lot of config files, you suck!” feel free to leave a comment or two. Keep in mind that the fictive application I used only included two modules, so the benefit might not be too clear. The application we used this method in has more than 20 modules and 7 different environments, which would result in an ungodly amount of configuration files if we were to copy/paste configuration between environments instead of using inheritance.

And that’s it for now. Happy configuring!

Senior developer at VG. Coder of code, drinker/brewer of beer and listener of metal/punk/hc. @cogocogo | @BeerNorway | www.beernorway.com


3 comments

Leave your comment