3

How do I get CodeIgniter to run custom rules on fields which don't have the required rule but the user left empty?

The best I can come up with is to add a space to the field if the string is empty, and then add a trim rule -- but this feels hacky.

Example rule #1

Field is required only if another field has a certain value:

// depends[another_field.some_val]
public function depends($str, $field){
    list($post_key, $post_val)=explode('.', $field);
    if($_POST[$post_key] == $post_val){
        return $str != "";
    }
    return true;
}

Example rule #2

Field is required only if a regex exists on the database:

// regex[table_name.col_name.some_val]
public function regex($str, $field){
  list($table, $col, $post_val)=explode('.', $field);
  // Grab the regex
  $regex = $this->CI  ->db
                      ->limit(1)
                      ->select($col)
                      ->where($post_val, $_POST[$post_val])
                      ->get($table)
                      ->row($col);

  return preg_match('/'.$regex.'/', $str) === 1;
}

7 Answers 7

1

Why is there a need of a different function for a simple task. Use if..else.

Assuming that if input1 has value equals value1, then only you have to set the required validation rule for the other input which is say input2.

View:

<form action="/controller_name/function_name/" method="POST">

    <input type="text" name="input1" />
    <input type="text" name="input2" />

    <input type="submit" />
</form>


Controller:

class Controller_name extends CI_Controller
{
    public function __construct()
    {
       parent::__construct();
       $this->load->library('form_validation');
    }

    public function function_name()
    {
        if($this->input->is_post())
        {
            if($this->input->post('input1') == 'value1')
            {
               $this->form_validation->set_rules('input2', 'input2', 'required');
            }

            if ($this->form_validation->run() == FALSE)
            {
                 // something's wrong in form!
            }
            else
            {
                 // form is good, proceed!
            }
        }
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

It was an example, and this does not answer my question.
Thats what I said. I answered to your example in a different way (better way). You down voted thats fine. But I want to tell you that your method is WRONG.
1

From the code, the problem you point out is that you NEED make a field required. Well, make a kind of required field with a new rule: 'keep_checking'. This way, you force the system to check whatever you want. What I did:

  • A My_Form_validation class which extends system core class (so, you won't have to touch system files). NOTE: Don't forget this file goes inside of application/libraries folder
  • Check if the custom rule 'keep_checking' is set. That will override the behaviour of not checking the fields when 'required' rule is set (See the code below)

Last point, after extending the Form_validation class you'll have a place to put all your new custom rules you'll be using all the time, XD

class MY_Form_validation extends CI_Form_validation
{
    public function __construct( $rules = array( ) ) {
        parent::__construct( $rules );
    }

    protected function _execute($row, $rules, $postdata = NULL, $cycles = 0)
    {
        // If the $_POST data is an array we will run a recursive call
        if (is_array($postdata))
        {
            foreach ($postdata as $key => $val)
            {
                $this->_execute($row, $rules, $val, $cycles);
                $cycles++;
            }

            return;
        }

        // --------------------------------------------------------------------

        // If the field is blank, but NOT required, no further tests are necessary
        $callback = FALSE;

        //====================================================================
        // NEW ADDED RULE > 'keep_checking', will check all the rules even if 
        // the field is empty
        //====================================================================
        if ( ! in_array('required', $rules) AND is_null($postdata) AND ! in_array( 'keep_checking', $rules ) )
        {
            // Before we bail out, does the rule contain a callback?
            if (preg_match("/(callback_\w+(\[.*?\])?)/", implode(' ', $rules), $match))
            {
                $callback = TRUE;
                $rules = (array('1' => $match[1]));
            }
            else
            {
                return;
            }
        }
        // --------------------------------------------------------------------

        // Isset Test. Typically this rule will only apply to checkboxes.
        //====================================================================
        // NEW ADDED RULE > 'keep_checking', will check all the rules even if 
        // the field is empty
        //====================================================================
    if (is_null($postdata) AND $callback == FALSE && !in_array( 'keep_checking', $rules ))
        {
            if (in_array('isset', $rules, TRUE) OR in_array('required', $rules))
            {
                // Set the message type
                $type = (in_array('required', $rules)) ? 'required' : 'isset';

                if ( ! isset($this->_error_messages[$type]))
                {
                    if (FALSE === ($line = $this->CI->lang->line($type)))
                    {
                        $line = 'The field was not set';
                    }
                }
                else
                {
                    $line = $this->_error_messages[$type];
                }

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

                // 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;
        }

        // --------------------------------------------------------------------

        // Cycle through each rule and run it
        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 AND 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
            {
                $postdata = $this->_field_data[$row['field']]['postdata'];
            }

            // --------------------------------------------------------------------

            // Is the rule a callback?
            $callback = FALSE;
            if (substr($rule, 0, 9) == 'callback_')
            {
                $rule = substr($rule, 9);
                $callback = TRUE;
            }

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

            // Call the function that corresponds to the rule
            if ($callback === TRUE)
            {
                if ( ! method_exists($this->CI, $rule))
                {
                    continue;
                }

                // Run the function and grab the result
                $result = $this->CI->$rule($postdata, $param);

                // 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;
                }

                // If the field isn't required and we just processed a callback we'll move on...
                if ( ! in_array('required', $rules, TRUE) AND $result !== FALSE)
                {
                    continue;
                }
            }
            else
            {
                if ( ! 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))
                    {
                        $result = $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);
                    }

                    continue;
                }

                $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)
            {
                if ( ! isset($this->_error_messages[$rule]))
                {
                    if (FALSE === ($line = $this->CI->lang->line($rule)))
                    {
                        $line = 'Unable to access an error message corresponding to your field name.';
                    }
                }
                else
                {
                    $line = $this->_error_messages[$rule];
                }

                // 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]) AND isset($this->_field_data[$param]['label']))
                {
                    $param = $this->_translate_fieldname($this->_field_data[$param]['label']);
                }

                // Build the error message
                $message = sprintf($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;
            }
        }
    }
}

UPDATE:

Checkbox line was avoiding keep checking. Just add the new line I added, and it'll work. You have to add the keep_checking rule to any field you want to check:

class Welcome extends CI_Controller {

    /**
     * Index Page for this controller.
     *
     * Maps to the following URL
     *      http://example.com/index.php/welcome
     *  - or -
     *      http://example.com/index.php/welcome/index
     *  - or -
     * Since this controller is set as the default controller in
     * config/routes.php, it's displayed at http://example.com/
     *
     * So any other public methods not prefixed with an underscore will
     * map to /index.php/welcome/<method_name>
     * @see http://codeigniter.com/user_guide/general/urls.html
     */
    public function index()
    {
        $this->load->view('welcome_message');
    }

    public function test()
    {
        $this->load->library('form_validation');

        $this->form_validation->set_rules('name',       'Name',     'keep_checking|required');
        $this->form_validation->set_rules('surname',    'Surname',  'keep_checking|is_numeric');

        if ( $this->form_validation->run() ) {

        } else {
            $this->load->view('form');
        }
    }
}

View: form.php

<form action="test" method="post">

    <?php echo validation_errors(); ?>

    <p>
        Name: <input name="name">
    </p>
    <p>
        Surname: <input name="surname">
    </p>
    <p>
        <input type="submit" value="Send">
    </p>
</form>

After submit that form, you'll see as CI check all rules from the input fields. Last point, don't forget that MY_Form_validation goes inside of libraries folder

Result of the checking

2 Comments

Did you test this? I have copied your code into MY_Form_validation.php, added my custom rules before the last } and added your keep_checking rule; it doesn't seem to change anything.
You were right, didn't word as expected. I corrected it and added a simple test to allow you to check it. Now, every rule that has keep_checking will check all the rules even if the field is empty, XD. Be aware that this could make you lead to some errors if the rule requires have a value, so, check your callbacks!
1

In my update methods I only wanted to submit fields that were dirty. Not all fields were required and validation was failing if one a field that needed no validation was sent as empty.

So if the user wanted to remove their phone it would be sent like phone:"" and the validation wouldn't see it if I tried to pass it like so.

if($this-put("phone")) $this->form_validation->set_rules('phone', 'Phone', 'trim');

So I had to use array_key_exist() for it to see it and pass it, even it it was empty.

if($this->put("description")) $this->form_validation->set_rules('description', 'Description', 'trim|required');

if(array_key_exists("phone", $this->input->post())) $this->form_validation->set_rules('phone', 'Phone', 'trim');

Comments

0

I think what you are looking for is callbacks

You can define callbacks in your rule

$this->form_validation->set_rules('field1', 'Field 1', 'trim|callback_field1_check');
$this->form_validation->set_rules('field2', 'Field 2', 'callback_field2_check');

And now you can have a function with boolean return value.

public function field1_check($input) {
    if ($input != '') {
        $this->field1Set = true;
    }
}

public function field2_check($input) {
    // do something on $input
    $input = trim($input);
    // awesome thing is, you get to access all the field variables of your control here
    // so in some other function, you'll toggle a boolean to note that an optional field was filled
    // that variable set by other validation callback, you can use here
    if ($this->field1Set === true && $input == '') return false;
    return true;
}

2 Comments

I would need to add this to every controller that needs it though, right? Not quite as tidy as putting it inside MY_Form_validation.php with all the other rules.
I answered my own question, but if you can think of a better solution the bounty still stands.
0

I've worked out a way to do this myself by editing system/libraries/Form_validation.php.

I changed $callback to TRUE on line 487:

$callback = TRUE;

And commented out lines 488 - 500:

    if ( ! in_array('required', $rules) AND is_null($postdata))
    {
        // Before we bail out, does the rule contain a callback?
        if (preg_match("/(callback_\w+(\[.*?\])?)/", implode(' ', $rules), $match))
        {
            $callback = TRUE;
            $rules = (array('1' => $match[1]));
        }
        else
        {
            return;
        }
    }

The bounty still stands if someone can think of a solution without editing CodeIgniter's system files.

Comments

0
function add($id = '') {

    $this->form_validation->set_rules('title', 'Title', 'trim|required');
    $this->form_validation->set_rules('title_description', 'title_description', 'trim|required');
    $this->form_validation->set_rules('color', 'color', 'trim|required');
    $this->form_validation->set_rules('button', 'button', 'trim|required');
    //$this->form_validation->set_rules('description', 'Description', 'trim|required');
    if ($this->form_validation->run() == FALSE) {
        echo "Not Valid";
    } else {
        echo "Valid";
    }
}

Comments

0

You can add an hidden input in the view with permanent value and test it in validates rules.

In view:

<input type="hidden" name="id_form" value="1"/>

In model or controller (it depends of your architecture)

 public function validates_rules(){
        $this->form_validation->set_rules('id_form', 'Title', 'callback_values_check');
    ...
        }


   public function values_check($id_form){
       if($this->input->post('other_value_to_test')){
...
       }
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.