Rules and validation

Rules and validation – Checking that you get the values you need

Introduction

Server-side validation in HTML_QuickForm2 is performed by HTML_QuickForm2::validate() method. Validation rules doing actual checks on element values are implemented as subclasses of HTML_QuickForm2_Rule, they are added to elements via HTML_QuickForm2_Node::addRule().

Basically, the form is invalid if it contains at least one invalid element. The element is considered invalid if it has an error message (accessible by HTML_QuickForm2_Node::getError()) set and valid otherwise. That error can appear in two different ways:

  • You can manually set an error message for an element using HTML_QuickForm2_Node::setError().
  • A rule added to the element will set an error message if it has such a message and its validation routine returned FALSE.

The latter happens in the course of executing HTML_QuickForm2::validate(). It iterates over all form's elements, for each element calling validate() methods of all its rules in the order they were added. As soon as an error is set on an element, its validation stops.

Do not forget to provide an error message to the rule, otherwise the element will be considered valid even if rule's validation routine returns FALSE. Not setting an error message is only useful when chaining (see below).

Some of the elements may perform additional hardcoded validation. For example, file uploads will check the value of 'error' field in $_FILES and assign a relevant error message when file upload was attempted but failed.

Instantiating Rule objects directly

<?php
require_once 'HTML/QuickForm2.php';
require_once 
'HTML/QuickForm2/Rule/Required.php';
require_once 
'HTML/QuickForm2/Rule/Regex.php';

$form = new HTML_QuickForm2('tutorial');
$username $form->addElement('text''username');
$form->addElement('submit'null, array('value' => 'Send!'));

$username->addRule(new HTML_QuickForm2_Rule_Required(
    
$username'Username is required!'
));
$username->addRule(new HTML_QuickForm2_Rule_Regex(
    
$username'Username should contain only letters, digits and underscores''/^[a-zA-Z0-9_]+$/'
));

if (
$form->validate()) {
    
// process form
}

echo 
$form;
?>

Of course, you will rarely need to instantiate Rule subclasses directly, Rule objects can be created by HTML_QuickForm2_Node::createRule() or automatically by addRule() if first parameter is a string representing registered rule type.

Automatic creation of Rule objects

<?php
require_once 'HTML/QuickForm2.php';

$form = new HTML_QuickForm2('tutorial');
$username $form->addElement('text''username');
$form->addElement('submit'null, array('value' => 'Send!'));

$username->addRule('required''Username is required!');
$username->addRule('regex''Username should contain only letters, digits and underscores'
                   
'/^[a-zA-Z0-9_]+$/');

if (
$form->validate()) {
    
// process form
}

echo 
$form;
?>

Validating containers

Most of the built-in rules are designed to check scalar values and will not work properly if added to a Container (this includes Groups and Group-based elements like Date and Hierselect), as Containers return their values in an associative array. One notable exception is nonempty / required rule that can validate a container (or <select multiple="multiple" />):

Checks that at least two checkboxes in a group are selected

<?php
$boxGroup 
$form->addElement('group''boxes')->setLabel('Check at least two:');
$boxGroup->addElement('checkbox'null, array('value' => 'first'))->setContent('First');
$boxGroup->addElement('checkbox'null, array('value' => 'second'))->setContent('Second');
$boxGroup->addElement('checkbox'null, array('value' => 'third'))->setContent('Third');

$boxGroup->addRule('required''Check at least two boxes'2);
?>

It is of course possible to implement a custom rule that will properly handle an associative array as the element's value. It is also possible to leverage existing "scalar" rules to validate Containers by using each rule, it applies a template rule to all the elements in a Container and considers Container valid if its validation routine returns TRUE for all of them:

Checks that all phone fields in a group contain numeric data

<?php
$phones 
$form->addElement('group''phones')->setLabel('Phones (numeric):')
               ->
setSeparator('<br />');
$phones->addElement('text''0');
$phones->addElement('text''1');

$phones->addRule('each''Phones should be numeric',
                 
$phones->createRule('regex''''/^\\d+([ -]\\d+)*$/'));
?>

More specific rules are run first: rules added to container will be checked after rules added to its contained elements.

Chaining the rules

HTML_QuickForm2 allows validation of elements based on values and validation status of other elements. This is done by building a "chain" of validation rules using HTML_QuickForm2_Rule::and_() and HTML_QuickForm2_Rule::or_() methods. Execution of the chain starts with a rule that was added to an element, then results of other rules' validation routines are combined using corresponding logical operators. Error is only set on the element if the whole chain returned FALSE.

Behaviour of and_() and or_() is similar to PHP's and and or operators:

  • and_() has higher precedence than or_().
  • Evaluation is short-circuited. If first argument of and_() evaluates to FALSE then FALSE is returned without evaluating second argument, if first argument of or_() evaluates to TRUE then TRUE is returned without evaluating second argument.

Rules that are added to the chain behave the same way as the rules that are added directly to the element they validate (this is not necessarily the same element the chain is added to), they will set an error if the rule itself returns FALSE, not the chain. Thus it is often needed not to provide error messages to the rules. It may also make sense to add a chain of rules to a chain (this is similar to adding parentheses to a PHP expression with and and or).

Skips checking email field if "receive email" box is not checked

<?php
$emailPresent 
$email->createRule('nonempty''Supply a valid email if you want to receive our spam');
// note lack of error message here, error should only be set by previous rule
$emailValid   $email->createRule('callback''', array('callback'  => 'filter_var',
                                                         
'arguments' => array(FILTER_VALIDATE_EMAIL)));
// note lack of error message for 'empty' rule, we don't want error on a checkbox
$spamCheck->addRule('empty')
          ->
or_($emailPresent->and_($emailValid));
?>

Checks password fields in password change form

<?php
$newPassword
->addRule('empty')
            ->
and_($repPassword->createRule('empty'))
            ->
or_($newPassword->createRule('minlength''The password is too short'6))
            ->
and_($repPassword->createRule('eq''The passwords do not match'$newPassword))
            ->
and_($oldPassword->createRule('nonempty''Supply old password if you want to change it'));
?>

Client-side validation

Validation library

Client-side validation depends on a JS library residing in quickform.js file. Neither a link to that file nor its contents is automatically included in the output generated by a renderer, the next section describes how you can properly handle including it.

You can tell a rule to also generate Javascript necessary for client-side validation. This is done by passing a $runAt parameter with HTML_QuickForm2_Rule::CLIENT flag set to addRule():

<?php
// if first parameter to addRule() is a string:
$username->addRule('required''Username is required'null,
                   
HTML_QuickForm2_Rule::SERVER HTML_QuickForm2_Rule::CLIENT);
// if first parameter to addRule() is a Rule instance:
$username->addRule($username->createRule('required''Username is required'),
                   
HTML_QuickForm2_Rule::CLIENT_SERVER); // using a shorthand for above constants
?>

If more rules were chained to the added one with and_() and or_(), Javascript will be generated for the whole chain.

Since release 0.6.0 it is possible to run client-side rules for an element on changing its value or on it losing input focus ('onchange' and 'onblur' events) in addition to form submit ('onsubmit' event). This is triggered by passing a $runAt parameter with HTML_QuickForm2_Rule::ONBLUR_CLIENT flag set to addRule(). If a rule has chained rules, then validation will be triggered by all elements appearing in a chain.

<?php
// here validation will be run onchange / onblur of both $newPassword and $repPassword fields
$newPassword->addRule('empty'''nullHTML_QuickForm2_Rule::ONBLUR_CLIENT_SERVER)
            ->
and_($repPassword->createRule('empty'))
            ->
or_($repPassword->createRule('eq''The passwords do not match'$newPassword));
?>

Another change introduced in 0.6.0 is that validation errors are now output near the elements instead of being shown in Javascript alert(). In a nuthshell, client-side validation behaviour in HTML_QuickForm2 0.6.0+ is more similar to that of HTML_QuickForm_DHTMLRulesTableless than to that of old HTML_QuickForm.

Most of the built-in rules are able to run client-side, the only exceptions are maxfilesize and mimetype rules specific for file uploads.

If you want to run callback rule client-side, you will obviously need to implement a callback in Javascript as well as in PHP. If you don't explicitly set 'js_callback' configuration parameter, callback rule will try to run Javascript function having the same name as provided PHP 'callback'. This may be especially useful if you use HTML_AJAX to proxy PHP classes or callbacks in Javascript.

When running regex rules client-side, you should stick to regular expression syntax common in PHP and Javascript:

  • Use a slash / as a delimiter.
  • Use only i, m, u pattern modifiers. If u modifier is used, PHP's Unicode escapes \x{NNNN} are automatically converted to Javascript's Unicode escapes \uNNNN when creating a client-side rule.
  • Do not use regular expession features that are not supported in Javascript (e.g. lookbehind assertions, recursive patterns).

While it is possible to add a client-side only rule

<?php
$username
->addRule('minlength''Username should be at least 4 characters long'4,
                   
HTML_QuickForm2_Rule::CLIENT);
?>

it is not recommended unless you perform the same validation server-side using some other rule.