Stretching Configuration.php for Git

Written by | 05 April 2016 | Published in 2016 April
My company worked on a web application that was basically an extension of the Joomla 2.5 core a few years ago.  We took over the app from another development company that had gotten stuck (we never would have modified the core.) It was being deployed to AWS and the original developers altered the configuration.php file to return different values from their local environments based upon environmental variables set by Amazon's cloud servers. When I saw this I thought, "There has got to be a better way."
Stretching Configuration.php for Git Original by Jeff Peterson as posted to Flickr: http://is.gd/tpvgSJ. Used under CC https://creativecommons.org/licenses/by/2.0/

These days it's common for developers to deploy applications to several different environments and to share copies of source code using Git. It can be a pain to make sure that the configuration.php file is always ignored by everybody and to set up specific configuration.php files for different environments (e.g. "staging", "local", "production".)

Fortunately, Joomla 3 is flexible enough to make using multiple configuration.php files relatively easy. In this article, I'll show you how to liberate your configuration.php file from the web root and create multiple copies of it so that you don't have to mess around with this. Best of all, no core changes needed.

A disclaimer: though we are not making changes to the core, we are heading off road, so this is an article specifically for developers who understand what is occurring with these changes and how they impact the application.

Re-Wiring Joomla Definitions

As the Joomla application loads, it looks for a file that does not exist in the web root named defines.php.  When it does not find it, it loads its own from the includes folder.

The defines.php file is used to set several constants for the environment including paths to the libraries folder, the administrator folder, and the templates folder. Of particular help for our situation is that it sets the path to the configuration.php file.

We are going to take advantage of this by creating our own defines.php file and set a custom configuration path. However, instead of just moving the configuration file somewhere else, we are going to use a "location" class that inspects the file path and uses that to determine a specific configuration file to load.

Why would we want to do this?

When a developer on a workstation loads up their version of the site, our class will match their local directory to a specific configuration file for their local database. Without changing any of the files, when that same set of code is deployed to a staging directory on a server somewhere else, the class will match that directory structure to the staging database there. Finally, when we load it into the production environment, it will recognize that architecture and load the matching configuration.php file there. Not only can we match specific database credentials to their environments, we can also tweak the other configuration variables to optimize working in those environments (enabling caching on production, but not in development, using a real mailer in production, but a mock one in development, and etc., etc.)

Another benefit of this approach is that we can use a boilerplate .gitignore for every project and don't have to worry about configuration.php being added or altered in a branch (It's worth noting that there are security considerations for including sensitive information like database credentials in version control.)

Let's Do It!

Here is how it looks in practice:

#1 Add Custom Defines.php To Web Root and Administrator Directory

This is an excerpt from the top half of our custom defines.php.  Note the location class and how the return value is used to load a specific directory for configuration.php.

defined('_JEXEC') or die;

define('JPATH_BASE', __DIR__);

// Global definitions
$parts = explode(DIRECTORY_SEPARATOR, JPATH_BASE);

//maps to custom configuration files in configurations folders: local, production, staging
require_once(JPATH_BASE .'/configurations/location.php');
$location = ConfigurationLocation::getLocation();

define('_JDEFINES', true);

// Defines.
define('JPATH_ROOT', implode(DIRECTORY_SEPARATOR, $parts));
define('JPATH_SITE', JPATH_ROOT);
define('JPATH_CONFIGURATION', JPATH_ROOT . '/configurations/' . $location);

We need to perform the same trick for the administrator folder so that the application loads there as well by adding a custom defines.php to that folder.  The only difference in that custom defines.php file is to make sure  that the paths in it point to the correct locations for definitions and our location class (slightly different because it is in a slightly different location.)

#2 Use "Configuration Location" Class To Match the Environment's Path to Specific Directories

Each will contain unique configuration.php files (A note: we could refactor and clean the location class up, but it's simple and works for now.)

class ConfigurationLocation
{
CONST PRODUCTION_DIR = '/home/production';
CONST STAGING_DIR = '/home/staging';
CONST JOHN_DIR = '/home/john/';
CONST SARA_DIR = '/var/www/project1';
CONST PRODUCTION = 'production';
CONST JOHN = 'john';
CONST STAGING = 'staging';
CONST SARA = 'sara';

static public function getLocation()
{
$location = new ConfigurationLocation();
$homeDir = $location->_getHomeDir();
return $location->_searchConfigurations($homeDir);
}

protected function _getHomeDir()
{
$home = $this->_getHomeDirFromDocumentRoot();
return ($home) ? $home : getcwd();
}

protected function _getHomeDirFromDocumentRoot()
{
return ($_SERVER['DOCUMENT_ROOT'] !== '') ? $_SERVER['DOCUMENT_ROOT'] : false;
}

protected function _searchConfigurations($dir)
{
if ($this->_containsDirectory($dir, static::PRODUCTION_DIR)) return static::PRODUCTION;
elseif ($this->_containsDirectory($dir, static::STAGING_DIR)) return static::STAGING;
elseif ($this->_containsDirectory($dir, static::JOHN_DIR)) return static::JOHN;
elseif ($this->_containsDirectory($dir, static::SARA_DIR)) return static::SARA;
return static::PRODUCTION;
}

protected function _containsDirectory($search, $dirConstant)
{
return (strpos($search, $dirConstant) !== false);
}
}

#3 Add "Configurations" Directory to House Each Configuration.php

Configuration directories

Sample Files

If you want a head start on your own projects using this approach, you can get a copy of a sample custom defines.php file and the location class here:  

Multiple Configurations for Joomla Sample Files

Joomla is the Limbo Queen

This is one example of how the flexibility of Joomla's architecture can be helpful. Being able to redefine core paths opens up other options as well: moving the configuration file and the libraries above the web root, renaming the administrator or template folder to improve security, running multiple sites off of a common core, and etc. When you combine this with lazy loading, template overrides, HMVC components, event based plug-ins, modules, and extensibility you have a very powerful tool for crafting all sorts of solutions. As a developer, this makes me uber grateful.

Thanks Joomla core team!

 

Read 5497 times Tagged under Developers, English
John Hooley

John Hooley

I'm the author of the popular online guides:

  • How to Fix a Hacked Joomla Website (http://www.bluebridgedev.com/joomla-hacked)
  • Speed Up Joomla (http://www.bluebridgedev.com/speed-up-joomla)
  • Joomla Best Practices Checklist (http://www.bluebridgedev.com/joomla-checklist)

I also write on business for the freelance web developer on my blog at Knight Errant (knighterrant.co)

I've contributed to the Joomla project as a bug squasher, JUG leader, JCM author, and extension developer.

Social Profiles

LinkedIn