Environment bootstrapping with Titon\Environment

Handling multiple environments in an application can be tedious as each environment supplies its own set of configuration and unique bootstrapping. The amount of environments can grow to unmanageable numbers when dealing with dev environments, for example, different configuration for each developer. With no external dependencies, and a basic requirement of PHP 5.3, the Titon\Environment package hopes to solve this problem.

Defining Environments

An environment is mapped using an HTTP host or an IP address. During an HTTP request, an environment will be detected that matches the current host/IP, which is then loaded and bootstrapped. But first the environments need to be added through addHost(), which accepts a unique key, an array of hosts and IPs to match against, and finally the type. Out of the box, the package supports 3 types of environments — dev, staging and prod.

use Titon\Environment\Environment;
use Titon\Environment\Host;
// Create an environment instance
$env = new Environment();
// Register a dev env
$env->addHost(new Host('dev', array('127.0.0.1', '::1', 'localhost'), Environment::DEVELOPMENT));

What this did is create a development host environment for the localhost host, the 127.0.0.1 v4 IP, and the ::1 v6 IP. When either of those values are matched against the current request, the environment will be bootstrapped. But where does the bootstrapping take place? We must define it first! This can be accomplished through the setBootstrap() method on the Titon\Environment\Host object. Using the same example above, an absolute path to a bootstrap file can be defined.

$env->addHost(new Host('dev', array('127.0.0.1', '::1', 'localhost'), Environment::DEVELOPMENT))
	->setBootstrap('/absolute/path/to/bootstrap.php');

Now when an environment is detected a bootstrap will occur. This bootstrap file should contain configuration and logic specific to each environment.

Multiple Environments

In the previous example only one environment was added, dev. In standard applications, at minimum 2 environments will exist, dev and prod. Let's add the prod environment through the production domain and include a fallback. A fallback is used when an environment cannot be matched — this usually will fallback to prod as there is no risk of dev code making it into production, or at minimum a dev environment with error reporting turned off.

$env->addHost(new Host('prod', 'website.com', Environment::PRODUCTION))->setBootstrap('/envs/prod.php');
// Set a fallback using the host key
$env->setFallback('prod');

Let's now solve the multiple developer issue mentioned in the opening paragraph.

// Share the default boostrap
$env->addHost(new Host('john', 'john.dev.website.com', Environment::DEVELOPMENT))->setBootstrap('/envs/dev.php');
$env->addHost(new Host('mike', 'mike.dev.website.com', Environment::DEVELOPMENT))->setBootstrap('/envs/dev.php');
// Custom boostrap
$env->addHost(new Host('chris', 'chris.dev.website.com', Environment::DEVELOPMENT))->setBootstrap('/envs/dev-chris.php');

Or perhaps multiple QA staging environments for different regions?

foreach (array('us', 'eu', 'cn', 'au') as $region) {
	$env->addHost(new Host('qa-' . $region, $region. '.qa.website.com', Environment::STAGING))->setBootstrap('/envs/qa-' . $region. '.php');
}
Initializing An Environment

Once all the environment hosts have been defined, the package must be initialized. This can be done through the initialize() method, which will kick-start the matching and bootstrapping process.

$env->initialize();

Once an environment has been chosen, you can access it at anytime.

// Return the current host
$env->current();
// Or values from the host
$env->current()->getKey();
$env->current()->isStaging();
// Or use the convenience methods
$env->isDevelopment();
In Closing

For being such a lightweight class, the Titon\Environment package provides an extremely helpful application pattern. Be sure to give it a try in any MVC framework!

Setting up cron jobs with Cake shells

This post will primarily be about the process I had to go through to get cron jobs and shells working on Dreamhost. The main problem I kept running into was that the shells would simply not work at all through a cron job; yet they worked when I manually did it through SSH. The weird thing was the source of the console/cake file was being printed in my logs, instead of executed. Below is a quick checklist of things to look out for:

TERM environment variable not set

This isn't really an error, but the TERM variable is used by the server environment and does not affect the shell, even if the error is thrown. Now I may not know much about server maintenance and configuration, but adding the following code to console/cake seemed to do the trick (Hats off to Matt Curry for the tip). And of course, you should change linux to whatever your server is.

TERM=linux
export TERM
Cron environment not PHP 5

This was a weird problem I ran into. It seemed the environment in which my crons ran was PHP 4, so I was receiving unexpected results and would receive this error (it mainly depends where the php folder is located and if its loaded correctly).

console/cake: line 30: exec: php: not found

To fix this, I had to rewrite the cake console file and update it with the path to the PHP folder (path should represent your servers structure). You should only need to do this if you can't upgrade PHP to 5 for some reason.

exec php -q ${LIB}cake.php -working "${APP}" "$@"
# change to
exec /usr/local/php5/bin/php -q ${LIB}cake.php -working "${APP}" "$@"
Making sure your cron has ownership

A simple problem with a simple fix. Since the user that uploaded the console files was different than the user running the cron, I would receive this error:

sh: cake/console/cake: Permission denied

All I had to do was switch the cron user to the user that owns the file, and it fixed the permissions problem.

Making sure the cake file uses unix line endings

This was another pain in the ass that took me forever to get working. My cron environment would throw this error once I fixed the things above (this applies to my Dreamhost server and may not apply to all).

sh: cake/console/cake: /bin/bash^M: bad interpreter: No such file or directory

It required me contacting my host and asking for help on this one. I tried saving all my files as unix and made sure my FTP was not converting them in any way. Still nothing. So my host told me about this SSH command called dos2unix which would convert the file to unix line endings, and wallah, magic! Everything seemed to be working now.

dos2unix cake/console/cake

These are just a few of the minor setbacks I had to deal with when setting up my cron jobs. Most of this really applies to my Dreamhost server, as my Media Temple server worked right away with no configuration. Furthermore, here are some more Dreamhost articles that I used for this problem, that might be of aid:

Editing Your Environment Profile
Make PHP5 the Default in the Shell

Turning debug off automatically in production

So I recently got tired of having to manually set debug to 0 every time I updated an old site or published a new one. So I thought, why not have it automatically turn off when the site is live? For this to work, I'm assuming that you are working on a localhost with an IP of 127.0.0.1. If you are not, you might get this to work by changing the IP in the code below, or using HTTP_HOST instead of REMOTE_ADDR. Simply add this code at the top of your app/config/bootstrap.php file.

// Production
if (env('REMOTE_ADDR') != '127.0.0.1') {
	Configure::write('debug', 0);
}
// Or alternate technique
if (env('HTTP_HOST') != 'localhost') {
	Configure::write('debug', 0);
}

This also means you can leave debug set to 2 in your core config file at all times.