Welcome Guest, Not a member yet? Register   Sign In
How to use callbacks in validation the correct way?
#1

[eluser]Warz[/eluser]
Hi,

So I got two controllers:
- Register
- Profile

I already created a few nice functions in Register. For instance, this one:

Code:
function valid_date()
    {
        if (!checkdate($this->input->post('month'), $this->input->post('day'), $this->input->post('year')))
        {
            $this->form_validation->set_message('valid_date', 'The %s field is invalid.');
            return FALSE;
        }
    }
This is a function I use when user register. Now I also want this function in my profile controller.

What is the best way to do this?
#2

[eluser]Jelmer[/eluser]
I must admit it isn't the pretiest solution, but I solved it like this (PHP5 only):
- I place custom validation rules in the model instead of in the controller
- they're all prefixed with a single underscore
- I add the following "magic method" from PHP5 which is called when an undefined method is called. Also it works solely on methods prefixed with an underscore and calls them from the model (it also asumes the model is already loaded):
Code:
function __call($function, $args)
{
    if (substr($function, 0, 1) == '_' && substr($function, 1, 1) != '_')
        return $this->members->$function($args[0]);
    else
        throw(new Exception('Could perform this function on members.'));
}
- After that you can add the rule like this:
Code:
$this->form_validation->set_rules('pass', 'Password', 'trim|callback__secure_password');
#3

[eluser]Warz[/eluser]
Thanks for you solution, so you do like this?:
Controller: register.php
Code:
function Register() {
        parent::Controller();
        $this->load->library('formdate');
        $this->load->model('user_model');    
    }
.....
$this->form_validation->set_rules('year','Date','required|callback__valid_date');
.....
Model: user_model.php
Code:
function _valid_date()
    {
        if (!checkdate($this->input->post('month'), $this->input->post('day'), $this->input->post('year')))
        {
            $this->form_validation->set_message('valid_date', 'The %s field is invalid.');
            return FALSE;
        }
    }
function __call($function, $args) { ... }

I tried this, and it would not call _valid_date() I don't understand how set_rules will know that the callback function is placed in the model? How to say: callback__(User_model)__valid_date... ?
#4

[eluser]Jelmer[/eluser]
The __call() function should be in the controller.

The Form_validation class requests the callback rule in the controller, so the magic method should catch the function for not-existing in the controller.

If you only need rules from 1 model you can solve it like this - to be put in the controller:
Code:
function __call($function, $args)
{
    if (substr($function, 0, 1) == '_' && substr($function, 1, 1) != '_')
        return $this->user_model->$function($args[0]);
    else
        throw(new Exception('Could perform this function on members.'));
}

If you might need to use this with multiple models you might do it like this:
- make sure all models are 1 word and postfixed with "_model"
- put the model name as the first part of the function and seperate it from the second part using an underscore
- change the __call() function to something like this (untested code):
Code:
function __call($function, $args)
{
    if (substr($function, 0, 1) == '_' && substr($function, 1, 1) != '_')
    {
        preg_match('/^_([a-z]+)_([a-z0-9_]+)$/i', $function, $matches);
        $model = $matches[1].'_model';
        $function = '_'.$matches[2];
        $this->$model->$function($args[0]);
    }
    else
        throw(new Exception('Could perform this function on members.'));
}
Which you can call using:
Code:
$this->form_validation->set_rules('year','Date','required|callback__user_valid_date');
#5

[eluser]Warz[/eluser]
Thanks again, I couldn't get it to call "__call" function for some reason, I will look more into it tomorrow. However I'm wondering how come you didn't use a Controller class to put these validations in and then extend it? Isn't that possible too?
#6

[eluser]Jelmer[/eluser]
Like putting the rules in an extended MY_Controller you mean? Mostly because I don't need those validation rules everywhere and I might need combinations of multiple sets of rules so different base controllers doesn't really work either. It will work, and it will work quite well in smaller applications. But in larger applications where you're working with multiple models that might need their validation functions shared, this will become quite ugly.

Also: if __call() doesn't work that might be because you're not using PHP5.
#7

[eluser]Warz[/eluser]
Like making a controller called "User" then extend like this:
Register extends User
Profile extends User

And in the "User" controller you keep all validation functions and then in Profile for instance you may then call whatever you need by set_rules and callback (By the way I tried this too and couldn't get it working).

I am running the script in wamp server with php 5.3.0 so there must be something else causing it, weird thing.
#8

[eluser]Unknown[/eluser]
[quote author="Warz" date="1273889834"]Like making a controller called "User" then extend like this:
Register extends User
Profile extends User

And in the "User" controller you keep all validation functions and then in Profile for instance you may then call whatever you need by set_rules and callback (By the way I tried this too and couldn't get it working).

I am running the script in wamp server with php 5.3.0 so there must be something else causing it, weird thing.[/quote]

I was having this issue to and found out that the problem is in the form_validation library.

Around line 583 in /system/libraries/form_validation.php you will find:

Code:
if ( ! method_exists($this->CI, $rule))
{        
continue;
}

CI is checking if the callback method exists which it doesn't so it skips calling the function all together. After I commented out this line the __call() method works perfectly.

Hope that helps.
#9

[eluser]MT206[/eluser]
What about extending the form_validation library? Wouldn't that work? Then the valid date function could be used like any other validation type.
#10

[eluser]Jelmer[/eluser]
That would work fine for rules that may be usefull in general. This solution is meant for rules that are specific to certain models in larger applications. You could still keep extending the Form_validation class but in the end that will make the class impossible to keep track of, and if you ever need those rules but not need form validation you will need to load the whole class (which is quite illogical).

Whichever solution is the best depends on the size of your application and on how much effort you're willing to put in to keep your application logical (specific validation rules belong with the model in my opinion). Keeping things logical will take a little more time in the beginning, but will pay off handsomely in the end when you're extending your application or changing things around after not having seen the code for a while.

But you're right, I should have mentioned it in the beginning - it's an easier solution, though only a better one when the rules are non-specific. I sometimes forget the simple solutions after I've found solutions I consider to be better.
You could also solve this by allowing a way to load a model's methods from within the form_validation class - in the end I prefered the __call() solution because otherwise you'll have to copy-paste the entire validation loading function in the form_validation extention just to add a few lines in the middle (and I always prefer to use the original class methods as much as possible).




Theme © iAndrew 2016 - Forum software by © MyBB