milikpribumi
milikpribumi

Reputation: 41

Codeigniter Passing Extra Parameters to Custom Validation Rule

Based on this documentation , how to pass second parameter to the rule method?

This is my custom rule

public function email_exists($email, $exclude_id=NULL)
{
    if ( $exclude_id !== NULL ) $this->db->where_not_in('id', $exclude_id);

    $result = $this->db->select('id')->from('users')->where('email', $email)->get();

    if ( $result->num_rows() > 0 ) {
        $this->form_validation->set_message('email_exists', '{field} has been used by other user.');
        return FALSE;
    } else {
        return TRUE;
    }
}

and this is how i call it from controller

$rules = [
    [
        'field' => 'email',
        'label' => 'Email',
        'rules' => [
            'required',
            'trim',
            'valid_email',
            'xss_clean',
            ['email_exists', [$this->m_user, 'email_exists']]
        ]
    ]
];

$this->form_validation->set_rules($rules);

How can I pass second parameter to email_exists method?

Upvotes: 0

Views: 3477

Answers (3)

nelzro
nelzro

Reputation: 1

I use CI 3.1.10 and this issue still exists, I extend the library and use the same way as callback

array('username_callable[param]' => array($this->some_model, 'some_method'))

Extended Form_validation library:

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class MY_Form_validation extends CI_Form_validation {

    /**
     * Executes the Validation routines
     *
     * @param   array
     * @param   array
     * @param   mixed
     * @param   int
     * @return  mixed
     */
    protected function _execute($row, $rules, $postdata = NULL, $cycles = 0)
    {
        // If the $_POST data is an array we will run a recursive call
        //
        // Note: We MUST check if the array is empty or not!
        //       Otherwise empty arrays will always pass validation.
        if (is_array($postdata) && ! empty($postdata))
        {
            foreach ($postdata as $key => $val)
            {
                $this->_execute($row, $rules, $val, $key);
            }

            return;
        }

        $rules = $this->_prepare_rules($rules);
        foreach ($rules as $rule)
        {
            $_in_array = FALSE;

            // We set the $postdata variable with the current data in our master array so that
            // each cycle of the loop is dealing with the processed data from the last cycle
            if ($row['is_array'] === TRUE && is_array($this->_field_data[$row['field']]['postdata']))
            {
                // We shouldn't need this safety, but just in case there isn't an array index
                // associated with this cycle we'll bail out
                if ( ! isset($this->_field_data[$row['field']]['postdata'][$cycles]))
                {
                    continue;
                }

                $postdata = $this->_field_data[$row['field']]['postdata'][$cycles];
                $_in_array = TRUE;
            }
            else
            {
                // If we get an array field, but it's not expected - then it is most likely
                // somebody messing with the form on the client side, so we'll just consider
                // it an empty field
                $postdata = is_array($this->_field_data[$row['field']]['postdata'])
                    ? NULL
                    : $this->_field_data[$row['field']]['postdata'];
            }

            // Is the rule a callback?
            $callback = $callable = FALSE;
            if (is_string($rule))
            {
                if (strpos($rule, 'callback_') === 0)
                {
                    $rule = substr($rule, 9);
                    $callback = TRUE;
                }
            }
            elseif (is_callable($rule))
            {
                $callable = TRUE;
            }
            elseif (is_array($rule) && isset($rule[0], $rule[1]) && is_callable($rule[1]))
            {
                // We have a "named" callable, so save the name
                $callable = $rule[0];
                $rule = $rule[1];
            }

            // Strip the parameter (if exists) from the rule
            // Rules can contain a parameter: max_length[5]
            $param = FALSE;
            if ( ! $callable && preg_match('/(.*?)\[(.*)\]/', $rule, $match))
            {
                $rule = $match[1];
                $param = $match[2];
            }
            elseif ( is_string($callable) && preg_match('/(.*?)\[(.*)\]/', $callable, $match))
            {
                $param = $match[2];
            }

            // Ignore empty, non-required inputs with a few exceptions ...
            if (
                ($postdata === NULL OR $postdata === '')
                && $callback === FALSE
                && $callable === FALSE
                && ! in_array($rule, array('required', 'isset', 'matches'), TRUE)
            )
            {
                continue;
            }

            // Call the function that corresponds to the rule
            if ($callback OR $callable !== FALSE)
            {
                if ($callback)
                {
                    if ( ! method_exists($this->CI, $rule))
                    {
                        log_message('debug', 'Unable to find callback validation rule: '.$rule);
                        $result = FALSE;
                    }
                    else
                    {
                        // Run the function and grab the result
                        $result = $this->CI->$rule($postdata, $param);
                    }
                }
                else
                {
                    $result = is_array($rule)
                        ? $rule[0]->{$rule[1]}($postdata, $param)
                        : $rule($postdata);

                    // Is $callable set to a rule name?
                    if ($callable !== FALSE)
                    {
                        $rule = $callable;
                    }
                }

                // Re-assign the result to the master data array
                if ($_in_array === TRUE)
                {
                    $this->_field_data[$row['field']]['postdata'][$cycles] = is_bool($result) ? $postdata : $result;
                }
                else
                {
                    $this->_field_data[$row['field']]['postdata'] = is_bool($result) ? $postdata : $result;
                }
            }
            elseif ( ! method_exists($this, $rule))
            {
                // If our own wrapper function doesn't exist we see if a native PHP function does.
                // Users can use any native PHP function call that has one param.
                if (function_exists($rule))
                {
                    // Native PHP functions issue warnings if you pass them more parameters than they use
                    $result = ($param !== FALSE) ? $rule($postdata, $param) : $rule($postdata);

                    if ($_in_array === TRUE)
                    {
                        $this->_field_data[$row['field']]['postdata'][$cycles] = is_bool($result) ? $postdata : $result;
                    }
                    else
                    {
                        $this->_field_data[$row['field']]['postdata'] = is_bool($result) ? $postdata : $result;
                    }
                }
                else
                {
                    log_message('debug', 'Unable to find validation rule: '.$rule);
                    $result = FALSE;
                }
            }
            else
            {
                $result = $this->$rule($postdata, $param);

                if ($_in_array === TRUE)
                {
                    $this->_field_data[$row['field']]['postdata'][$cycles] = is_bool($result) ? $postdata : $result;
                }
                else
                {
                    $this->_field_data[$row['field']]['postdata'] = is_bool($result) ? $postdata : $result;
                }
            }

            // Did the rule test negatively? If so, grab the error.
            if ($result === FALSE)
            {
                // Callable rules might not have named error messages
                if ( ! is_string($rule))
                {
                    $line = $this->CI->lang->line('form_validation_error_message_not_set').'(Anonymous function)';
                }
                else
                {
                    $line = $this->_get_error_message($rule, $row['field']);
                }

                // Is the parameter we are inserting into the error message the name
                // of another field? If so we need to grab its "field label"
                if (isset($this->_field_data[$param], $this->_field_data[$param]['label']))
                {
                    $param = $this->_translate_fieldname($this->_field_data[$param]['label']);
                }

                // Build the error message
                $message = $this->_build_error_msg($line, $this->_translate_fieldname($row['label']), $param);

                // Save the error message
                $this->_field_data[$row['field']]['error'] = $message;

                if ( ! isset($this->_error_array[$row['field']]))
                {
                    $this->_error_array[$row['field']] = $message;
                }

                return;
            }
        }
    }

}

Upvotes: 0

Praveen Kumar
Praveen Kumar

Reputation: 2408

Just do it the right way (at least for CI 2.1+) as described in the docs:

$this->form_validation->set_rules('uri', 'URI', 'callback_check_uri['.$this->input->post('id').']');
// Later:
function check_uri($field, $id){
    // your callback code here
}

If this is not working than make an hidden field in your form for $exclude_id and check that directly in your callback via

$exclude_id = $this->input->post('exclude_id');//or whatever the field name is

More here

Upvotes: 2

milikpribumi
milikpribumi

Reputation: 41

Its seems CI does not provide a mechanism for this. I found several approaches to solve this. First way, you can hack the file system (Form_validation.php) and modify some script at line 728

if ( preg_match('/(.*?)\[(.*)\]/', $rule[1], $rulea) ) {
    $method = $rulea[1];
    $extra = $rulea[2];
} else {
    $method = $rule[1];
    $extra = NULL;
}

$result = is_array($rule)
    ? $rule[0]->{$method}($postdata, $extra)
    : $rule($postdata);

Second way you can extends CI_Form_validation core and add your custom rule in it. I found the detail about this on codeigniter documentation.

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class MY_Form_validation extends CI_Form_validation
{

    public function __construct()
    {
        parent::__construct();
    }

    public function check_conflict_email($str, $exclude_id=NULL)
    {
        if ( $exclude_id !== NULL ) $this->CI->db->where_not_in('id', $exclude_id);

        $result = $this->CI->db->select('id')->from('users')->where('email', $str)->get();

        if ( $result->num_rows() > 0 ) {
            $this->set_message('check_conflict_email', '{field} has been used by other user.');
            return FALSE;
        } else {
            return TRUE;
        }
    }

}

/* End of file MY_Form_validation.php */
/* Location: ./application/libraries/MY_Form_validation.php */

Third way, and I think this is the best way to do it. Thanks to skunkbad for provide the solution

$rules = [
    [
        'field' => 'email',
        'label' => 'Email',
        'rules' => [
            'required',
            'trim',
            'valid_email',
            'xss_clean',
            [
                'email_exists', 
                function( $str ) use ( $second_param ){
                    return $this->m_user->email_exists( $str, $second_param ); 
                }
            ]
        ]
    ]
]; 

Upvotes: 4

Related Questions