Tag Archives: apache

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

Switching coffee and PHP testing

Whoa, long time since my last post.

Anyways, what has happened recently?

I’ve switched jobs and are now working at a small IR firm in Denmark, which is located on Amager.
We do webcasts / Quarterly reports for firms in Denmark.

Saying “We do” is a bit off though.
I don’t, that is. I help out if we are missing a technician, else i’m creating webapps to help the business complete daily tasks faster.

And how is that going?
Well, I’ve completed a few systems already, and within one month the first system was deployed to the wild. It was fun to create a whole new system, especially because I am on my own.
Roughly speaking, i’m the only developer there, so I don’t get a lot of sparing on the code i’m writing. Only the designs and the general “look and feel” of an app.
It’s fun, it’s different and I like the pace.

An other thing i’ve been working on, is moving the old and new sites to new virtualized servers.
I’ve been managing a few servers for the last 6 years, but not anything on this scale. But it’s fun. It takes a little time from the programming though, but after it’s configured, it mostly runs smoothly.

I developed an SSO system and deployed it to the wild a few months ago. So far i’ve only had minor problems with it, and all things are running great.

And yes, I code in PHP. It was by choice actually. On my last jobs I’ve coded Bash, Perl, C, C++, then Java and then Ruby on Rails and on the new job I was given a free choice.

So I chose PHP.

“Why?” you might ask, when there’s soo much hype about Ruby on Rails these past few years. Without starting a flame wall in the comments, let’s just say, that while Ruby is a beautiful language, I just couldn’t get accustomed to the Rails frame work. So many rules, so many conventions. So little time. (The testing was nice though.. but meh)
I might return to Rails someday, but for now, it’s PHP controlling the battle.

The webapps i’ve created at my new job are all created using my small github project called php-mvc-base, which basically is just a structure I use to get started on a PHP project. It gives me “nice urls”, but other than that, the rules are pretty basic.

The structure outlines as follows:

/app
/app/controllers
/app/models
/app/views
/public/images
/public/javascripts
/public/stylesheets

And all controllers (ofc) go in the app/controllers folder.
So let’s say, that we want a new controller at the URL http://localhost/my_projects/ there is basically two things that can be done:
* Create a folder in the app/controllers folder, called my_projects. Place an index.php file there, do the controller code.
* Create a my_projects.php file in the app/controllers folder. Do the controller code.
Both options give the same path.

Simple and clean (well, at least in my world. And I use the folder approach btw, if you wanted to know).

(You can find the project at http://github.com/jimmiw/php-mvc-base along with a simple example.)

I’ve also released a few javascripts to CodeCanyon and I’ve had a few purchases already. It’s nice knowing people can use your stuff. I mostly coded them because I had a problem they could solve, but releasing them there, made the code so much better. This was mostly due to the javascript approval team on CodeCanyon (Thanks Jeremy McPeak for the patience and help).

You can see my profile on CodeCanyon here: http://codecanyon.net/user/jimmiw

I’ve also taken over a small project with a designer friend of mine called Janus C.
The project is familielivet and is a danish page the centers around the family. It’s free to use and hopefully easy to understand and use for all ages.
I’ve not actually released any code to the system yet, but we a doing some design changes and a total rewrite of the codebase, and it will hopefully be released soon.

After switching from Ruby to PHP I missed the easy testing that Ruby offered. Thank god for phpunit btw, this lovely tool simply makes testing fun again. Be sure to check it out when you are writing tests for your webapps (As you should be!).

On an interesting side note, Rails offers the ease of different environments for you to use.
E.g. Test, Staging and Production.
I actually found a simple way of doing this with PHP as well, but it requires that you have access to the apache configuration files (which you at least have on your development machine).

Roughly speaking, all you need to do, is to add a variable to your apache config, and then use the $_SERVER variable in PHP to test what environment your are currently working on.
Nice and simple.
A small example is this (taken from my development machine):
Just add the following piece of code to the bottom of your httpd.conf file
SetEnv APPLICATION_ENV "development"

And in PHP you can do the following when initializing the database connection:

<?php
  // development machine
  if($_SERVER['APPLICATION_ENV'] == "development") {
    mysql_connect(HOST, USER, PASSWORD);
    mysql_select_db(DATABASENAME);
  }
  // add as many environments as you want
  // the ELSE part is used for production. On shared hosts you cannot edit the
  // httpd.conf file, so if nothing is set, assume it's Production ;)
  else {
    mysql_connect(PRODUCTION_HOST, PRODUCTION_USER, PRODUCTION_PASSWORD);
    mysql_select_db(PRODUCTION_DATABASENAME);
  }
?>

I have a “unit” environment as well, which I use when testing database models using phpunit tests. This makes it easier to wipe database tables when starting tests.

And I switched from black coffee to Lattes.
No idea why, but after drinking black coffee for about 10years, they suddenly seem a bit boring…

Fixing symlinks in Mac OS 10 + built in Apache

The other night I was fiddling about with my macbook, trying to setup a “MySQL-Apache-PHP” server on my system. Digging a bit about the system and googling a bit, it seems that the system comes with a built in apache server and php installation.

I thought to myself, why not use the built in server instead of installing a MAMP package?

Anyways, seconds went by and I found the option in the system preferences, turned on “web sharing” and excitedly headed on to localhost. It worked perfectly, now I just needed to set it up as it was on my linux desktop.

The apache’s httpd.conf file is located in the folder: /private/etc/apache2/httpd.conf
(I used textmate to edit the file.)

What I wanted to do was, since I’m the only user on this laptop, I wanted my Sites folder to have a www “folder”, which was a symlink to a folder in my Documents folder. The symlink would then point to the webapp I was currently coding.

This worked on my linux desktop, so it should also work here.

The first thing I did was to fix the DocumentRoot so it now points directly to my /Users/username/Sites/www folder (or actually the symlink, which we’ll create now).
The I added PHP support by removing the comments from line 116, containing:

LoadModule php5_module         libexec/apache2/libphp5.so

Then I went into my Sites folder and created a symlink to a folder in the Documents folder called “current_project” (yes yes, secret stuff here). This is done using the following command:

ln -s ~/Documents/current_project ~/Sites/www

After that, just reboot you apache server (you can use the Web Sharing option in the System Preferences. Just uncheck the web sharing checkbox and recheck it again).

An here my problems began. I got a lot of warnings in my apache error log (/private/var/log/apache2/error_log) saying:

Symbolic link not allowed or link target not accessible: /Users/username/Sites/www

After googling for a while with no answers, I thought : “why not disable the userdir module? I’m not going to use the localhost/~username anyways”

This turned out to do the trick.

The solution was simply to disable the userdir module in apache.
Comment out line 112 (LoadModule userdir_module libexec/apache2/mod_userdir.so) and line 465 (Include /private/etc/apache2/extra/httpd-userdir.conf)
Restart the apache server (web sharing thingy in the System preferences interface) and you server can now access the symlink.

I hope this will help you on your way, when coding using a Mac.

Small update: (2010-06-02 at 13:15)
Remember to right click the Documents folder and choose “Get Info”. Unfold “Sharing & Permissions” and set “Everyone” to “Read only”, else you will get a permission denied