URI.php CI Explained

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

URI.php is a core CodeIgniter class. This post is part of a series which explains the CodeIgniter (CI) source code. This post explains the CI 2.1.3 URI.php.

./system/core/URI.php

It’s assumed you know what URI means in the context of CodeIgniter. If you don’t then read the user guide (especially this UG page.)

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

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

/**
 * URI Class
 *
 * Parses URIs and determines routing
 *
 * @package		CodeIgniter
 * @subpackage	Libraries
 * @category	URI
 * @author		ExpressionEngine Dev Team
 * @link		http://codeigniter.com/user_guide/libraries/uri.html
 */
class CI_URI {

Declaration of Properties

/**
 * List of cached uri segments
 *
 * @var array
 * @access public
 */
var	$keyval			= array();
/**
 * Current uri string
 *
 * @var string
 * @access public
 */
var $uri_string;
/**
 * List of uri segments
 *
 * @var array
 * @access public
 */
var $segments		= array();
/**
 * Re-indexed list of uri segments
 * Starts at 1 instead of 0
 *
 * @var array
 * @access public
 */
var $rsegments		= array();

Code: __construct()

/**
 * Constructor
 *
 * Simply globalizes the $RTR object.  The front
 * loads the Router class early on so it's not available
 * normally as other classes are.
 *
 * @access	public
 */
function __construct()
{
	$this->config =& load_class('Config', 'core');
	log_message('debug', "URI Class Initialized");
}

I have no idea what this comment means:

 * Simply globalizes the $RTR object.  The front
 * loads the Router class early on so it's not available
 * normally as other classes are.

What I do know:

The constructor creates a property $config. Its value is (by-reference) the configuration object.

And, it does: log_message('debug', "URI Class Initialized")

Code: _fetch_uri_string()

Establishes the current URI string and stores it in $uri_string

/**
 * Get the URI String
 *
 * @access	private
 * @return	string
 */
function _fetch_uri_string()
{
	if (strtoupper($this->config->item('uri_protocol')) == 'AUTO')
	{
		// Is the request coming from the command line?
		if (php_sapi_name() == 'cli' or defined('STDIN'))
		{
			$this->_set_uri_string($this->_parse_cli_args());
			return;
		}

		// Let's try the REQUEST_URI first, this will work in most situations
		if ($uri = $this->_detect_uri())
		{
			$this->_set_uri_string($uri);
			return;
		}

		// Is there a PATH_INFO variable?
		// Note: some servers seem to have trouble with getenv() so we'll test it two ways
		$path = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : @getenv('PATH_INFO');
		if (trim($path, '/') != '' && $path != "/".SELF)
		{
			$this->_set_uri_string($path);
			return;
		}

		// No PATH_INFO?... What about QUERY_STRING?
		$path =  (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING');
		if (trim($path, '/') != '')
		{
			$this->_set_uri_string($path);
			return;
		}

		// As a last ditch effort lets try using the $_GET array
		if (is_array($_GET) && count($_GET) == 1 && trim(key($_GET), '/') != '')
		{
			$this->_set_uri_string(key($_GET));
			return;
		}

		// We've exhausted all our options...
		$this->uri_string = '';
		return;
	}

	$uri = strtoupper($this->config->item('uri_protocol'));

	if ($uri == 'REQUEST_URI')
	{
		$this->_set_uri_string($this->_detect_uri());
		return;
	}
	elseif ($uri == 'CLI')
	{
		$this->_set_uri_string($this->_parse_cli_args());
		return;
	}

	$path = (isset($_SERVER[$uri])) ? $_SERVER[$uri] : @getenv($uri);
	$this->_set_uri_string($path);
}

let us break _fetch_uri_string() down

if (strtoupper($this->config->item('uri_protocol')) == 'AUTO')
{

See ./application/config/config.php configuration file to understand what the individual settings represent. From there we find:

/*
|--------------------------------------------------------------------------
| URI PROTOCOL
|--------------------------------------------------------------------------
|
| This item determines which server global should be used to retrieve the
| URI string.  The default setting of 'AUTO' works for most servers.
| If your links do not seem to work, try one of the other delicious flavors:
|
| 'AUTO'			Default - auto detects
| 'PATH_INFO'		Uses the PATH_INFO
| 'QUERY_STRING'	Uses the QUERY_STRING
| 'REQUEST_URI'		Uses the REQUEST_URI
| 'ORIG_PATH_INFO'	Uses the ORIG_PATH_INFO
|
*/
$config['uri_protocol']	= 'AUTO';

back to _fetch_uri_string()

If uri_protocol is 'AUTO' then:

Execute five if statements and two regular statements.

Here’s the first if statement.

// Is the request coming from the command line?
if (php_sapi_name() == 'cli' or defined('STDIN'))
{
	$this->_set_uri_string($this->_parse_cli_args());
	return;
}

If CI is being used from the command line then use _parse_cli_args to feed _set_uri_string… and we are done. This will establish the property uri_string.

Here’s the second if statement.

// Let's try the REQUEST_URI first, this will work in most situations
if ($uri = $this->_detect_uri())
{
	$this->_set_uri_string($uri);
	return;
}

Try the _detect_uri method. If it works then use its return value as an argument to _set_uri_string … and we are done. The property uri_string will have been set. Which means we have established the current uri string.

Here’s the third if statement.

// Is there a PATH_INFO variable?
// Note: some servers seem to have trouble with getenv() so we'll test it two ways
$path = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : @getenv('PATH_INFO');
if (trim($path, '/') != '' && $path != "/".SELF)
{
	$this->_set_uri_string($path);
	return;
}

This is some other way of trying to established the current uri string.

Here’s the fourth if statement.

// No PATH_INFO?... What about QUERY_STRING?
$path =  (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING');
if (trim($path, '/') != '')
{
	$this->_set_uri_string($path);
	return;
}

This is some other way of trying to established the current uri string.

Here’s the fifth if statement.

// As a last ditch effort lets try using the $_GET array
if (is_array($_GET) && count($_GET) == 1 && trim(key($_GET), '/') != '')
{
	$this->_set_uri_string(key($_GET));
	return;
}

// We've exhausted all our options...
$this->uri_string = '';
return;

Try one last thing before giving up and setting the current uri string to an empty string.

END OF: If uri_protocol is 'AUTO'.

$uri = strtoupper($this->config->item('uri_protocol'));

if ($uri == 'REQUEST_URI')
{
	$this->_set_uri_string($this->_detect_uri());
	return;
}
elseif ($uri == 'CLI')
{
	$this->_set_uri_string($this->_parse_cli_args());
	return;
}

$path = (isset($_SERVER[$uri])) ? $_SERVER[$uri] : @getenv($uri);
$this->_set_uri_string($path);

Just more things to try in order to set the current uri string

Code: _set_uri_string()

This is a helper which sets the current uri string ($this->uri_string) when given (as an argument) the path resulting from one of some other helpers — These “other helpers” have specialized approaches to extracting the path from the system.

/**
 * Set the URI String
 *
 * @access	public
 * @param 	string
 * @return	string
 */
function _set_uri_string($str)
{
	// Filter out control characters
	$str = remove_invisible_characters($str, FALSE);

	// If the URI contains only a slash we'll kill it
	$this->uri_string = ($str == '/') ? '' : $str;
}

To better understand how the URI class derives the URI from the system we need to understand what form $str is in. To accomplish this understanding we must explain how $str becomes $uri_string.

Based on analysis (which I did in my head) I’ve concluded:

Both $str and $uri_string essentially have the same form.

So, the magic happens in those “other helpers”.

Code: _detect_uri()

/**
 * Detects the URI
 *
 * This function will detect the URI automatically and fix the query string
 * if necessary.
 *
 * @access	private
 * @return	string
 */
private function _detect_uri()
{
	if ( ! isset($_SERVER['REQUEST_URI']) OR ! isset($_SERVER['SCRIPT_NAME']))
	{
		return '';
	}

	$uri = $_SERVER['REQUEST_URI'];
	if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0)
	{
		$uri = substr($uri, strlen($_SERVER['SCRIPT_NAME']));
	}
	elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0)
	{
		$uri = substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME'])));
	}

	// This section ensures that even on servers that require the URI to be in the query string (Nginx) a correct
	// URI is found, and also fixes the QUERY_STRING server var and $_GET array.
	if (strncmp($uri, '?/', 2) === 0)
	{
		$uri = substr($uri, 2);
	}
	$parts = preg_split('#\?#i', $uri, 2);
	$uri = $parts[0];
	if (isset($parts[1]))
	{
		$_SERVER['QUERY_STRING'] = $parts[1];
		parse_str($_SERVER['QUERY_STRING'], $_GET);
	}
	else
	{
		$_SERVER['QUERY_STRING'] = '';
		$_GET = array();
	}

	if ($uri == '/' || empty($uri))
	{
		return '/';
	}

	$uri = parse_url($uri, PHP_URL_PATH);

	// Do some final cleaning of the URI and return it
	return str_replace(array('//', '../'), '/', trim($uri, '/'));
}

If the page requested was:

http://test.com/sample.php/easy/task

What would this evaluate to?

$_SERVER['REQUEST_URI']

It evaluates to:

/sample.php/easy/task

What would this evaluate to?

$_SERVER['SCRIPT_NAME']

It evaluates to:

/sample.php
Advertisements

About samehramzylabib

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

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