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;
// Global definitions
$parts = explode(DIRECTORY_SEPARATOR, JPATH_BASE);
//maps to custom configuration files in configurations folders: local, production, staging
$location = ConfigurationLocation::getLocation();
define('JPATH_ROOT', implode(DIRECTORY_SEPARATOR, $parts));
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.)
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();
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;
protected function _containsDirectory($search, $dirConstant)
return (strpos($search, $dirConstant) !== false);
#3 Add "Configurations" Directory to House Each Configuration.php
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:
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!