Writing powerful and easy config files with PHP-arrays

Friday, May 8, 2009

I was asked many times how I organize my config files, and my response was always the same, until some time ago when I switched began refactoring the codebase of my blog. I always used PHP config files in some way (as I got inspired to it by Matthew Weier O'Phinney). So first let's clearify the advantages of PHP-array config files:

· they allow to organize one config into multiple files
· they can be cached by an opcode cache
· they support constants
· they allow to create easily readable config trees
· they support boolean and integer values

Looking at those advantages, you may ask now why not everbody is using them. Well the problem mostly is that you cannot create extend-sections (when working with Zend_Config for example). So in the past I always had to create separate config files for each development and production environment. When I started refactoring the codebase I thought about that problem and came to a very simple solution. First you have your base config file looking somehow like this:
<?php
return array_merge_recursive(array(
    'resources'   => array(
        'frontController' => array(
            'moduleDirectory' => APPLICATION_PATH . '/modules'
        ),
        'router' => array(
            'routes' => include dirname(__FILE__) . '/routes.config.php'
        ),
        'db' => array(
            'adapter' => 'pdo_mysql',
            'params'  => array(
                'charset' => 'utf-8'
            )
        )
    )
), include dirname(__FILE__) . '/' . APPLICATION_ENV . '.config.php');
This config file uses array_merge_recursive to tage a basic config array and overwrite or extend properties from an environment specific config file (in this case development.config.php and production.config.php). Additionally the routes for the router are outsourced into a separate file, since they are taking up a lot of space. Now let's take a look at the the development.config.php:
<?php
return array(
    'phpSettings' => array(
        'display_startup_errors' => 1,
        'display_errors'         => 1,
        'error_reporting'        => (E_ALL | E_STRICT)
    ),
    'resources'   => array(
        'frontController' => array(
            'baseUrl'         => '/some/dev/path',
            'throwExceptions' => true
        ),
        'db' => array(
            'params'  => array(
                'host'     => 'localhost',
                'username' => 'foo',
                'password' => 'bar',
                'dbname'   => 'baz'
            )
        )
    )
);
As you can see, the development specific config file now enables all the error reporting, set the baseUrl for the front controller and the connection parameters for the database adapter. The production specific config file is doing similar things but disabling all error reporting. As you have seen in the base config file, two constants should already be set (APPLICATION_PATH and APPLICATION_ENV). To use the base config file within your Zend Framework application, you load it like this:
<?php
// Loading the config manually
$config = new Zend_Config(require APPLICATION_PATH . '/config/config.php');

// Or telling Zend_Application to load the config
$application = new Zend_Application(APPLICATION_ENV,
                                    APPLICATION_PATH . '/config/config.php');
If you want to see a full example of a working config-structure, you can take a look at my new codebase in my SVN repository.

Update 2010-10-07
As of PHP 5.3, you should use array_replace_recursive() instead of array_merge_recursive().

Comments to this article

  • Avatar of Ryan Mauger Reply Ryan Mauger Friday, May 8, 2009 11:28 AM

    Nice!

    Better than my approach to it by a long way, I will be sure to use this notation when i refactor Zend_App into my projects!

  • Avatar of Romeo Adrian Cioaba Reply Romeo Adrian Cioaba Friday, May 8, 2009 11:28 AM

    Might be just the fact that i haven't been using much .ini files in the past, but your solution just seems natural for PHP development.

    However, i think this might break some functionality when you want to use the configs for another enviroment, other than PHP. For interoperability i think .ini and .xml are better.

    What do you think?

  • Avatar of Ben Scholzen 'DASPRiD' Reply Ben Scholzen 'DASPRiD' Friday, May 8, 2009 11:31 AM

    In fact this does not work when working with other programming languages as well. Tho in those cases the other programming languages usually just want you database connection settings, and not any information about PHP settings or application resources :)

  • Avatar of Harald Lapp Reply Harald Lapp Friday, May 8, 2009 5:37 PM

    mmm ... i don't see the benefit over using something else and caching it. your solution seems to be way more complicated compared to doing configuration with for example yml files.

    i've thought about configuration files very much, too, when i designed my framework and i came to the conclusion, that yml works very well using spyc or syck.

  • Avatar of Ben Scholzen 'DASPRiD' Reply Ben Scholzen 'DASPRiD' Friday, May 8, 2009 6:00 PM

    @Haral: I named the benefits above, and IMHO the major one is that you are able to split the configuration into logical units.

  • Avatar of Freeaqingme Reply Freeaqingme Friday, May 8, 2009 11:14 PM

    I have per resource a file, all in a separate resource-dir (just like is done with Zym_App). My looks like this:

    <?php
    $resourceDir = dirname(__FILE__).'/resource/';
    $config = array('resources'=>array());

    // Scan for resource files
    $resourceFiles = scandir($resourceDir);
    unset($resourceFiles[0],$resourceFiles[1]);
    foreach ($resourceFiles as $file) {
    $config['resources'] = $config['resources'] + include $resourceDir . $file;
    }

    return array_merge_recursive($config,include dirname(__FILE__) . '/' . APPLICATION_ENV . '.php');

    ?>

    -- Freeaqingme

  • Avatar of anon Reply anon Friday, May 8, 2009 11:57 PM

    I just use .ini files. Zend_Config can handle inheritance, so it's easy to create sections within a single .ini file.

  • Avatar of James Reply James Saturday, May 9, 2009 2:51 AM

    Interesting, but how do you load front controller plugins eg

    resources.frontController.plugins.auth = "Default_Plugin_Auth"

    works, but this:

    'resources' => array(
    'frontController' => array(
    'plugins' => array(
    'auth' => 'Default_Plugin_Auth'
    )
    )
    doesn't. Producing this error:

    Declaration of Default_Plugin_Auth::preDispatch() should be compatible with that of Zend_Controller_Plugin_Abstract::preDispatch()

  • Avatar of Tomáš Fejfar Reply Tomáš Fejfar Saturday, May 9, 2009 8:04 PM

    That's your problem, James. You have to define plugin's preDispatch with same arguments as those in Zend's abstract.

  • Avatar of Chris B. Reply Chris B. Sunday, May 10, 2009 11:35 PM

    This approach has one major drawback, there is no code completion in the IDE (except jcx visual studio extension, correct if im wrong). What do you think about code generator (to create strongly typed config based on whatever format u like - less error-prone, can generate type hints etc.) ?

  • Avatar of Radoslav Stankov Reply Radoslav Stankov Monday, May 11, 2009 11:29 AM

    I use the same approach in my application, and it is really easy to manage the config files.

  • Avatar of Sriv Reply Sriv Monday, May 11, 2009 5:34 PM

    This is wonderful. Thanks Ben for such an easy approach :)

  • Avatar of Jérôme Reply Jérôme Monday, May 11, 2009 8:48 PM

    How about trying the Configuration eZ Component ?

    http://ezcomponents.org/docs/tutorials/Configuration

    Cheers :)

  • Avatar of Ben Scholzen 'DASPRiD' Reply Ben Scholzen 'DASPRiD' Monday, May 11, 2009 9:14 PM

    Jérôme, the array-config format in the eZ component is still theme as for Zend_Config and thus this article can also be applied there.

  • Avatar of Voyteck Reply Voyteck Tuesday, May 12, 2009 10:00 AM

    And I must disagree with an idea of creating config files in PHP arrays. Config files in my oppinion are the files, that keeps setings, that should be able to be moified by system administrator. Making them in PHP-array standard forces sysadmin to learn PHP - which in fact might not happen (he might be a Linux system admin - without programming knowledge).
    Therefore I suppose the config files should rather be kept in more universal format - such as INI or .conf - which btw can be easily parsed by Zend_Config, changed to OO way and operated.

  • Avatar of Ben Scholzen 'DASPRiD' Reply Ben Scholzen 'DASPRiD' Tuesday, May 12, 2009 10:37 AM

    Voyteck, I wouldn't consider a Linux administrator without knowledge about the basics of a programming language actually an administrator. Also, to change e.g. the database username, password or soemthing similar in a PHP config file doesn't really require any knowledge about PHP (except for escaping, but that applies to any config file format, and is usally not required).

  • Avatar of KingCrunch Reply KingCrunch Wednesday, May 13, 2009 8:47 AM

    There is one problem with array_merge_recursive. Two "not-array" values with the same key will be merged together in an array.

    < ?php
    $a = array (
    'config'=>array(
    'db'=>array(
    'dbname'=>'name')));
    $b = array (
    'config'=>array(
    'db'=>array(
    'dbname'=>'thisConfigUseAnotherName')));

    var_dump (array_merge_recursive($a,$b));
    /*
    array(1) {
    ["config"]=>
    array(1) {
    ["db"]=>
    array(1) {
    ["dbname"]=>
    array(2) {
    [0]=>
    string(4) "name"
    [1]=>
    string(24) "thisConfigUseAnotherName"
    }
    }
    }

  • Avatar of ruflin Reply ruflin Monday, August 3, 2009 10:07 AM

    There are two more thing you can do with php/arrays that isn't possible in ini files as far as I know:

    Calcuations:
    $config['cookie_lifetime'] = 60*60*7; // makes it more readable

    And if you're not doing it inside the nested structure, it's also possible to reuse already defined fields:

    $config['tmp']['base'] = '/tmp';
    $config['tmp']['logs'] = $config['tmp']['base'] . '/logs;

    Sometimes i like to have such dependent fields.

  • Avatar of ruflin Reply ruflin Monday, August 3, 2009 10:16 AM

    @KingCrunch: I solve this problem, by not using the nested structure for arrays (a least in the additional files).

    // file config.php
    ....
    $config['config']['db']['dbname'] = 'production';

    include dirname(__FILE__) . '/' . APPLICATION_ENV . '.config.php');

    return $config;


    // file dev.config.php
    $config['config']['db']['dbname'] = 'test';

  • Avatar of Cristian Reply Cristian Saturday, May 15, 2010 4:41 PM

    Dasprid, did you made any profiling to see if there is a significant speedup on loading the config file by using PHP arrays instead of let's say .ini files ?

    I am interesting by any things which are speeding up the ZF apps...

    • Avatar of Ben Scholzen 'DASPRiD' Reply Ben Scholzen 'DASPRiD' Friday, August 13, 2010 1:13 AM

      Sure. Though you don't really need to measure it. Simply think about this: With APC enabled, PHP keeps the config file(s) in memory, while INI files have to be read (stat call) and parsed on every page request.

  • Avatar of viviendas prefabricadas en cordoba Reply viviendas prefabricadas en cordoba Monday, August 22, 2011 9:40 PM

    This approach has one major drawback, there is no code completion in the IDE (except jcx visual studio extension, correct if im wrong). What do you think about code generator (to create strongly typed config based on whatever format u like - less error-prone, can generate type hints etc.) ?

Leave a comment

Please note that your email address will not be shown, it is only used to fetch your avatar image from gravatar.com and for notifications.

     _                       
 ___(_)_ __  _   _  ___ __ _ 
/ __| | '_ \| | | |/ __/ _` |
\__ \ | |_) | |_| | (_| (_| |
|___/_| .__/ \__, |\___\__,_|
      |_|    |___/