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

[h3] Introduction [/h3]

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 [b] without modifying controller itself [/b]. This allows to change the behavior of the application accordingly to the deployers needs.

Filters system is similar to the servlet filters in Java or filters in Rails. The filter is a class that is called right before calling the controller method and / or just after returning back from the controller.

Common usages:

  • to redirect user before calling controller action if he is not logged in [li] to implement access rights verification (similar to detecting users not logged in) [li] to implement cache easily (check if cached version exists and return it before calling controller, store its results after returning from the controller) [li] 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] How it works [/h3]

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

    [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] Example filters configuration [/h3]

    [code] $filter['auth'] = array('exclude', array('login/', 'about/')); $filter['cache'] = array('include', array('login/index', 'about/*', 'register/form,rules,privacy')); [/code]

    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:

    [code] / - 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' [/code]

    The combination of 'exclude' and '*' means that the filter will be never applied.

    [h4] Interpretation of the example filters configuration [/h4]

    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.

    [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