Welcome Guest, Not a member yet? Register   Sign In
rails-like before_filter
#1

[eluser]dioony[/eluser]
Hi there! Now i want to thank you all for the great ideas and solutions and want to share my code with you too.

I was searching for a filter-mechanism like in ruby on rails, but i only found the filter-system in the wiki. But i think that solution is to huge and could be more easier. so i written my own solution. Because there are some more requests for an rails-like-before-filter, i thought, it would be nice to share this.

my solution exists only of one hook-class.

1. Configure the Hooks-Config
first of all you have to configure the application/config/hooks.php. Add the following code :
Code:
$hook['post_controller_constructor'][] = array(
                                'class'    => 'Filter',
                                'function' => 'doBeforeFilter',
                                'filename' => 'Filter.php',
                                'filepath' => 'hooks'                                
                                );

2. Add the Filter-Class
add a new file to your CI-App called: application/hooks/Filter.php
add the following code into this file:
Code:
<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');

/**
* Filter Class
*
* This class enables you to integrate filters like in rails into your web apps.
*
* @package     CodeIgniter
* @subpackage  Hooks
* @category    Hooks
* @author      dioony
* @version     1.0
*/
class Filter
{


    function doBeforeFilter(){
        $CI =& get_instance();
        $router =& load_class('Router');
        $called_function = $router->fetch_method();
        if(isset($CI->before_filter)){
            if(!isset($CI->before_filter["name"])){
                show_error("BeforeFilter: Name must be set");
            }else{
                if(method_exists($CI,$CI->before_filter["name"])){
                    if(isset($CI->before_filter["except"]) && isset($CI->before_filter["only"])){
                        show_error("BeforeFilter: Filter can only run either \"except\" or \"only\"");
                    }else{
                        if(isset($CI->before_filter["except"])){
                            if(!empty($CI->before_filter["except"])){
                                if(!in_array($called_function, $CI->before_filter["except"])){
                                    call_user_func(array($CI,$CI->before_filter["name"]));
                                }
                            }else{
                                call_user_func(array($CI,$CI->before_filter["name"]));
                            }
                        }else{
                            if(isset($CI->before_filter["only"])){
                                if(!empty($CI->before_filter["only"])){
                                    if(in_array($called_function, $CI->before_filter["except"])){
                                        call_user_func(array($CI,$CI->before_filter["name"]));
                                    }
                                }else{
                                    call_user_func(array($CI,$CI->before_filter["name"]));
                                }
                            }else{
                                call_user_func(array($CI,$CI->before_filter["name"]));
                            }
                        }
                    }
                }else{
                    show_error("BeforeFilter: Function \"".$CI->before_filter["name"]."\" does not exists");
                }
            }
        }
    }
    
}

?>

3.Setting up the Controller
Now you can set up you're controller to run the before_filter.
Code:
<?
class Start extends Controller {

    
    var $before_filter = array('name' => 'authorization');
    
    function Start() {
        parent::Controller();
    }
    
    function index(){
        //... the index ...
    }
    
    function authorization(){
        if(user_not_logged()){
            redirect('other_ctrl/login');
        }
    }
        
}

?>
now, if anybody runs a function from the Start-controller, automatically the authorization-function will be called before this function and will redirect the user to another controller if he is not logged in. note that it have to be another controller and not the same, because elsewise you will generate an endless loop.

now, it is also possible to exclude some functions to be filtered. (e.g. if you want to redirect to the same controller)
Code:
var $before_filter = array('name' => 'authorization', 'except' => array('index'));


another way is to specify only the functions which have to be filterd
Code:
var $before_filter = array('name' => 'authorization', 'only' => array('index'));

4. Annotations and Thoughts
This solution only accepts one filter per controller. there are two reasons:
1. i think, that there should not be more than one filter to keep the code clear
2. i never needed more than one in rails or ci.
But i think, it should be very easy to change it to a multiple-filter-hook. if someone take an interest in it, i could change the code.

in rails you have to put the before_filter-method in the application-controller. in theory it should also be possible with CI, if you have written an application-controller and each of your controllers extends this one. Then you can put the filter-method into the application-controller. But i didn't test it!

it should also be possible to write an after_filter. you only have to replace "post_controller_constructor" with "post_controller" in the hooks.php and change the variable "before_filter" to "after_filter" to keep the code clear.

if you have any feedback, comments, bugs or something like that, feel free to reply
#2

[eluser]dioony[/eluser]
i forgotten to notice the following:

note that the hooks-config uses post_controller_constructor instead of pre_controller. this is necessary, because it uses some functions/attributes of the controller itself. therefore please note that the constructor is called before the filter will be called to avoid bugs! so, i you're using this filter, then never run any code in the constructor which normally have to run after the filter!
#3

[eluser]webthink[/eluser]
Perhaps it's because I'm not familiar with rails but what advantage do filters give you over something like the following?
Code:
<?
class Start extends Controller {

    function Start() {
        parent::Controller();
        $this->authorization();
    }
    
    function index(){
        //... the index ...
    }
    
    function authorization(){
        if(user_not_logged()){
            redirect('other_ctrl/login');
        }
    }
        
}

?>
#4

[eluser]dioony[/eluser]
maybe your solution may work if you want to filter all the functions. the main idea was to create a filter, where you can decide which functions will be filtered and which not (except and only mechanism).

i dont know how php or ci works, but if the constructor will only be called once for a session and not for each request, the authorization will only work for the first function-call and not for following function-calls. but i never dealt with something like that before...
#5

[eluser]sophistry[/eluser]
xwero just showed how to do this by modifying the CI core... another approach to protecting controllers and methods and worth looking at for comparison and learning.


protecting controllers and methods




Theme © iAndrew 2016 - Forum software by © MyBB