Skip to content
World Wide Web Server edited this page Jul 4, 2012 · 31 revisions

[h3] Introduction [/h3]

Filters system is similar to the servlet filters in Java or filters in Rails.

Filter is a class that is called right before calling the controller method and / or just after returning back from the controller.

It allows you for example:

  • to redirect user before calling controller action if he is not logged in
  • to implement access rights verification (similar to detecting users not logged in)
  • to implement cache easily (check if cached version exists and return it before calling controller, store its results after returning from the controller)
  • benchmarking (store action execution data into the database or send email to admin if execution seems to be slow)

Filter system is not limited to only one filter per request - you can configure as many filters as you like. The configuration of the filters is stored in system/application/config/filters.php. Filter classes should be placed in the system/filters (default filters provided with CI) and system/application/filters (application specific filters).

The filter system is implemented by 2 classes (system/library/CI_Pipe and Filter). CI_Pipe is filter manager - it does all the rules matching and runs filters specified in the configuration file.

[h3] Request execution flow [/h3]

The diagram below shows the way the request is executed in CI with the filters system.

Image:Request_processing_with_filters.png

[h3] Required changes in CodeIgniter.php [/h3]

To enable the filter system system/base/CodeIgniter.php had to be changed:

  • lines 153-173 initialize filters system (load config and instantiate CI_Pipe)
  • line 183 runs before() section of the filters
  • line 200 runs after() section of the filters

[h3] Filter configuration file [/h3]

Example filter configuration file with some technical info (system/application/config/filter.php):

[code] <?php if (!defined('BASEPATH')) exit('No direct script access allowed');

/*
FILTERS
--------------------------------------------------------------------------

| | Filters allow you to execute arbitrary code before calling controller, | after its execution or even 'around' it - letting you take the complete | control over its execution. | | Example filters config: | | $filter['auth'] = array('exclude', array('login/', 'about/')); | $filter['cache'] = array('include', array('login/index', 'about/', 'register/form,rules,privacy')); | | Those lines define two filters - 'auth' and 'cache'. | | First parameter of the array containing filter definition determines the | way the following array is interpreted: | * 'exclude' - filter will be applied to all requests excluding those matching given patterns | * 'include' - filter will be applied only to the requests matching given patterns | | Next element is an array containing the list of controller and method patterns. | | You can use following patterns: | / - requests to the front page | * - all requests - use VERY carefully! | ctrl/ - requests to the controller 'ctrl' | ctrl/meth - requests to the method 'meth' of the controller 'ctrl' | ctrl/meth1,meth2 - requests to the methods 'meth1' and 'meth2' of the controller 'ctrl' | | The combination of 'exclude' and '*' means that the filter will be never applied. | | Interpretation of the example filters configuration: | | Filter 'auth' will be applied to each request excluding all requests to | the 'login' and 'about' controller. | | All requests which will pass the first filter will be processed by the | 'cache' filter, but only if they are 'login/index', any of the 'about' controller | methods or 'form', 'rules' or 'privacy' methods in the 'register' controller. | | | ------------------------------------------------------------------- | HOW IT WORKS | ------------------------------------------------------------------- | | First, CI will look for the 'auth.php' file containing filter definition | in the 'system/application/filters' directory. If it won't be found there | CI will look into 'system/filters' directory. As you can see, this is the | way to override default filters included in CI distribution. | | If slashes are used in the filter name, CI will search for the filter in | the giver directory. For example, if the filter name is 'security/auth', | CI will search for the 'auth.php' in the 'system/application/filters/security' | and in case of failure - in the 'system/filters/security' directory. | | Each filter can define one or both filtering methods - before() and after(). | | Method before() is executed just before calling controller action (and before | scaffolding code). | | Method after() is executed right after finishing controller action (but | before displaying any output) | | Remember that if you will use redirect() in the controller action or in the | before() filter method, the after() method will NOT be called. | | Example usages of the filters system: | - cacheing | - logging | - profiling | - authentication / authorization | */

/*
Filters configuration
-------------------------------------------------------------------

| | Note: The filters will be applied in the order that they are defined | | Example configuration: | | $filter['auth'] = array('exclude', array('login/', 'about/')); | $filter['cache'] = array('include', array('login/index', 'about/*', 'register/form,rules,privacy')); | */

$filter['auth'] = array('exclude', array('/', 'login/*')); //$filter['remove_xss'] = array('include', array('login/create')); ?> [/code]

[h3] Pipe class [/h3]

CI_Pipe class is the real filter engine. It processes filter configuration, matches request to the configured filters and provides means for the execution of filters' methods.

Code of the CI_Pipe class (system/library/Pipe.php):

[code] <?php if (!defined('BASEPATH')) exit('No direct script access allowed'); /** *

  • Class provides request filtering feature for CI apps.
  • CI_Pipe should be called after the execution of the request router,
  • because it needs to know controller and method names which are
  • going to be used to serve current request.

*/ class CI_Pipe { var $filters = array(); // filter configuration array var $call_stack = array(); // list of filter objects to call after processing the controller var $controller_name = ''; // name of the current request controller var $method_name = ''; // name of the current request action method

//-------------------------------------
//  Initialize Prefs
//-------------------------------------

function initialize($config = array())
{
    if (sizeof($config) > 0)
    {
        foreach ($config as $key => $val)
        {
            if (isset($this->$key))
            {
                $this->$key = $val;            
            }
        }
    }
}
// END

//-------------------------------------
//  Default constructor - requires names of the requested controller and method.
//-------------------------------------

function __construct($filters, $controller_name, $method_name) {
    $this->filters         = $filters;
    $this->controller_name = $controller_name;
    $this->method_name     = $method_name;
}
// END

//-------------------------------------
//  Processes filter pipe before controller action call
//-------------------------------------

function process_before() {
    foreach( $this->filters as $filter_name => $filter_conf ) {
        if ( empty($filter_name) ) {
            throw new Exception("Filter name can't be empty!");
        }

        if ($this->_applies($filter_conf)) {
            $this->_run($filter_name);
        }
    }
}
// END

//-------------------------------------
// Processes filter pipe after controller action call
//-------------------------------------
function process_after() {
    foreach( $this->call_stack as $filter ) {
        $filter->after();
    }
}
// END

//-------------------------------------
// PRIVATE: Tries to apply given filter config entry.
// Returns true if filter should be applied to the request and false in other case.
//-------------------------------------
function _applies($filter_conf)
{
    $paths = $filter_conf[1];
    switch ( $filter_conf[0] ) {
        // exclusion mode
        case 'exclude':
            $apply = true;
            foreach( $paths as $path ) {
                if ( $this->_matches($path) ) {
                    return false;
                }
            }
            break;
        // inclusion mode
        case 'include':
            $apply = false;
            foreach( $paths as $path ) {
                if ( $this->_matches($path) ) {
                    return true;
                }
            }
            break;
        default:
            throw new Exception('Bad type of filter type - only "exclude" and "include" are valid.');
    }
    
    return $apply;
}
// END

//-------------------------------------
// PRIVATE: Matches given URI pattern to the current request
//-------------------------------------
function _matches($path) {
    if ( $path == '*' ) {
        return true;
    } else if ( $path == '/' ) {
        if ($_SERVER['REQUEST_URI'] == '' || $_SERVER['REQUEST_URI'] == '/') {
            return true;
        }
    } else {
        $parts = explode('/', $path);
        if ( $parts[1] == '*' ) {
            if ( $parts[0] == $this->controller_name ) {
                return true;
            }
        } else if ( strpos($parts[1], ',') !== false ) {
            $subparts = explode( ',', $parts[1] );
            if ( array_search($this->method_name, $subparts) !== false ) {
                return true;
            }
        } else {
            if ( $parts[0] == $this->controller_name && $parts[1] == $this->method_name ) {
                return true;
            }
        }
    }
    return false;
}    
// END

//-------------------------------------
// PRIVATE: Applies filter with given name
//-------------------------------------
function _run($filter_name) {
    $this->_load_filter( $filter_name );
    $class_name = $filter_name.'_filter';
    $filter = new $class_name();
    $this->call_stack[] = $filter;
    $filter->before();
}
// END

//-------------------------------------
// PRIVATE: Locates and loads code of the filter with given name.
//-------------------------------------
function _load_filter($filter_name)
{        
    $file_name = $filter_name.EXT;
    if ( file_exists(APPPATH.'filters/'.$file_name) ) {
        require_once(APPPATH.'filters/'.$file_name);
    } elseif ( file_exists(BASEPATH.'filters/'.$file_name) ) {
        require_once(BASEPATH.'filters/'.$file_name);
    } else {
        throw new Exception("Filter [$filter_name] not found in application/filters nor in system/filters");
    }
}

} ?> [/code]

[h3] Filter class [/h3]

Code of the Filter class (system/library/Filter.php):

[code] <?php if (!defined('BASEPATH')) exit('No direct script access allowed');

/**

  • Base class for all filters. Defines empty before() and after() methods.

  • Override them in your filter class to customize the filter behavior. */ class Filter { function before() { }

    function after() { } } ?> [/code]

[h3] Example filter [/h3]

Example filter from my app (system/application/filters/auth.php):

[code] <?php if (!defined('BASEPATH')) exit('No direct script access allowed'); /**

  • Authentication filter */ class Auth_filter extends Filter { function before() { $obj =& get_instance();
    $obj->load->library('native_session'); $obj->load->helper('url');

      if (!$obj->session->userdata('user_id')) {
          
          // Character '/' is replaced by ':' because it causes problems
          // in URI interpretation and the URL encoded. Login controller
          // knows how to reverse this process and redirect to the given
          // URL. By using such solution, filter works even for query paths
          // turned off.
          $return_uri = urlencode( str_replace( '/', ':', substr( $_SERVER['REQUEST_URI'], 1 ) ) );
          
          redirect('/login/index/'.$return_uri);
      }
    

    }
    } ?> [/code]

[h3] Excerpt from the changed CodeIgniter.php [/h3]

Changes in the system/codeigniter/CodeIgniter.php:

[code] // ...

require(BASEPATH.'libraries/Controller'.EXT); require(APPPATH.'controllers/'.$RTR->fetch_class().EXT);

/*


  • Security check

  • None of the functions in the app controller or the
  • loader class can be called via the URI, nor can
  • controller functions that begin with an underscore */ $class = $RTR->fetch_class(); $method = $RTR->fetch_method();

if ( ! class_exists($class) OR $method == 'controller' OR substr($method, 0, 1) == '_' OR in_array($method, get_class_methods('Controller')) ) { show_404(); }

/*


  • Load the filters list file

*/ require(APPPATH.'config/filters'.EXT);

if ( ! isset($filter) OR ! is_array($filter)) { show_error('Your filters file does not appear to be formatted correctly.'); }

/*


  • Initialize filters system

*/ require(BASEPATH.'libraries/Filter'.EXT); require(BASEPATH.'libraries/Pipe'.EXT);

$FPI = new CI_Pipe($filter, $class, $method);

/*


  • Instantiate the controller and call requested method

*/ $CI = new $class();

// Run 'before' filters for the controller $FPI->process_before();

if ($RTR->scaffolding_request === TRUE) { $CI->_ci_scaffolding(); } else { if ( ! method_exists($CI, $method)) { show_404(); }

$CI->$method();

}

// Run 'after' filters for the controller $FPI->process_after();

/*


  • Send the final rendered output to the browser

*/ $OUT->_display();

// ... [/code]

Category:Libraries

Clone this wiki locally