Config.php CI Explained

I’ve stopped work on this series to start a new one for CI 3.

Introduction

Config.php — This post is part of a series which explains the CodeIgniter (CI) source code. This post explains the CI 2.1.3 Config.php .

./system/core/Config.php

Although, prior to loading the Config library, the code did access the configuration. From this point forward, the code will rely on this library to know the configuration.

The Script’s Boilerplate

<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
 * CodeIgniter
 *
 * An open source application development framework for PHP 5.1.6 or newer
 *
 * @package		CodeIgniter
 * @author		ExpressionEngine Dev Team
 * @copyright	Copyright (c) 2008 - 2011, EllisLab, Inc.
 * @license		http://codeigniter.com/user_guide/license.html
 * @link		http://codeigniter.com
 * @since		Version 1.0
 * @filesource
 */

// ------------------------------------------------------------------------

/**
 * CodeIgniter Config Class
 *
 * This class contains functions that enable config files to be managed
 *
 * @package		CodeIgniter
 * @subpackage	Libraries
 * @category	Libraries
 * @author		ExpressionEngine Dev Team
 * @link		http://codeigniter.com/user_guide/libraries/config.html
 */
class CI_Config {

Code: $config

/**
 * List of all loaded config values
 *
 * @var array
 */
var $config = array();

Declare the array property $config which is a list of all loaded config values.

Code: $is_loaded

/**
 * List of all loaded config files
 *
 * @var array
 */
var $is_loaded = array();

Declare the array property $is_loaded which is a list of all loaded config files.

Code: $_config_paths

/**
 * List of paths to search when trying to load a config file
 *
 * @var array
 */
var $_config_paths = array(APPPATH);

Declare array property $_config_paths which is a list of paths to search when trying to load a config file.

The first element is set to APPPATH .

Code: __construct()

Summary: __construct() loads the property $config with configuration from config.php — with one enhancement — it assigns a proper base_url configuration setting — if the current one is not proper. Also, logs the fact that the cofiguration class loaded.

	/**
	 * Constructor
	 *
	 * Sets the $config data from the primary config.php file as a class variable
	 *
	 * @access   public
	 * @param   string	the config file name
	 * @param   boolean  if configuration values should be loaded into their own section
	 * @param   boolean  true if errors should just return false, false if an error message should be displayed
	 * @return  boolean  if the file was successfully loaded or not
	 */
	function __construct()
	{
		$this->config =& get_config();
		log_message('debug', "Config Class Initialized");

		// Set the base_url automatically if none was provided
		if ($this->config['base_url'] == '')
		{
			if (isset($_SERVER['HTTP_HOST']))
			{
				$base_url = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off' ? 'https' : 'http';
				$base_url .= '://'. $_SERVER['HTTP_HOST'];
				$base_url .= str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']);
			}

			else
			{
				$base_url = 'http://localhost/';
			}

			$this->set_item('base_url', $base_url);
		}
	}

I’ll explain it piece-by-piece.

$this->config =& get_config();

get_config() is one of the common functions. It lets us grab the configuration file even if the configuration class hasn’t been instantiated yet.

get_config() — called without parameters — will return (by-reference) the array generated from the main configuration file (specific to the ENVIRONMENT.) See my post which explains this function for a more detaileds.

So, the $config property at (this point) gets an array with the type of configuration information that a call to the common function get_config() returns.

log_message('debug', "Config Class Initialized");

Make a log entry indicating that the configuration class has been initialized.

// Set the base_url automatically if none was provided
if ($this->config['base_url'] == '')
{
	if (isset($_SERVER['HTTP_HOST']))
	{
		$base_url = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off' ? 'https' : 'http';
		$base_url .= '://'. $_SERVER['HTTP_HOST'];
		$base_url .= str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']);
	}

	else
	{
		$base_url = 'http://localhost/';
	}

	$this->set_item('base_url', $base_url);
}

This code takes a look at the configured base_url . If it’s an empty string then it will assign it something. This “something” will either be the string 'http://localhost/' or a similar string which is based on information gathered about the currently executing (main) script.

Code: load()

/**
 * Load Config File
 *
 * @access	public
 * @param	string	the config file name
 * @param   boolean  if configuration values should be loaded into their own section
 * @param   boolean  true if errors should just return false, false if an error message should be displayed
 * @return	boolean	if the file was loaded correctly
 */
function load($file = '', $use_sections = FALSE, $fail_gracefully = FALSE)
{
	$file = ($file == '') ? 'config' : str_replace('.php', '', $file);
	$found = FALSE;
	$loaded = FALSE;

	$check_locations = defined('ENVIRONMENT')
		? array(ENVIRONMENT.'/'.$file, $file)
		: array($file);

	foreach ($this->_config_paths as $path)
	{
		foreach ($check_locations as $location)
		{
			$file_path = $path.'config/'.$location.'.php';

			if (in_array($file_path, $this->is_loaded, TRUE))
			{
				$loaded = TRUE;
				continue 2;
			}

			if (file_exists($file_path))
			{
				$found = TRUE;
				break;
			}
		}

		if ($found === FALSE)
		{
			continue;
		}

		include($file_path);

		if ( ! isset($config) OR ! is_array($config))
		{
			if ($fail_gracefully === TRUE)
			{
				return FALSE;
			}
			show_error('Your '.$file_path.' file does not appear to contain a valid configuration array.');
		}

		if ($use_sections === TRUE)
		{
			if (isset($this->config[$file]))
			{
				$this->config[$file] = array_merge($this->config[$file], $config);
			}
			else
			{
				$this->config[$file] = $config;
			}
		}
		else
		{
			$this->config = array_merge($this->config, $config);
		}

		$this->is_loaded[] = $file_path;
		unset($config);

		$loaded = TRUE;
		log_message('debug', 'Config file loaded: '.$file_path);
		break;
		}

	if ($loaded === FALSE)
	{
		if ($fail_gracefully === TRUE)
		{
			return FALSE;
		}
		show_error('The configuration file '.$file.'.php does not exist.');
	}

	return TRUE;
}

Step-by-step explanation:

function load($file = '', $use_sections = FALSE, $fail_gracefully = FALSE)

load is the name of the method.

$file — a parameter — string the name of your config file, without the .php file extension.

$use_sections — a parameter — boolean if configuration values should be loaded into their own section

$fail_gracefully — a parameter — boolean true if errors should just return false, false if an error message should be displayed

$file = ($file == '') ? 'config' : str_replace('.php', '', $file);

This makes $file = 'config' if $file == ''; otherwise, it will delete the .php extension if it’s there.

Don’t use load() with an empty string first parameter unless you know what you’re doing. It is not harmless — it will overwrite things.

$found = FALSE;
$loaded = FALSE;

Sets these two flags.

$check_locations = defined('ENVIRONMENT')
		? array(ENVIRONMENT.'/'.$file, $file)
		: array($file);

If ENVIRONMENT is defined then $check_locations = array(ENVIRONMENT.'/'.$file, $file)

Otherwise, $check_locations = array($file)

foreach ($this->_config_paths as $path)
{

This starts a loop where each iteration has a value for $path which comes from the array $this->_config_paths . At the very least this array will contain APPPATH .

Because $this->_config_paths is a property it may have accumulated array elements from statements which have executed beyond the scope of this load method.

foreach ($check_locations as $location)
{

This starts a loop where each iteration has a value for $location which comes from the array $check_locations . At the very least this array will contain the value assigned above.

Because $check_locations is an array which has method scope it will only contain elements assigned to it within the load method. And (unless the code here is changed) it looks like there will be only one element.

We’re nested two (2) deep.

$file_path = $path.'config/'.$location.'.php';

Assign $file_path a value.

if (in_array($file_path, $this->is_loaded, TRUE))
{

If $file_path is found in $this->is_loaded then …

$loaded = TRUE;
continue 2;

continue 2 tells PHP to skip the rest of the current (outer) loop iteration and continue execution at the condition evaluation and then the beginning of the next iteration.

So … if the current incarnation of $file_path leads to a configuration file which is already loaded then PHP will try to load from a different location.

}

if (file_exists($file_path))
{
	$found = TRUE;
	break;
}

If file_exists($file_path) then $found = TRUE and break out of inner loop.

}

If there are more $check_locations go to the next one.

Otherwise, we’re out of the inner loop.

At this point either a fresh (not previously loaded) configuration file has been found and is ready to be loaded — or no configuration file exists for a $file_path which has any of the $check_locations locations.

The line continue 2 causes search to continue though $found === TRUE. The reason why this is allowed to happen is that although a previous call to load() has loaded the file we also want the current call to load() to load one..

if ($found === FALSE)
{
	continue;
}

If (in the case of the particular $this->_config_paths element) no configuration file could be found then skip to the loop condition — so we can try a different one.

Otherwise …

include($file_path);

Load the configuration file which was found and is associated with the current $this->_config_paths element.

if ( ! isset($config) OR ! is_array($config))
{
	if ($fail_gracefully === TRUE)
	{
		return FALSE;
	}
	show_error('Your '.$file_path.' file does not appear to contain a valid configuration array.');
}

If the file did not properly set up $config then an error has occurred. Depending on $fail_gracefully one of two things will happen. Either show_error() (which terminates the script) will be called . Or, load() returns FALSE.

Scope:

This is too big to pass up. The code we just saw can teach us something about PHP. Notice, we have a file being included from within the load() method. This file is the one specified by $file_path. This file defines $config. Notice that $config is now a local variable for load(); it’s not global. Usually we include files from global scope; and their variables become global. However, note that functions declared within an included file become global — no matter where their file is included from; that’s because functions are global by nature — except when the function is a method; that’s a different story.

if ($use_sections === TRUE)
{
	if (isset($this->config[$file]))
	{
		$this->config[$file] = array_merge($this->config[$file], $config);
	}
	else
	{
		$this->config[$file] = $config;
	}
}
else
{
	$this->config = array_merge($this->config, $config);
}

If the user wants load() to use sections when storing the new configuration items (a.k.a. $config) then load() will oblige by placing the new items in our object’s configuration array at index $file (a.k.a. $this->config[$file]). Note the following:

  1. $this->config[$file] will be an array — whereas you’d normally expect to find strings in $this->config
  2. load() will merge existing items from $this->config['$file'] with the new items if $this->config[$file] already exists. Otherwise, it creates $this->config[$file] and assigns $config (the new items) to it.
  3. load() is doing what it normally does when adding items to the configuration. The only difference is the storage location.

Otherwise, load() will just merge the new configuration items with the old ones in $this->config.

$this->is_loaded[] = $file_path;
unset($config);

$loaded = TRUE;
log_message('debug', 'Config file loaded: '.$file_path);

Save $file_path in $this->is_loaded.

Obliterate $config so it won’t interfere with anything else load() will do.

Set the flag $loaded = TRUE to indicate that load has done what it came here to do.

Make a log entry.

	break;
}

break to make this loop end as soon as possible.

We are outside all loops.

	if ($loaded === FALSE)
	{
		if ($fail_gracefully === TRUE)
		{
			return FALSE;
		}
		show_error('The configuration file '.$file.'.php does not exist.');
	}

	return TRUE;
}

If no file got loaded do the correct thing by $fail_gracefully.

Otherwise, return TRUE.

Code: item()

See UG post — subtopic: Fetching Config Items.

/**
 * Fetch a config file item
 *
 *
 * @access	public
 * @param	string	the config item name
 * @param	string	the index name
 * @param	bool
 * @return	string
 */
function item($item, $index = '')
{
	if ($index == '')
	{
		if ( ! isset($this->config[$item]))
		{
			return FALSE;
		}

		$pref = $this->config[$item];
	}
	else
	{
		if ( ! isset($this->config[$index]))
		{
			return FALSE;
		}

		if ( ! isset($this->config[$index][$item]))
		{
			return FALSE;
		}

		$pref = $this->config[$index][$item];
	}
	return $pref;
}

$item is configuration item name.

$index is configuration file name without extension. This parameter is used only for items loaded with $use_sections === TRUE.

If you’re trying to get the item value using just its name then item() will either find and return it; or return FALSE.

If you are passing both an item name and configuration file name (minus the extension) then you will get the item value unless it was not stored using $use_sections === TRUE — in which case you will get a FALSE.

Code: slash_item()

	/**
	 * Fetch a config file item - adds slash after item (if item is not empty)
	 *
	 * @access	public
	 * @param	string	the config item name
	 * @param	bool
	 * @return	string
	 */
	function slash_item($item)
	{
		if ( ! isset($this->config[$item]))
		{
			return FALSE;
		}
		if( trim($this->config[$item]) == '')
		{
			return '';
		}

		return rtrim($this->config[$item], '/').'/';
	}

slash_item() only returns values for items which were stored the standard way.

It fetches a config file item – adds slash after item (if item is not empty.)

It can return an empty string.

Code: _uri_string()

/**
 * Build URI string for use in Config::site_url() and Config::base_url()
 *
 * @access protected
 * @param  $uri
 * @return string
 */
protected function _uri_string($uri)
{
	if ($this->item('enable_query_strings') == FALSE)
	{
		if (is_array($uri))
		{
			$uri = implode('/', $uri);
		}
		$uri = trim($uri, '/');
	}
	else
	{
		if (is_array($uri))
		{
			$i = 0;
			$str = '';
			foreach ($uri as $key => $val)
			{
				$prefix = ($i == 0) ? '' : '&';
				$str .= $prefix.$key.'='.$val;
				$i++;
			}
			$uri = $str;
		}
	}
    return $uri;
}

_uri_string() is a helper for other methods in CI_Config. It helps site_url() and base_url().

A URI (in CI) is the portion of a URL which specifies the controller, method, and parameters.

The parameter $uri should be one of the following:

  1. a string like /controller/method/param1/param2/
  2. an array like $uri['0'] = 'controller' $uri['1'] = 'method' $uri['2'] = 'param1' $uri['3'] = 'param2'
  3. an array like $uri['cont'] = 'controller' $uri['mtd'] = 'method' $uri['p1'] = 'param1' $uri['p2'] = 'param2'

If our installation is configured to work with segment based URLs then _uri_string() will return something like controller/method/param1/param2

Otherwise, _uri_string() will return something like cont=controller&mtd=method&p1=param1&p2=param2

Code: site_url()

/**
 * Site URL
 * Returns base_url . index_page [. uri_string]
 *
 * @access	public
 * @param	string	the URI string
 * @return	string
 */
function site_url($uri = '')
{
	if ($uri == '')
	{
		return $this->slash_item('base_url').$this->item('index_page');
	}

	if ($this->item('enable_query_strings') == FALSE)
	{
		$suffix = ($this->item('url_suffix') == FALSE) ? '' : $this->item('url_suffix');
		return $this->slash_item('base_url').$this->slash_item('index_page').$this->_uri_string($uri).$suffix;
	}
	else
	{
		return $this->slash_item('base_url').$this->item('index_page').'?'.$this->_uri_string($uri);
	}
}

The two methods site_url() and base_url() are normally accessed via the corresponding functions in the URL Helper.

See my explanation of _uri_string() above to learn what a URI is (in the context of CodeIgniter.)

The most basic use of site_url() is to retrieve a string consisting of the URL to your site concatenated with the “index” value you’ve specified in the config file.

Also site_url() can produce this same string terminated with a URI (you specify.) Although the value you supply for $uri may take any one of several forms (string, enumerated array or associative array,) the URI in the returned URL will be appropriate. As a matter of fact it will abide by the configuration setting enable_query_strings.

Another thing — a suffix (.php, .html, etc.) will be added to the end of the URL if you have configured CI to do so.

Code: base_url()

/**
 * Base URL
 * Returns base_url [. uri_string]
 *
 * @access public
 * @param string $uri
 * @return string
 */
function base_url($uri = '')
{
	return $this->slash_item('base_url').ltrim($this->_uri_string($uri), '/');
}

base_url() does the same thing site_url() does except:

  • The URL won’t include a suffix (even if you put it in configuration.)
  • The URL won’t include the front controller file name.
  • The URL won’t include a question mark at the start of a URI query string
  • I don’t think base_url() works when CI is configured for query string URIs.

Code: system_url()

/**
 * System URL
 *
 * @access	public
 * @return	string
 */
function system_url()
{
	$x = explode("/", preg_replace("|/*(.+?)/*$|", "\\1", BASEPATH));
	return $this->slash_item('base_url').end($x).'/';
}

This method retrieves the URL to your system folder.

Yes! You read that correctly. It will take the base_url from configuration and slap on it the name of the system folder which got specified within the front controller. The reason for all the fancy code is that it can’t just access the value for $system_path (name of system folder) directly since this variable is out of scope. Constants such as BASEPATH don’t go out of scope. Since the developer writes code within the controller method he/she doesn’t have access to variables defined in the global scope.

I have no idea why someone would want a URL for the system folder. That folder is for storing system files only. No one would need to access it from a web browser.

You don’t need to know what the following code snippet does but I’ll tell you in case you’re curious.

preg_replace("|/*(.+?)/*$|", "\\1", BASEPATH)

gives you BASEPATH with a trim of / from both ends

Code: set_item()

/**
 * Set a config file item
 *
 * @access	public
 * @param	string	the config item key
 * @param	string	the config item value
 * @return	void
 */
function set_item($item, $value)
{
	$this->config[$item] = $value;
}

Code: _assign_to_config()

/**
 * Assign to Config
 *
 * This function is called by the front controller (CodeIgniter.php)
 * after the Config class is instantiated.  It permits config items
 * to be assigned or overriden by variables contained in the index.php file
 *
 * @access	private
 * @param	array
 * @return	void
 */
function _assign_to_config($items = array())
{
	if (is_array($items))
	{
		foreach ($items as $key => $val)
		{
			$this->set_item($key, $val);
		}
	}
}

Advertisements

About samehramzylabib

See About on https://samehramzylabib.wordpress.com
This entry was posted in CI Source Code Explained. Bookmark the permalink.

One Response to Config.php CI Explained

  1. Pingback: Bootstrap Source Code for CI Explained | Sam's PHP

Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s