This week Chris Hartjes published his approach on how to handle different environments on a PHP application.
Although a nice approach, it demands different versions of a file in all environments, what,
correct me if I’m wrong, may lead to serious headaches with version control when server has to be tweaked.
Then Neil Crookes published his own, straight forward, but not very extensible.
Now it’s my turn. Got this shiny piece of code from an old cd-rw that I used to store some ideas – well I
didn’t born knowing about SCM – and then refactored it to look prettier.
So, the code is the following:
/**
* Singleton class to handle environment specific configurations.
*
* Auto-detect environment based on specific configured params and
* allow per environment configuration and environment emulation.
*
* Environment. Smart Environment Handling.
* Copyright 2008 Rafael Bandeira - rafaelbandeira3
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*/
class Environment {
public $environments = array();
protected $_configMap = array(
'security' => 'Security.level'
);
protected $_paramMap = array(
'server' => 'SERVER_NAME'
);
static function &getInstance() {
static $instance = array();
if (!isset($instance[0])) {
$Environment = 'Environment';
if (config('app_environment')) {
$Environment = 'App' . $Environment;
}
$instance[0] = new $Environment();
Configure::write('Environment.initialized', true);
}
return $instance[0];
}
static function configure($name, $params, $config = null) {
$_this = Environment::getInstance();
$_this->environments[$name] = compact('name', 'params', 'config');
}
static function start($environment = null) {
$_this =& Environment::getInstance();
$_this->setup($environment);
}
static function is($environment) {
$_this =& Environment::getInstance();
return ($_this->name === $environment);
}
public function __construct() {
if (Configure::read('Environment.initialized')) {
throw new Exception('Environment can only be initialized once');
return false;
}
}
public function setup($environment = null) {
if (empty($environment)) {
foreach ($this->environments as $environment => $config) {
if ($this->_match($config['params'])) {
break;
}
}
}
$config = array_merge(
$this->environments[$environment]['config'],
array('Environment.name' => $environment)
);
foreach ($config as $param => $value) {
if (isset($this->_configMap[$param])) {
$param = $this->_configMap[$param];
}
Configure::write($param, $value);
}
}
protected function _match($params) {
if ($params === true) {
return true;
}
foreach ($params as $param => $value) {
if (isset($this->_paramMap[$param])) {
$param = $this->_paramMap[$param];
}
if (function_exists($param)) {
$match = call_user_func($param, $value);
} else {
$match = (env($param) === $value);
}
if (!$match) {
return false;
}
}
return true;
}
}
The cool is that, placing it inside APP/config folder,
it can be loaded like
config('environment');
Wow, and even a dog would know what it does.
The usage is simple, as you might guess, to configure enviroments
Environment::configure(
'my_environment',
array('server' => 'sandbox.localhost'),
array('debug' => 3)
);
Environment::configure(
'mama\'s pc',
array('DOCUMENT_ROOT' => 'C:\\Workspace\\my_proj\\webroot'),
array('debug' => 1, 'Email.server' => false)
);
And then to start
Environment::start();
start it anywhere, when you want.
You can also emulate an environment configuration
Environment::start('staging');
useful on tests
Environment::start('linux_development');
$this->assertFalse($obj->doIt(), 'works fine on dev');
Environment::start('linux_staging');
$this->assertFalse($obj->doIt(), 'breaks with some staging configs');
and
$this->skipIf(Environment::is('cli'));
$this->assertEqual('mamma', $variable);
You can easily configure all your environments
Environment::configure(
'cli'
,array('defined' => 'CAKEPHP_SHELL')
,array('debug' => 1)
);
Enviroment::configure(
'development'
,array('server' => 'localhost')
,array('debug' => 2, 'security' => 'low')
);
Environment::configure(
'staging'
,array('server' => 'staging.localhost')
,array('debug' => 2, 'security' => 'medium')
);
Environment::configure(
'production'
,true
,array('debug' => 1, 'security' => 'high')
);
notice that the “true” param on ‘production’ says that it will match always, so,
if it’s not ‘dev’, nor ’staging’, nor ‘cli’, Environment will, by default, assume it’s ‘production’,
but you could do the same with the ‘dev’ set.
You can create ‘app_environment.php’ on APP/config containing the AppEnvironment class,
that should extend Environment, and then specify different config key aliases and
match params. So, to mix Hartjes’ approach, you can create the AppEnvironment
class AppEnvironment extends Environment {
protected $_paramMap = array('env' => 'APP_ENV');
}
set APP_ENV in the .htaccess
SetEnv APP_ENV "littlehart.net"
and configure needed enviroments
Environment::configure(
'production'
,array('env' => 'littlehart')
,array('debug' => 1, 'Session.cookie' => 'attthekeyboard')
);
Environment::configure(
'development'
,array('env' => 'sandbox')
,array('debug' => 1, 'Session.timeout' => '1500')
);
To get rid of the ‘Session.cookie’, you can map ‘cookie’ to it, doing
class AppEnvironment extends Environment {
protected $_paramMap = array('env' => 'APP_ENV');
protected $_configMap = array('cookie' => 'Session.cookie');
}
and then configuring
Environment::configure(
'production'
,array('env' => 'littlehart')
,array('debug' => 1, 'cookie' => 'attthekeyboard')
);
Environment::configure(
'development'
,array('env' => 'sandbox')
,array('debug' => 1, 'Session.timeout' => '1500')
);
Although the implementation is CakePHP specific, it’s really easy to adapt – actually replace two or three lines
and you are done… ok, here is the tip: get rid of all Configure::write() and Configure::read(), config() and env()…
ok… maybe it’s 4 or 5 lines, but you got the picture.
What do you think? I think it’s just fine!
9 responses so far ↓
Neil Crookes // December 5, 2008 at 2:04 pm |
I like it, lengthy, but I like it, nice one!
Brendon Kozlowski // December 5, 2008 at 3:09 pm |
I think this would be great to have in the core somehow. IIRC, from my brief exposure to Symfony, that it can handle this type of thing from within the framework itself. Would only make sense that Cake could too. I like the approach here.
Chris Hartjes // December 5, 2008 at 9:49 pm |
Hey Rafael, to be fair the only file that needs to change in my method is the Apache configuration file, which is not something that would be under version control along with the rest of your application.
rafaelbandeira3 // December 5, 2008 at 10:03 pm |
@Neil Crookes yeah, well it’s not that short, but it’s quite handy isn’t it? Nice to know you liked.
@Brendon Kozlowski yes, I think too that something like this could ship with cake. I’ll check Symfony when I have time, they got some nice stuff there.
@Chris Hartjes thanks for your reply! I can’t help to say that it’s nice to have one of my “guides” replying to me…
Actually, server configurations may be under version control, I’ve worked on few apps that relied on some server configurations, not saying it’s a good thing though, and it requires configuration of files outside of the app scope, what sometimes may mean trouble and debug mess. Going further, I’m not sure, but maybe your approach is Apache specific, taking off some portability of the app.
Joel Moss // December 6, 2008 at 1:47 am |
Nice! but thinking that it might be a touch of overkill.
rafaelbandeira3 // December 6, 2008 at 3:37 am |
@Joel Moss Thanks and you are right: it is totally overkill. Serious. But I must admit that it also is very scalable and extensible. Maybe it just wouldn’t fit a blog engine or a basic cms with some work flow controls… Anyway it is already planned to be implemented on some major products of my company, because it end up covering lots of “problems” we face with different dev sets, and possibly different prod environments.
Mark Story // December 13, 2008 at 5:28 am |
Since you have PHP5 spices you should use self:: and static class members. It will help you reduce all the getInstance() calls, and make things a bit shorter.
But otherwise I like it, good stuff
CakePHP Digest Volume #3 :: PseudoCoder.com // December 16, 2008 at 1:47 pm |
[...] There were also a couple interesting posts about handling environment specific settings in Cake apps. Personally I’m a fan of having a “bootstrap.local.php” file that isn’t stored in SVN and is included in the normal bootstrap file. There’s no sex appeal to this method though, so if you don’t want to be standing alone in the corner while everyone else dances the night away, I suggest you read the posts by Neil Crookes, Chris Hartjes and Rafael Bandeira. [...]
cakealot // December 29, 2008 at 12:36 am |
Easy CakePHP deployment: Environment & Database Config…
How to use the Environment class setup in DATABASE_CONFIG class to switch databases between environments
……