From 4b90a3746e929f6bbf2b83c71948665c0c31d1fe Mon Sep 17 00:00:00 2001 From: Andrey Andreev Date: Mon, 10 Mar 2014 10:24:24 +0200 Subject: Add support for callable form validation rules --- system/database/drivers/mysqli/mysqli_driver.php | 2 +- system/libraries/Form_validation.php | 101 ++++++++++++++------- user_guide_src/source/changelog.rst | 1 + .../source/libraries/form_validation.rst | 38 +++++++- 4 files changed, 106 insertions(+), 36 deletions(-) diff --git a/system/database/drivers/mysqli/mysqli_driver.php b/system/database/drivers/mysqli/mysqli_driver.php index 6ab304901..c5565f629 100644 --- a/system/database/drivers/mysqli/mysqli_driver.php +++ b/system/database/drivers/mysqli/mysqli_driver.php @@ -163,7 +163,7 @@ class CI_DB_mysqli_driver extends CI_DB { */ protected function _db_set_charset($charset) { - return @$this->conn_id->set_charset($charset); + return $this->conn_id->set_charset($charset); } // -------------------------------------------------------------------- diff --git a/system/libraries/Form_validation.php b/system/libraries/Form_validation.php index 0c4f94914..0a80888d2 100644 --- a/system/libraries/Form_validation.php +++ b/system/libraries/Form_validation.php @@ -144,7 +144,7 @@ class CI_Form_validation { * Set Rules * * This function takes an array of field names and validation - * rules as input, any custom error messages, validates the info, + * rules as input, any custom error messages, validates the info, * and stores it * * @param mixed $field @@ -153,7 +153,7 @@ class CI_Form_validation { * @param array $errors * @return CI_Form_validation */ - public function set_rules($field, $label = '', $rules = '', $errors = array()) + public function set_rules($field, $label = '', $rules = array(), $errors = array()) { // No reason to set rules if we have no POST data // or a validation array has not been specified @@ -187,26 +187,32 @@ class CI_Form_validation { return $this; } - // Convert an array of rules to a string - if (is_array($rules)) - { - $rules = implode('|', $rules); - } - // No fields? Nothing to do... - if ( ! is_string($field) OR ! is_string($rules) OR $field === '') + if ( ! is_string($field) OR $field === '') { return $this; } + elseif ( ! is_array($rules)) + { + // BC: Convert pipe-separated rules string to an array + if (is_string($rules)) + { + $rules = explode('|', $rules); + } + else + { + return $this; + } + } // If the field label wasn't passed we use the field name $label = ($label === '') ? $field : $label; // Is the field name an array? If it is an array, we break it apart // into its components so that we can fetch the corresponding POST data later - $indexes = array(); - if (preg_match_all('/\[(.*?)\]/', $field, $matches)) + if (($is_array = (bool) preg_match_all('/\[(.*?)\]/', $field, $matches)) === TRUE) { + $indexes = array(); sscanf($field, '%[^[][', $indexes[0]); for ($i = 0, $c = count($matches[0]); $i < $c; $i++) @@ -219,10 +225,6 @@ class CI_Form_validation { $is_array = TRUE; } - else - { - $is_array = FALSE; - } // Build our master array $this->_field_data[$field] = array( @@ -468,7 +470,7 @@ class CI_Form_validation { continue; } - $this->_execute($row, explode('|', $row['rules']), $this->_field_data[$field]['postdata']); + $this->_execute($row, $row['rules'], $this->_field_data[$field]['postdata']); } // Did we end up with any errors? @@ -591,19 +593,33 @@ class CI_Form_validation { if ( ! in_array('required', $rules) && ($postdata === NULL OR $postdata === '')) { // Before we bail out, does the rule contain a callback? - if (preg_match('/(callback_\w+(\[.*?\])?)/', implode(' ', $rules), $match)) + foreach ($rules as &$rule) { - $callback = TRUE; - $rules = array(1 => $match[1]); + if (is_string($rule)) + { + if (strncmp($rule, 'callback_', 9) === 0) + { + $callback = TRUE; + $rules = array(1 => $rule); + break; + } + } + elseif (is_callable($rule)) + { + $callback = TRUE; + $rules = array(1 => $rule); + break; + } } - else + + if ( ! $callback) { return; } } // Isset Test. Typically this rule will only apply to checkboxes. - if (($postdata === NULL OR $postdata === '') && $callback === FALSE) + if (($postdata === NULL OR $postdata === '') && ! $callback) { if (in_array('isset', $rules, TRUE) OR in_array('required', $rules)) { @@ -668,39 +684,55 @@ class CI_Form_validation { // 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']; + ? NULL + : $this->_field_data[$row['field']]['postdata']; } // Is the rule a callback? - $callback = FALSE; - if (strpos($rule, 'callback_') === 0) + $callback = $callable = FALSE; + if (is_string($rule)) { - $rule = substr($rule, 9); - $callback = TRUE; + if (strpos($rule, 'callback_') === 0) + { + $rule = substr($rule, 9); + $callback = TRUE; + } + } + elseif (is_callable($rule)) + { + $callable = TRUE; } // Strip the parameter (if exists) from the rule // Rules can contain a parameter: max_length[5] $param = FALSE; - if (preg_match('/(.*?)\[(.*)\]/', $rule, $match)) + if ( ! $callable && preg_match('/(.*?)\[(.*)\]/', $rule, $match)) { $rule = $match[1]; $param = $match[2]; } // Call the function that corresponds to the rule - if ($callback === TRUE) + if ($callback OR $callable) { - if ( ! method_exists($this->CI, $rule)) + if ($callback) { - log_message('debug', 'Unable to find callback validation rule: '.$rule); - $result = FALSE; + 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 { - // Run the function and grab the result - $result = $this->CI->$rule($postdata, $param); + $result = is_array($rule) + ? $rule[0]->{$rule[1]}($postdata, $param) + : $rule($postdata, $param); } // Re-assign the result to the master data array @@ -725,6 +757,7 @@ class CI_Form_validation { // 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) diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst index 423755b89..36ddbc92e 100644 --- a/user_guide_src/source/changelog.rst +++ b/user_guide_src/source/changelog.rst @@ -345,6 +345,7 @@ Release Date: Not Released - :doc:`Language ` line keys must now be prefixed with **form_validation_**. - Added rule **alpha_numeric_spaces**. - Added support for custom error messages per field rule. + - Added support for callable rules when they are passed as an array. - :doc:`Caching Library ` changes include: diff --git a/user_guide_src/source/libraries/form_validation.rst b/user_guide_src/source/libraries/form_validation.rst index 988d6fa25..e51630905 100644 --- a/user_guide_src/source/libraries/form_validation.rst +++ b/user_guide_src/source/libraries/form_validation.rst @@ -474,6 +474,42 @@ then it will be passed as the second argument of your callback method. boolean TRUE/FALSE it is assumed that the data is your newly processed form data. +Callable: Use anything as a rule +================================ + +If callback rules aren't good enough for you (for example, because they are +limited to your controller), don't get disappointed, there's one more way +to create custom rules: anything that ``is_callable()`` would return TRUE for. + +Consider the following example:: + + $this->form_validation->set_rules( + 'username', 'Username', + array( + 'required', + array($this->users_model, 'valid_username') + ) + ); + +The above code would use the ``valid_username()`` method from your +``Users_model`` object. + +This is just an example of course, and callbacks aren't limited to models. +You can use any object/method that accepts the field value as its' first +parameter. Or if you're running PHP 5.3+, you can also use an anonymous +function: + + $this->form_validation->set_rules( + 'username', 'Username', + array( + 'required', + function($value) + { + // Check $value and return TRUE/FALSE + } + ) + ); + .. _setting-error-messages: Setting Error Messages @@ -491,7 +527,7 @@ If you need to set a custom error message for a particular field on some particular rule, use the set_rules() method:: $this->form_validation->set_rules('field_name', 'Field Label', 'rule1|rule2|rule3', - array('rule2' => 'Error Message on rule2 for this field_name') + array('rule2' => 'Error Message on rule2 for this field_name') ); Where rule corresponds to the name of a particular rule, and Error -- cgit v1.2.3-24-g4f1b