Tuesday, November 27, 2012 - 03:24

Drupal 7. Injecting form validators the right way.

Drupal has a very neat feature for form validations. The form holds a flat array '#validate' that is simply a list of functions to be executed when validating the form in question. Our example validator for today will be the login form. The array looks like this by default.

form['#validate'] = (
	'user_login_name_validate',
	'user_login_authenticate_validate',
	'user_login_final_validate',
);

Those validators are simply executed one after another. If one sets a form error the form is considered invalid. This is true for every form in Drupal.

Adding your own validator is as simple as adding it to the array. I.e.

$form['#validate][] = 'my_form_validator';

Now our validator is in the chain and will be executed. In this case at the end as we added it to the end. In most cases it doesn't matter where you add it. This has two reasons.

The form is considered invalid if one validator invalidates it. It doesn't matter if it's the first or the last.

Relying on information by other validators is not that good practice because you cannot assume they are there in the first place. Some module might - although it shouldn't - have replaced them.

It's your validator. So validate whatever you have to validate. Requiring it in a specific position is not a good idea.

But for the sake of the argument. Let's assume you need it right before user_login_final_validate. Do not rewrite the entire array as it will break other modules. You can always unshift it to the front or splice it. We are going to splice it since we want it to sit right before user_login_validate.

	// first let's define our replacement.
	$replace = array(
		'my_form_validator',
		'user_login_final_validate',
	);
 
	// now let's see if the array actually contains prey
 
	if( in_array( 'user_login_final_validate', $form['#validate'] )
	{
		// found it. But where is it in the array?
 
		$key = 0;
 
		foreach( $form['#validate'] as $validator )
		{
			if( $validator == 'user_login_final_validate' )
				break;
 
			$key++;
		}
 
		// $key now holds the index. So let's splice the array.
 
		array_splice( $form['#validate'], $key, 1, $replace );
	}
	else
	{
		/* our prey isn't in the array so let's add it to the end
		 * for the sake of this example */
 
		$form['#validate'][] = 'user_login_final_validate;
	}

So what is our splicing doing there? We replace the element at index $key, which is our desired target, with itself and our own prepended function from $replace. So our final array now looks like this.

form['#validate'] = (
	'user_login_name_validate',
	'user_login_authenticate_validate',
	'my_form_validator',
	'user_login_final_validate',
);

Injecting the validator in a specific position should never be necessary since other folks may get the very same idea and thus might break yours. It however can be advisable to place it before or after a well known validator...for whatever reason you may have.

The validator itself is a function taking two parameters.$form and $form_state. So our function looks like this.
function my_form_validator( $form, $form_state ){}

$form_state holds the current state of the form. In other words...the values submitted. We simple do our form validation on it and then...well depends. If everything is fine we simply do nothing. If we found a reason to invalidate it we trigger form_set_error() and that's it. The function can take parameters for our field ( if we have any ) and a message presented to the user. I.e.
form_set_error( 'field', t( 'Bad value' ) ).

Add new comment

This form is protected by Google Recaptcha. By clicking here you agree to include Google Recaptcha for this session. The page will reload and the form will become avaiable.