Rules and validation
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', '', null, HTML_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. Ifu
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.