Common.php CI Explained

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

Introduction

Common.php loads the global functions. This post is part of a series which explains the CodeIgniter (CI) source code. This post explains the CI 2.1.2 Common.php.

Location

Typical location is ./system/core/Common.php

Comment

<?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
 */

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

The same as for all previously talked about scripts.

Comment

/**
 * Common Functions
 *
 * Loads the base classes and executes the request.
 *
 * @package		CodeIgniter
 * @subpackage	codeigniter
 * @category	Common Functions
 * @author		ExpressionEngine Dev Team
 * @link		http://codeigniter.com/user_guide/
 */

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

This is the header comment which relates directly to this script.

Code: is_php()

/**
* Determines if the current version of PHP is greater then the supplied value
*
* Since there are a few places where we conditionally test for PHP > 5
* we'll set a static variable.
*
* @access	public
* @param	string
* @return	bool	TRUE if the current version is $version or higher
*/
if ( ! function_exists('is_php'))
{
	function is_php($version = '5.0.0')
	{
		static $_is_php;
		$version = (string)$version;

		if ( ! isset($_is_php[$version]))
		{
			$_is_php[$version] = (version_compare(PHP_VERSION, $version) < 0) ? FALSE : TRUE;
		}

		return $_is_php[$version];
	}
}

Defines is_php() and $_is_php[]

Using static variables

Another important feature of variable scoping is the static variable. A static variable exists only in a local function scope, but it does not lose its value when program execution leaves this scope.

is_php() determines if the PHP version being used is greater than the supplied version_number.

Returns boolean TRUE if the installed version of PHP is equal to or greater than the supplied version number. Returns FALSE if the installed version of PHP is lower than the supplied version number.

As side effect this function stores its return value in the static variable $_is_php[] at index version_number. This is save the function from having to do the same work if it is called again with the same parameter.

Code: is_really_writable()

/**
 * Tests for file writability
 *
 * is_writable() returns TRUE on Windows servers when you really can't write to
 * the file, based on the read-only attribute.  is_writable() is also unreliable
 * on Unix servers if safe_mode is on.
 *
 * @access	private
 * @return	void
 */
if ( ! function_exists('is_really_writable'))
{
	function is_really_writable($file)
	{
		// If we're on a Unix server with safe_mode off we call is_writable
		if (DIRECTORY_SEPARATOR == '/' AND @ini_get("safe_mode") == FALSE)
		{
			return is_writable($file);
		}

		// For windows servers and safe_mode "on" installations we'll actually
		// write a file then read it.  Bah...
		if (is_dir($file))
		{
			$file = rtrim($file, '/').'/'.md5(mt_rand(1,100).mt_rand(1,100));

			if (($fp = @fopen($file, FOPEN_WRITE_CREATE)) === FALSE)
			{
				return FALSE;
			}

			fclose($fp);
			@chmod($file, DIR_WRITE_MODE);
			@unlink($file);
			return TRUE;
		}
		elseif ( ! is_file($file) OR ($fp = @fopen($file, FOPEN_WRITE_CREATE)) === FALSE)
		{
			return FALSE;
		}

		fclose($fp);
		return TRUE;
	}
}

I’m not going to try to understand the code; However, I’ll write what is known about it.

is_really_writable() is an alternative to the built in PHP function called is_writable()

See Common Functions.

Code: load_class()

/**
* Class registry
*
* This function acts as a singleton.  If the requested class does not
* exist it is instantiated and set to a static variable.  If it has
* previously been instantiated the variable is returned.
*
* @access	public
* @param	string	the class name being requested
* @param	string	the directory where the class should be found
* @param	string	the class name prefix
* @return	object
*/
if ( ! function_exists('load_class'))
{
	function &load_class($class, $directory = 'libraries', $prefix = 'CI_')
	{
		static $_classes = array();

		// Does the class exist?  If so, we're done...
		if (isset($_classes[$class]))
		{
			return $_classes[$class];
		}

		$name = FALSE;

		// Look for the class first in the local application/libraries folder
		// then in the native system/libraries folder
		foreach (array(APPPATH, BASEPATH) as $path)
		{
			if (file_exists($path.$directory.'/'.$class.'.php'))
			{
				$name = $prefix.$class;

				if (class_exists($name) === FALSE)
				{
					require($path.$directory.'/'.$class.'.php');
				}

				break;
			}
		}

		// Is the request a class extension?  If so we load it too
		if (file_exists(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php'))
		{
			$name = config_item('subclass_prefix').$class;

			if (class_exists($name) === FALSE)
			{
				require(APPPATH.$directory.'/'.config_item('subclass_prefix').$class.'.php');
			}
		}

		// Did we find the class?
		if ($name === FALSE)
		{
			// Note: We use exit() rather then show_error() in order to avoid a
			// self-referencing loop with the Excptions class
			exit('Unable to locate the specified class: '.$class.'.php');
		}

		// Keep track of what we just loaded
		is_loaded($class);

		$_classes[$class] = new $name();
		return $_classes[$class];
	}
}

Cross Reference

What does load_class() do?

The short answer: This function acts as a singleton. If the requested class does not exist then it is instantiated and set to a static variable — then returned by reference. If an object of that class has previously been instantiated then a reference to it is returned.

The long answer:

Its parameters specify a class and that class’s file.

Its return value is returned pass-by-reference.

It creates $_classes — a static variable.

$_classes is an array.

$_classes is an associative array of objects. Each object corresponds to a class which has already been instantiated. There can only be one object per class in this array.

The previously existing object of the specified class is returned by reference (and the function terminates) if the object exists.

I believe an ampersand (&) should be used when assigning the return value to a variable. See return by reference above.

NOTE: If you see functions that are being used within this function which you haven’t seen declared THEN look farther down in the script to find their definition. Yes this works in PHP!

$name is a local variable—initialized to FALSE.

The class file will be loaded either from the application folder or the system folder. If the class file is not in the application folder then it will be loaded from the system folder. Note: I’m oversimplifying it a bit—see the code.

$name is assigned the class name right before the class file is loaded.

The class extension file will also be loaded if it exists.

Again, $name is assigned the extension class name right before the extension class file is loaded.

Note:

An (object) instantiation of the extension class is the one being loaded into the $_classes array—at an index consisting of the simple class name. Also, when the class is registered using is_loaded(), only the simple class name is used. Note: the term simple class name refers to the class name without any prefix.

Ramification: Use the class name without its prefix when you want to refer to the class. For example you would do so when searching for the class in the $_classes array—or, when you need to use the class name in some CI functions.

Code: is_loaded()

/**
* Keeps track of which libraries have been loaded.  This function is
* called by the load_class() function above
*
* @access	public
* @return	array
*/
if ( ! function_exists('is_loaded'))
{
	function is_loaded($class = '')
	{
		static $_is_loaded = array();

		if ($class != '')
		{
			$_is_loaded[strtolower($class)] = $class;
		}

		return $_is_loaded;
	}
}

is_loaded() builds the static array $_is_loaded—which is an associative array of library names. These are the libraries which have been loaded. Note: library names are the simple class names of the libraries. This function is called by the load_class() function above. Each time is_loaded() is called with the name of a library this library is added to the array. Note: the index is all small letters but the value isn’t.

Code: get_config()

/**
* Loads the main config.php file
*
* This function lets us grab the config file even if the Config class
* hasn't been instantiated yet
*
* @access	private
* @return	array
*/
if ( ! function_exists('get_config'))
{
	function &get_config($replace = array())
	{
		static $_config;

		if (isset($_config))
		{
			return $_config[0];
		}

		// Is the config file in the environment folder?
		if ( ! defined('ENVIRONMENT') OR ! file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/config.php'))
		{
			$file_path = APPPATH.'config/config.php';
		}

		// Fetch the config file
		if ( ! file_exists($file_path))
		{
			exit('The configuration file does not exist.');
		}

		require($file_path);

		// Does the $config array exist in the file?
		if ( ! isset($config) OR ! is_array($config))
		{
			exit('Your config file does not appear to be formatted correctly.');
		}

		// Are any values being dynamically replaced?
		if (count($replace) > 0)
		{
			foreach ($replace as $key => $val)
			{
				if (isset($config[$key]))
				{
					$config[$key] = $val;
				}
			}
		}

		return $_config[0] =& $config;
	}
}

get_config() returns (by reference) a reference for the $config array. Yes! This is the same array defined in config.php. See return by reference above.

Exactly what does get_config() do?

It returns by reference. See returns by reference above.

	function &get_config($replace = array())

It takes an array as a parameter. This parameter defaults to an empty array.

	{
		static $_config;

$_config is defined to be a static variable—see above related material about static variables.

Note: this statement does not cause $_config to exist.

		if (isset($_config))
		{
			return $_config[0];
		}

IF $_config already exists THEN return by reference the first element of that array. See how to properly complete a return by reference.

The first element of $_config has special meaning. $_config[0] =& $config

So,
there is no real work to be done IF $_config already exists—because that would mean this function ran before; and, all its output is in $config (not to be confused with $_config.)

		// Is the config file in the environment folder?
		if ( ! defined('ENVIRONMENT') OR ! file_exists($file_path = APPPATH.'config/'.ENVIRONMENT.'/config.php'))
		{
			$file_path = APPPATH.'config/config.php';
		}

Define local variable $file_path to be the path to the config.php file. The code will first attempt to define it as the ENVIRONMENT specific file—this fails if ENVIRONMENT is not defined or if there is no config.php for the environment specified by ENVIRONMENT.

		// Fetch the config file
		if ( ! file_exists($file_path))
		{
			exit('The configuration file does not exist.');
		}

		require($file_path);

This will run config.php

config.php will define stuff like this:

  • $config['base_url']
  • $config['index_page']
  • $config['uri_protocol']
		// Does the $config array exist in the file?
		if ( ! isset($config) OR ! is_array($config))
		{
			exit('Your config file does not appear to be formatted correctly.');
		}

Error out if didn’t get $config array.

		// Are any values being dynamically replaced?
		if (count($replace) > 0)
		{
			foreach ($replace as $key => $val)
			{
				if (isset($config[$key]))
				{
					$config[$key] = $val;
				}
			}
		}

The input parameter $replace is used to dynamically alter the $config array.

		return $_config[0] =& $config;
	}

Return $config by reference and assign this reference to $_config[0]—so we don’t have to go through this mess again. See returns by reference above.

Code: config_item()

/**
* Returns the specified config item
*
* @access	public
* @return	mixed
*/
if ( ! function_exists('config_item'))
{
	function config_item($item)
	{
		static $_config_item = array();

		if ( ! isset($_config_item[$item]))
		{
			$config =& get_config();

			if ( ! isset($config[$item]))
			{
				return FALSE;
			}
			$_config_item[$item] = $config[$item];
		}

		return $_config_item[$item];
	}
}

config_item() takes a label for a CI config item and returns the value.

Code: show_error()

/**
* Error Handler
*
* This function lets us invoke the exception class and
* display errors using the standard error template located
* in application/errors/errors.php
* This function will send the error page directly to the
* browser and exit.
*
* @access	public
* @return	void
*/
if ( ! function_exists('show_error'))
{
	function show_error($message, $status_code = 500, $heading = 'An Error Was Encountered')
	{
		$_error =& load_class('Exceptions', 'core');
		echo $_error->show_error($heading, $message, 'error_general', $status_code);
		exit;
	}
}

What show_error() basically does:

Error Handler—This function lets us invoke the exception class and display errors using the standard error template located in application/errors/errors.php . This function will send the error page directly to the browser and exit.

Note: there is NO application/errors/errors.php file. However, there is a application/errors/ folder with error_404.php, error_db.php, error_general.php and error_php.php .

Really? What’s the code doing?

	function show_error($message, $status_code = 500, $heading = 'An Error Was Encountered')

show_error is the function name.

$message is a parameter. A string.

$status_code is a parameter. An integer defaulting to 500.

$heading is a parameter. A string defaulting to 'An Error Was Encountered'.

		$_error =& load_class('Exceptions', 'core');

$_error is a local variable.

$_error is a variable reference which refers to the returned variable coming from load_class().

load_class() will return (by variable reference) an Exceptions object. The Exceptions class is defined in the core directory; And, the class prefix is CI_ .

		echo $_error->show_error($heading, $message, 'error_general', $status_code);

Calls the show_error() method of the singleton Exceptions object.

This method is passed the following parameters:

$heading which was originally passed to the show_error function.

$message which was originally passed to the show_error function.

'error_general' a string which specifies the error display file to be used.

$status_code which was originally passed to the show_error function.

The script sends the string (an HTML page) to the browser. This string comes from the call to show_error().

		exit;

Terminate the script.

Note: there is no exception handling or any capturing of PHP errors. The function simply displays an error page using the information we gave it and some page templates.

Note:the show_error() function (I’m NOT talking about the show_error method) is hard wired to use the error_general.php page template. So it’s not really meant to be used with database, PHP or 404 errors.

Code: show_404()

/**
* 404 Page Handler
*
* This function is similar to the show_error() function above
* However, instead of the standard error template it displays
* 404 errors.
*
* @access	public
* @return	void
*/
if ( ! function_exists('show_404'))
{
	function show_404($page = '', $log_error = TRUE)
	{
		$_error =& load_class('Exceptions', 'core');
		$_error->show_404($page, $log_error);
		exit;
	}
}

This is pretty much a function wrapper for the Exceptions show_404 method. The only extra thing it does is terminate the script.

Code: log_message()

/**
* Error Logging Interface
*
* We use this as a simple mechanism to access the logging
* class and send messages to be logged.
*
* @access	public
* @return	void
*/
if ( ! function_exists('log_message'))
{
	function log_message($level = 'error', $message, $php_error = FALSE)
	{
		static $_log;

		if (config_item('log_threshold') == 0)
		{
			return;
		}

		$_log =& load_class('Log');
		$_log->write_log($level, $message, $php_error);
	}
}

This is a function wrapper for a method.

Code: set_status_header()

/**
 * Set HTTP Status Header
 *
 * @access	public
 * @param	int		the status code
 * @param	string
 * @return	void
 */
if ( ! function_exists('set_status_header'))
{
	function set_status_header($code = 200, $text = '')
	{
		$stati = array(
							200	=> 'OK',
							201	=> 'Created',
							202	=> 'Accepted',
							203	=> 'Non-Authoritative Information',
							204	=> 'No Content',
							205	=> 'Reset Content',
							206	=> 'Partial Content',

							300	=> 'Multiple Choices',
							301	=> 'Moved Permanently',
							302	=> 'Found',
							304	=> 'Not Modified',
							305	=> 'Use Proxy',
							307	=> 'Temporary Redirect',

							400	=> 'Bad Request',
							401	=> 'Unauthorized',
							403	=> 'Forbidden',
							404	=> 'Not Found',
							405	=> 'Method Not Allowed',
							406	=> 'Not Acceptable',
							407	=> 'Proxy Authentication Required',
							408	=> 'Request Timeout',
							409	=> 'Conflict',
							410	=> 'Gone',
							411	=> 'Length Required',
							412	=> 'Precondition Failed',
							413	=> 'Request Entity Too Large',
							414	=> 'Request-URI Too Long',
							415	=> 'Unsupported Media Type',
							416	=> 'Requested Range Not Satisfiable',
							417	=> 'Expectation Failed',

							500	=> 'Internal Server Error',
							501	=> 'Not Implemented',
							502	=> 'Bad Gateway',
							503	=> 'Service Unavailable',
							504	=> 'Gateway Timeout',
							505	=> 'HTTP Version Not Supported'
						);

		if ($code == '' OR ! is_numeric($code))
		{
			show_error('Status codes must be numeric', 500);
		}

		if (isset($stati[$code]) AND $text == '')
		{
			$text = $stati[$code];
		}

		if ($text == '')
		{
			show_error('No status text available.  Please check your status code number or supply your own message text.', 500);
		}

		$server_protocol = (isset($_SERVER['SERVER_PROTOCOL'])) ? $_SERVER['SERVER_PROTOCOL'] : FALSE;

		if (substr(php_sapi_name(), 0, 3) == 'cgi')
		{
			header("Status: {$code} {$text}", TRUE);
		}
		elseif ($server_protocol == 'HTTP/1.1' OR $server_protocol == 'HTTP/1.0')
		{
			header($server_protocol." {$code} {$text}", TRUE, $code);
		}
		else
		{
			header("HTTP/1.1 {$code} {$text}", TRUE, $code);
		}
	}
}

This function sends the specified status header to the web server. If the text for it is not specified then it will find it in its internal array.

I guess the status will also be conveyed to the browser. Whether or not the browser displays an error message will depend on the rest of the content which will be sent to it.

Code: _exception_handler()

/**
* Exception Handler
*
* This is the custom exception handler that is declaired at the top
* of Codeigniter.php.  The main reason we use this is to permit
* PHP errors to be logged in our own log files since the user may
* not have access to server logs. Since this function
* effectively intercepts PHP errors, however, we also need
* to display errors based on the current error_reporting level.
* We do that with the use of a PHP error template.
*
* @access	private
* @return	void
*/
if ( ! function_exists('_exception_handler'))
{
	function _exception_handler($severity, $message, $filepath, $line)
	{
		 // We don't bother with "strict" notices since they tend to fill up
		 // the log file with excess information that isn't normally very helpful.
		 // For example, if you are running PHP 5 and you use version 4 style
		 // class functions (without prefixes like "public", "private", etc.)
		 // you'll get notices telling you that these have been deprecated.
		if ($severity == E_STRICT)
		{
			return;
		}

		$_error =& load_class('Exceptions', 'core');

		// Should we display the error? We'll get the current error_reporting
		// level and add its bits with the severity bits to find out.
		if (($severity & error_reporting()) == $severity)
		{
			$_error->show_php_error($severity, $message, $filepath, $line);
		}

		// Should we log the error?  No?  We're done...
		if (config_item('log_threshold') == 0)
		{
			return;
		}

		$_error->log_exception($severity, $message, $filepath, $line);
	}
}

Cross reference — to see the class whose method (show_php_error()) is used in the code above.

As the comment says: “This is the custom exception handler that is declared at the top of Codeigniter.php.” Here is what that declaration looks like:

/*
 * ------------------------------------------------------
 *  Define a custom error handler so we can log PHP errors
 * ------------------------------------------------------
 */
	set_error_handler('_exception_handler');

set_error_handler() is a built-in PHP function.

As the comments go on to say, the main reason we use this is to permit PHP errors to be logged in our own log files (since the user may not have access to server logs.)

Additionally, since this function effectively intercepts PHP errors, however, we also need to display errors based on the current error_reporting level. We do that with the use of a PHP error template—from our Exceptions class.

It is insightful to contrast _exception_handler with show_error and show_404. _exception_handler is called by PHP itself (as opposed the programmer’s code calling it.) Also, it does not terminate the script; the errors are displayed but output can still be produced. _exception_handler uses the page template for showing PHP errors.

Now, some explaining of the code.

function _exception_handler($severity, $message, $filepath, $line)

_exception_handler is the name of the function.

$severity, $message, $filepath and $line are its parameters. The values for these parameters come directly from the PHP processor when an error occurs.

Each PHP error will generate its own call to _exception_handler.

// We don't bother with "strict" notices since they tend to fill up
// the log file with excess information that isn't normally very helpful.
// For example, if you are running PHP 5 and you use version 4 style
// class functions (without prefixes like "public", "private", etc.)
// you'll get notices telling you that these have been deprecated.
if ($severity == E_STRICT)
{
	return;
}

If $severity == E_STRICT then we don’t do anything.

$_error =& load_class('Exceptions', 'core');

Make $_error a reference to our Exceptions singleton object.

// Should we display the error? We'll get the current error_reporting
// level and add its bits with the severity bits to find out.
if (($severity & error_reporting()) == $severity)
{
	$_error->show_php_error($severity, $message, $filepath, $line);
}

The call to error_reporting() just returns the current error reporting level—since no argument is supplied.

The condition is basically testing to see if the current error reporting level is the same as the one found in $severity.

I guess that since we’ve disabled PHP’s built-in error_handler we could show all the errors no matter what the error_reporting setting is. But we won’t do that. Here we use this code to display only the errors which comply with the error_reporting level.

Although this code makes sure there is a match between $severity and error_reporting as a condition for displaying the error—this code has no bearing on whether the error will be logged or not logged.

// Should we log the error?  No?  We're done...
if (config_item('log_threshold') == 0)
{
	return;
}

$_error->log_exception($severity, $message, $filepath, $line);

Either we will log errors or we won’t. It depends on config_item('log_threshold').

Code: remove_invisible_characters()

/**
 * Remove Invisible Characters
 *
 * This prevents sandwiching null characters
 * between ascii characters, like Java\0script.
 *
 * @access	public
 * @param	string
 * @return	string
 */
if ( ! function_exists('remove_invisible_characters'))
{
	function remove_invisible_characters($str, $url_encoded = TRUE)
	{
		$non_displayables = array();
		
		// every control character except newline (dec 10)
		// carriage return (dec 13), and horizontal tab (dec 09)
		
		if ($url_encoded)
		{
			$non_displayables[] = '/%0[0-8bcef]/';	// url encoded 00-08, 11, 12, 14, 15
			$non_displayables[] = '/%1[0-9a-f]/';	// url encoded 16-31
		}
		
		$non_displayables[] = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S';	// 00-08, 11, 12, 14-31, 127

		do
		{
			$str = preg_replace($non_displayables, '', $str, -1, $count);
		}
		while ($count);

		return $str;
	}
}

Basically this function sanitizes a string by removing any characters other than the characters you would want to find in an ASCII text document. There is one complication here—which is: If the original string was URL encoded we want to end up with a URL encoded string; however, that string should be sanitized in a similar manner.

Derek Allard’s blog post says:

Generally speaking, you’ll probably be using XSS protection in any place you need this (in fact, CodeIgniter’s xss_clean() function actually calls remove_invisible_characters()), but if you find yourself in a situation where XSS cleaning isn’t warranted, then remove_invisible_characters() might be the way to go.

preg_replace() traverses $str (replacing parts of it which match the regexs found in the array $non_displayables with an empty string.

Q: How does the loop terminate?

A: It terminates when $count becomes zero.

Q: What is the initial value of $count?

A: There isn’t one.

Q: How does the condition ($count) end up evaluating to false causing the do-while to terminate?

A: $count is a pass-by-reference parameter; and (as such) preg_replace() will establish the same $count inside its function scope and outside of it. The value of $count is the number of replacements which took place in preg_replace(). Eventually there will be an iteration where zero replacements will occur; and $count will be zero causing termination of the loop.

Q: Why is there a need for iterations?

A: We need to do preg_replace() at least one more time because the new string which just got formed may have ended up with a string sequence we are trying to eliminate.

Code: html_escape()

/**
* Returns HTML escaped variable
*
* @access	public
* @param	mixed
* @return	mixed
*/
if ( ! function_exists('html_escape'))
{
	function html_escape($var)
	{
		if (is_array($var))
		{
			return array_map('html_escape', $var);
		}
		else
		{
			return htmlspecialchars($var, ENT_QUOTES, config_item('charset'));
		}
	}
}

/* End of file Common.php */
/* Location: ./system/core/Common.php */

This function provides a wrapper for the htmlspecialchars() function. It accepts strings and arrays. It is useful for preventing Cross Site Scripting (XSS.)

What does array_map() do?

array_map — Applies the callback to the elements of the given arrays

In our case the callback is html_escape. So, the function html_escape() is applied to each element of $var (for the case where $var is an array.)

What is html_escape()?

Duh!!! It’s the CodeIgniter function I’m trying to explain right here!

So, our CI function will call itself on each element of an array input parameter.

Q: Generally, what does htmlspecialchars() do?

A: htmlspecialchars — Convert special characters to HTML entities

Specifically, we have:

return htmlspecialchars($var, ENT_QUOTES, config_item('charset'));

This will replace the following characters with their HTML entities:

&, ", ', <, >

config_item('charset') will force PHP to recognize the bits making up the string as config_item('charset') encoded characters.

Advertisements

About samehramzylabib

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

4 Responses to Common.php CI Explained

  1. Pingback: Utf8.php CI Explained | Sam's PHP

  2. Pingback: Config.php CI Explained | Sam's PHP

  3. Pingback: Exceptions.php CI Explained | Sam's PHP

  4. 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