Handle different environments with PHP

Being both a Rails and PHP developer, I’m often lacking a few things when I’m switching from Rails to PHP.

One of the things I miss the most, is the different environments that Rails has, which makes testing, developing, staging and production environments easy.
However, I found a way to do this in PHP as well, without using frameworks like Zend, CakePHP etc.

Getting started

There are basically two things you need to do, in order to get this setup to work properly.

Configure your server (apache)

First we need to configure the apache server (If you are using nginx, look at their documentation for help).

The magic lies in using the SetEnv function in the apache server.
This functions makes a variable available in the $_SERVER object in PHP, which is what we are using to differentiate between the environments.

Virtual hosts

If you are using virtual hosts, then simply add it with in the section.

An example configuration with a “test” environment could be:

<VirtualHost *:80>
  ServerName my_site.test.dk
  DocumentRoot /var/www/my_site.test.dk

  SetEnv APPLICATION_ENV test

  <Directory /var/www/my_site.test.dk>
    Options Indexes FollowSymLinks -MultiViews
    AllowOverride All
    Order allow,deny
    allow from all
  </Directory>

  ErrorLog /var/log/apache2/my_site.test.dk.error.log

  # Possible values include: debug, info, notice, warn, error, crit,
  # alert, emerg.
  LogLevel warn

  CustomLog /var/log/apache2/access.log combined
</VirtualHost>

Using the apache.conf / httpd.conf

On my mac the file is called httpd.conf, but on the ubuntu linux servers I’m managing it’s called apache.conf.

Anyways, just head to the bottom of the file located here:
Linux: /etc/apache/apache.conf
Mac: /etc/apache/httpd.conf

And add the following line:

SetEnv APPLICATION_ENV "development"

Exchange “development” with the environment you are configuring (In my setup, production = no value)

Configure your application

Now we need to use the variable passed to the $_SERVER object, in order to see what environment we are “in”, and configure the application accordingly.

In most projects I have an environment.inc.php file, which has the following structure:

<?php

// initializing the configuration array (mostly to avoid null warnings)
$envConfiguration = array();

// the environment configuration for the development environment (local machine)
if(isset($_SERVER['APPLICATION_ENV']) && $_SERVER['APPLICATION_ENV'] == 'development') {
  $envConfiguration = array(
    'db_password' => '12345',
    'db_user' => 'root',
    'db_host' => '127.0.0.1',
    'db_name' => 'my_dev_db'
  );
}
// the environment configuration for the unit testing environment (local machine)
if(isset($_SERVER['APPLICATION_ENV']) && $_SERVER['APPLICATION_ENV'] == 'unittest') {
  $envConfiguration = array(
    'db_password' => '12345',
    'db_user' => 'root',
    'db_host' => '127.0.0.1',
    'db_name' => 'my_unittest_db'
  );
}
// add more environments here... E.g. staging, test etc

// Not having the APPLICATION_ENV variable set, forces the application to
// use PRODUCTION settings!
// The reason for this is, that I don't always have control of the production
// servers, while I have control over the staging and test servers. 
// (You can of course have a production value set
else {
  // production environment settings here.
  $envConfiguration = array(
    'db_password' => 'some_strong_password',
    'db_user' => 'some_production_user',
    'db_host' => '127.0.0.1',
    'db_name' => 'production_database'
  );
}
?>

Pretty simple ye?

What we are doing is simply checking if the APPLICATION_ENV variable is set in the $_SERVER object, and if it is, we test what it is.

The reason I’m checking if the APPLICATION_ENV isset, is because it gives a lot of warnings if the variable is not set (which would be in production for my setup).

What about unit testing? (phpunit)

Well, I have an answer there as well.

Since the $_SERVER variable is not available in unit tests, we simply create it ourselves and set the APPLICATION_ENV to “unittest”.

Here is a sample unittest include file, which should be included at the very top of your unittest.
Let’s call this file unitTestConfiguration.inc.php and put it in a folder called tests

<?php

// includes the phpunit framework
require_once 'PHPUnit/Framework.php';

// constructs the SERVER variable to set the environment to unittest.
$_SERVER = array(
  'APPLICATION_ENV' => 'unittest',
  'SERVER_NAME' => 'localhost',
  'REQUEST_URI' => ''
);
// SERVER_NAME and REQUEST_URI is not needed, but nice to have

// includes our environment file (remember to add a unittest section there!
include('config/environment.inc.php');

// includes the database file, which reads the $envConfiguration variable
// (which is set in the environment.inc.php file) and connects to the database
include('config/db.inc.php');

// sets the default timezone (Because strftime will throw a warning in PHP5+)
date_default_timezone_set("Europe/Copenhagen");

?>

When creating your unit test, simply do the following:

<?php

// includes the unit test configuration (including the PHPUnit framework)
include('tests/unitTestConfiguration.inc.php');

class EnvironmentTest extends PHPUnit_Framework_TestCase {
  /**
   * A small test to see if our environment is actually set.
   * (You don't need this test in your test files, this is 
   * just for the scope of this post!)
   */
  function testEnvironment() {
    $this->assertTrue(isset($_SERVER['APPLICATION_ENV']));
    $this->assertTrue($_SERVER['APPLICATION_ENV'] == 'unittest');
  }
}
?>

To run the unit test, simply open a terminal / command / cmd (or what ever you are using), and go to the project folder.
There you should run the following command:

phpunit tests/environmentTest.php

On my machine that gives the following output:

$ phpunit tests/environmentTest.php 
PHPUnit 3.4.14 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 5.75Mb

OK (1 test, 2 assertions

Files for this post: 2011-01-08_php_testing_article

One Response to Handle different environments with PHP

  1. Sergey says:

    How then CLI scripts will fetch the environment?