How to make CodeIgniter callable work for checkboxes that are left empty, custom form validation bug
You can create your rules in a form_validation file inside /config
application/config/form_validation.php
$config = array( 'controller/method' => array( array('field'=>'', 'label'=>'', 'rules'=>'required|acceptTerms') array('field'=>'another', 'label'=>'', 'rules'=>'required') ),);
Note the controller/method
for the key, Codeigniter will use this if you don't set it specifically inside the calling form_validation function.
An example of this would be like so
application/controllers/Shop
class Shop extends CI_Controller{ public function __construct(){ parent::__construct(); } public function index() { // Show the Purchase Form // in some view return $this->load->view(); } public function purchase() { // notice we don't pass any params to the run() function // codeigniter will look inside application/config/form_validation/$config // for any matching key, ie: shop/purchase if(!$this->form_validation->run()){ // If validation fails, show the form again // and stop the method from executing any further return $this->index(); } }}
To validate if check boxes are set, we look for the keyword on
application/libraries/MY_Form_validation.php
class MY_Form_validation extends CI_Form_validation{ public function __construct($config) { parent::__construct( $config ); } public function acceptTerms( $field ) { $this->set_error('acceptTerms', 'your error message'); return (bool) $field == 'on'; }}
$terms = $this->input->post('terms');var_dump((int)$checked); //Just to see the value, then remove this line.if((int)$checked == 1) { //Checked = True} else { //Checked = False}
I'll check about the required flag and edit this answer.
Edit:Instead of just required
, try to use required|isset
, then when you perform callback_terms_accepted, do something like this:
function terms_accepted($value) { if (isset($checked)) { return true; } else { $this->form_validation->set_message('terms_accepted', 'Error message'); return false; }}
That should do the trick.
Again, hope that helps mate.
Alright, I managed to fix my problem. The issue here was as I suspected a bug in CodeIgniter related to callables specifically.
NOTE: This bug seems to have been fixed in CI 3.0.1+. I was running version 3.0.0.
The problem
The problem is that the Form_validation library has a piece of code in the _execute
function that checks if there is either a required
rule or a callback rule set for a field that isn't posted. This applies to checkboxes since they aren't part of the $_POST
array when left empty. This is the code that causes the issue:
$callback = FALSE;if ( ! in_array('required', $rules) && ($postdata === NULL OR $postdata === '')){ // Before we bail out, does the rule contain a callback? foreach ($rules as &$rule) { 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; } } if ( ! $callback) { return; }}
This code is used to skip validation entirely for a field if it is not required or has a callback. However, the CI devs made the mistake of checking for callbacks with is_callable
. This of course is fine for normal callables that are structured like this:
array($this->some_model_or_library, 'function_name')
But, CodeIgniter allows you to name your callback in order to set validation errors for it like this:
array('my_callback_function', array($this->some_model_or_library, 'function_name'))
Unsurprisingly, is_callable
returns false when applied to this array, and thus validation is skipped.
Relevant docs: http://www.codeigniter.com/user_guide/libraries/form_validation.html#callable-use-anything-as-a-rule
The solution
Personally, I didn't see the use of the abovementioned code, because I never want to skip validation when a field isn't posted. So I solved the problem by creating a MY_Form_validation
class and overriding the _execute
function, simply replacing the code with:
$callback = TRUE;
Of course, a slightly more conservative solution would be to check for multidimensional arrays and apply is_callable
to the appropriate element like so:
if (is_callable($rule) // Original check.|| (is_array($callback) && is_array($callback[1]) // Check if the callback is an array and contains an array as second element.&& is_callable($callback[1])) // Check if the second element is a callable.