Every web project faces similar challenges when it comes to deployment and development: how to store settings and how to deploy them. Settings such as database configuration or SMTP server and port or caching and error reporting are different for production and development environments. Let’s say the workflow looks like this:
- Developers Jane and Joe work on the project on their individual machines.
- Project is synced to a development server where bosses and beta testers can review the current development state.
- Finally, the code is deployed to the production server (or more likely, multiple servers).
Each machine (Jane’s, Joe’s, Preview and Production) needs separate configuration.
In it’s worst form, configuration file would look this:
<?php /** * This is a bad example and wrong approach */ $config['db']['encoding'] = 'UTF-8'; // We want to force this onto everybody if ($_SERVER['SERVER_NAME'] == 'joe.dev' || $_SERVER['SERVER_NAME'] == 'jane.dev') { $config['db']['host'] = 'localhost'; $config['smtp']['host'] = 'smtp.test.com'; } elseif ($_SERVER['SERVER_NAME'] == 'preview.server.dev') { $config['db']['host'] = 'localhost'; $config['smtp']['host'] = 'smtp.site.com'; } elseif ($_SERVER['SERVER_NAME'] == 'www.site.com') { $config['db']['host'] = 'dbhost'; $config['smtp']['host'] = 'smtp.site.com'; }
Of course, such approach has many problems: you have to account for every domain, you have to make sure developers use different domain names in order to have different configuration and it becomes hard to maintain after a while.
A better approach would be do create a structure like this:
config/global.php config/local.php config/local.php.example
- global.php would include shared configuration, like database encoding, which is to be forced upon everybody. This file would be committed to source control tool (Subversion, Git, etc.).
- local.php.example would contain an example setting array which developers could use to realize what options are there and what needs to be set. It would also be in source control, but would never be used except for reference.
- local.php would be ignored by source control (via .gitignore or svn:ignore) and developers would be expected to create that file locally, and also create it on preview and production servers as well.
You could take this approach a step further and have all .global.php files committed and all .local.php out of source control, so you could have different files for different settings easily:
config/db.global.php config/session.global.php config/db.local.php config/smtp.local.php ...
While this approach looks better than a single ifed file, there are some crucial problems with it:
- Configuration is not versioned. So, if your production site starts misbehaving due to a wrong configuration setting, you can’t just revert to an older version.
- Configuration is invisible: developers Joe and Jane have to log into the production servers to examine the configuration of each instance. If Joe needs help from Jane, she doesn’t know what kind of stuff Joe keeps in his local.php
- If a single configuration item has to be changed on e.g. all production servers, it will be tedious and can’t be done automatically.
This is clearly an anti-pattern, even if very widely used.
The best solution is to have a structure like this:
config/global.php config/jane.local.php config/joe.local.php config/preview.local.php config/production.local.php ...
In this scenario, all files are stored in version control. The main challenge is to decide which file to use on which machine. There are two approaches here. The first one is to just have a local.php file, unversioned like in the previous scenario, which only contains one directive:
<?php include('appropriate.local.php');
and change the content of that file per server. Not a terribly bad approach, but there is a better way. Your HTTP server can set an environment variable which will be available to PHP. In Apache, you would set it by adding SetEnv "APP_ENV" "production"
or SetEnv "APP_ENV" "jane"
in virtual hosts setting or in .htaccess. In lighttpd, you would use ModSetEnv and do something like setenv.add-environment = ("APP_ENV" => "jane")
After you have the environment variable ready, it will be available to PHP and you can use it in local.php like this:
Voila! It takes some setting up, but it is by far the easiest and cleanest solution. Of course, don't go overboard with using environment variables for everything and anything. Use them when they are appropriate: to decide which environment your PHP script is in.