Application Environment Configuration
Something I see a lot of developers struggle with is handling various environments separate from one other. I’ll go over a few methods here, specific to CakePHP applications.
Switch on hostname
This version is the least likely to work alone. Essentially, you switch configuration based on whatever hostname is requesting your application. For instance, you might configure your cache as follows:
<?php
if (in_array(env('SERVER_NAME'), array('example.com', 'staging.example.com'))) {
// production
Cache::config('default', array('engine' => 'Apc'));
} else {
// staging
Cache::config('default', array('engine' => 'File'));
}
?>
Why won’t this work?
- You probably do not have the
SERVER_NAME
environment variable set in CLI mode. If you do specify it, that isn’t intuitive and it is likely that someone will forget to specify it at one point or another - If you add more hostnames - which can and will happen - you have to go back and respecify this everywhere
- This is likely to be sprinkled throughout your application
Use the Environment Plugin
This method can be used by using the Environment Plugin
Early on in my CakePHP usage, I started using environment files. I would have a different environment file for each configuration:
app/Config/bootstrap/environments.php
app/Config/bootstrap/environments/production.php
app/Config/bootstrap/environments/staging.php
app/Config/bootstrap/environments/development.php
Your environments.php
file would contain the following:
<?php
CakePlugin::load('Environments');
App::uses('Environment', 'Environments.Lib');
include dirname(__FILE__) . DS . 'environments' . DS . 'production.php';
include dirname(__FILE__) . DS . 'environments' . DS . 'staging.php';
include dirname(__FILE__) . DS . 'environments' . DS . 'development.php';
Environment::start();
?>
An example development.php
:
<?php
Environment::configure('development',
true, // Defaults to development
array(
'Settings.FULL_BASE_URL' => 'http://example.dev',
'Email.username' => 'email@example.com',
'Email.password' => 'password',
'Email.test' => 'email@example.com',
'Email.from' => 'email@example.com',
'logQueries' => true,
'debug' => 2,
'Cache.disable' => true,
'Security.salt' => 'SALT',
'Security.cipherSeed' => 'CIPHERSEED',
),
function() {
if (!defined('FULL_BASE_URL')) {
define('FULL_BASE_URL', Configure::read('Settings.FULL_BASE_URL'));
}
}
);
?>
This was great, because now all my configuration was in one place, and all I needed to do was redeploy the app and every configuration would be picked up.
The environment switching was done by the existing of a CAKE_ENV
environment variable or by hostname, so I could get away with local development pretty easily as well as with command-line tools:
CAKE_ENV=production app/Console/cake Migrations.migration run all -p Migrations
One draw-back to this method is that now we assume that all developers will locally have the same environment. This is likely to be false - if we work on different projects, our database usernames might collide, or perhaps your Windows laptop can use WinCache and mine can use Opcache.
The other big issue is that this exposes production credentials for everything to all developers. While you may trust your developers, the day might come when you have an untrusted user - non-developer, or a new guy, or even a security auditor - that you’d rather not have complete access to your system, and thus it’s preferable to avoid specifying production environment information within the repository.
Environment Variable all the things
This is my current favorite. Essentially, all configuration is retrieved from an environment variable. You would, for instance, retrieve cache configuration from the CACHE_URL
environment variable:
CACHE_URL=redis://localhost:6379/0 app/Console/cake Migrations.migration run all -p Migrations
Your CakePHP code would parse environment variables as necessary to retrieve the data and configure your app.
Some benefits:
- Easily swap between one config and another.
- No need to force one user to use a configuration in their environment
- Can be used across multiple frameworks and languages, not just CakePHP
However, it’s more annoying to specify multiple config values:
export CACHE_URL=redis://localhost:6379/0
export DATABASE_URL=mysql://localhost:3306/example
export TEMP_PATH=/mnt
app/Console/cake Migrations.migration run all -p Migrations
I normally create a file in /etc/services/my-service
with the exports:
export CACHE_URL=redis://localhost:6379/0
export DATABASE_URL=mysql://localhost:3306/example
export TEMP_PATH=/mnt
And then source the file in:
. /etc/services/my-service app/Console/cake Migrations.migration run all -p Migrations