Form Validation
Required Controls
Controls are marked as required with the method setRequired()
, whose argument is the text of the error message that will be displayed if the user does not fill it. If no argument is given, the
default error message is used.
$form->addText('name', 'Name:')
->setRequired('Please fill your name.');
Rules
We add validation rules to controls with the addRule()
method. The first parameter is the rule, the second is the
error message, and the third is the validation rule argument.
$form->addPassword('password', 'Password:')
->addRule($form::MIN_LENGTH, 'Password must be at least %d characters', 8)
Nette comes with a number of built-in rules whose names are constants of the Nette\Forms\Form
class:
We can use the following rules for all controls:
constant | description | arguments |
---|---|---|
REQUIRED |
alias of setRequired() |
– |
FILLED |
alias of setRequired() |
– |
BLANK |
must not be filled | – |
EQUAL |
value is equal to parameter | mixed |
NOT_EQUAL |
value is not be equal to parameter | mixed |
IS_IN |
value is equal to some element in the array | array |
IS_NOT_IN |
value does not equal any element in the array | array |
VALID |
input passes validation (for conditions) | – |
For controls addText()
, addPassword()
, addTextArea()
, addEmail()
,
addInteger()
the following rules can also be used:
MIN_LENGTH |
minimal string length | int |
MAX_LENGTH |
maximal string length | int |
LENGTH |
length in range or exact length | pair [int, int] or int |
EMAIL |
valid email address | – |
URL |
valid URL | – |
PATTERN |
matches regular pattern | string |
PATTERN_ICASE |
like PATTERN , but case-insensitive |
string |
INTEGER |
integer | – |
NUMERIC |
alias of INTEGER |
– |
FLOAT |
integer or floating point number | – |
MIN |
minimum of the integer value | int|float |
MAX |
maximum of the integer value | int|float |
RANGE |
value in the range | pair [int|float, int|float] |
The INTEGER
, NUMERIC
a FLOAT
rules automatically convert the value to integer (or float
respectively). Furthermore, the URL
rule also accepts an address without a schema (eg nette.org
) and
completes the schema (https://nette.org
). The expressions in PATTERN
and PATTERN_ICASE
must
be valid for the whole value, ie as if it were wrapped in the characters ^
and $
.
For controls addUpload()
, addMultiUpload()
the following rules can also be used:
MAX_FILE_SIZE |
maximal file size | int |
MIME_TYPE |
MIME type, accepts wildcards ('video/*' ) |
string|string[] |
IMAGE |
uploaded file is JPEG, PNG, GIF, WebP | – |
PATTERN |
file name matches regular expression | string |
PATTERN_ICASE |
like PATTERN , but case-insensitive |
string |
The MIME_TYPE
and IMAGE
require PHP extension fileinfo
. Whether a file or image is of
the required type is detected by its signature. The integrity of the entire file is not checked. You can find out if an image is
not corrupted for example by trying to load it.
For controls addMultiUpload()
, addCheckboxList()
, addMultiSelect()
the following rules
can also be used to limit the number of selected items, respectively uploaded files:
MIN_LENGTH |
minimal count | int |
MAX_LENGTH |
maximal count | int |
LENGTH |
count in range or exact count | pair [int, int] or int |
Error Messages
All predefined rules except PATTERN
and PATTERN_ICASE
have a default error message, so they it be
omitted. However, by passing and formulating all customized messages, you will make the form more user-friendly.
You can change the default messages in configuration, by modifing the texts in the
Nette\Forms\Validator::$messages
array or by using translator.
The following wildcards can be used in the text of error messages:
%d |
gradually replaces the rules after the arguments |
%n$d |
replaces with the nth rule argument |
%label |
replaces with field label (without colon) |
%name |
replaces with field name (eg name ) |
%value |
replaces with value entered by the user |
$form->addText('name', 'Name:')
->setRequired('Please fill in %label');
$form->addInteger('id', 'ID:')
->addRule($form::RANGE, 'at least %d and no more than %d', [5, 10]);
$form->addInteger('id', 'ID:')
->addRule($form::RANGE, 'no more than %2$d and at least %1$d', [5, 10]);
Conditions
Besides validation rules, conditions can be set. They are set much like rules, yet we use addRule()
instead of
addCondition()
and of course, we leave it without an error message (the condition just asks):
$form->addPassword('password', 'Password:')
// if password is not longer than 8 characters ...
->addCondition($form::MAX_LENGTH, 8)
// ... then it must contain a number
->addRule($form::PATTERN, 'Must contain number', '.*[0-9].*');
Condition can be linked to a different element than the current one using addConditionOn()
. The first parameter is
a reference to the field. In the following case, the email will only be required if the checkbox is checked (ie. its value is
true
):
$form->addCheckbox('newsletters', 'send me newsletters');
$form->addEmail('email', 'Email:')
// if checkbox is checked ...
->addConditionOn($form['newsletters'], $form::EQUAL, true)
// ... require email
->setRequired('Fill your email address');
Conditions can be grouped into complex structures with elseCondition()
and endCondition()
methods.
$form->addText(...)
->addCondition(...) // if the first condition is met
->addConditionOn(...) // and the second condition on another element too
->addRule(...) // require this rule
->elseCondition() // if the second condition is not met
->addRule(...) // require these rules
->addRule(...)
->endCondition() // we return to the first condition
->addRule(...)
References Between Controls
The rule or condition argument can be a reference to another element. For example, you can dynamically validate that the
text
has as many characters as the value of the length
field is:
$form->addInteger('length');
$form->addText('text')
->addRule($form::LENGTH, null, $form['length']);
Custom Rules
We can create our own rules or conditions. As a rule, we pass any PHP callback.
class MyValidators
{
// tests whether the value is divisible by an argument
public static function divisibilityValidator(BaseControl $input, $arg): bool
{
return $input->getValue() % $arg === 0;
}
}
$input->addRule(
'MyValidators::divisibilityValidator',
'Number must be multiple of %d',
8
);
Modifying Input Values
Using the addFilter()
method, we can modify the value entered by the user. In this example, we will tolerate and
remove spaces in the zip code:
$form->addText('zip', 'Postcode:')
->addFilter(function ($value) {
return str_replace(' ', '', $value); // remove spaces from the postcode
})
->addRule($form::PATTERN, 'The postal code is not five digits', '\d{5}');
The filter is included between the validation rules and conditions and therefore depends on the order of the methods, ie the
filter and the rule are called in the same order as is the order of the addFilter()
and addRule()
methods.
JavaScript
The language of validation rules and conditions is powerful. Even though all constructions work both server-side and
client-side, in JavaScript. Rules are transferred in HTML attributes data-nette-rules
as JSON. The validation itself
is handled by another script, which hooks all form's submit
events, iterates over all inputs and runs respective
validations.
This script is netteForms.js
, which is available from several possible sources:
You can embed the script directly into the HTML page from the CDN:
<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script>
Or copy locally to the public folder of the project (e.g. from
vendor/nette/forms/src/assets/netteForms.min.js
):
<script src="/path/to/netteForms.min.js"></script>
Or install via npm:
npm install nette-forms
And then load and run:
import netteForms from 'nette-forms';
netteForms.initOnLoad();
Alternatively, you can load it directly from the folder vendor
:
import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js';
netteForms.initOnLoad();
Custom Rules in JS
Custom validation rules can also be added to JavaScript. The condition is that the callback of the custom rule on the PHP side
is a global function or a static method. Then we add its JavaScript equivalent to the JS object Nette.validators
:
<script>
Nette.validators.divisibilityValidator = function(elem, args, val) {
return val % args === 0;
};
</script>
If PHP validation callback is a static method, we derive the name for JavaScript by removing all backslashes \
and
by replacing the double colon by single underscore _
, e.g. App\MyValidator::divisibilityValidator
is
converted to AppMyValidator_divisibilityValidator
.
Disabling Validation
In certain cases, you need to disable validation. If a submit button isn't supposed to run validation after submitting (for
example Cancel or Preview button), you can disable the validation by calling
$submit->setValidationScope([])
. You can also validate the form partially by specifying items to be validated.
$form->addText('name')
->setRequired();
$details = $form->addContainer('details');
$details->addInteger('age')
->setRequired('age');
$details->addInteger('age2')
->setRequired('age2');
$form->addSubmit('send1'); // Validates the whole form
$form->addSubmit('send2')
->setValidationScope([]); // Validates nothing
$form->addSubmit('send3')
->setValidationScope([$form['name']]); // Validates only 'name' field
$form->addSubmit('send4')
->setValidationScope([$form['details']['age']]); // Validates only 'age' field
$form->addSubmit('send5')
->setValidationScope([$form['details']]); // Validates 'details' container
Event onValidate on the form is always invoked and is not affected by the
setValidationScope
. onValidate
event on the container is invoked only when this container is specified
for partial validation.
Postprocessing Errors
We often find that the user's input is wrong even though each of the form's controls is technically valid. For example,
stumbling upon a duplicate key when inserting a new database row. Or invalid login credentials. In that case, we can add the error
manually with addError()
method. It can be called either on a specific input or on a form itself:
try {
$values = $form->getValues();
$this->user->login($values->username, $values->password);
$this->redirect('Homepage:');
} catch (Nette\Security\AuthenticationException $e) {
if ($e->getCode() === Nette\Security\Authenticator::INVALID_CREDENTIAL) {
$form->addError('Invalid password.');
}
}
It's recommended to link the error directly to a form element because then the error will be displayed next to it when using default renderer.
$form['date']->addError("We apologize but this date has been already taken.");
You can call addError()
repeatedly to add multiple error messages to form or field. You can get them with
getErrors()
.
Note that $form->getErrors()
returns a summary of all error messages, even those that were added directly to
individual controls, not just the form. You can get error messages passed directly to the form via
$form->getOwnErrors()
.
Event onValidate
Validation raises the onValidate
event, the handler of which can be used
for additional validation, typically to verify the correct combination of values in multiple form controls:
protected function createComponentSignInForm(): Form
{
$form = new Form;
...
$form->onValidate[] = [$this, 'validateSignInForm'];
return $form;
}
public function validateSignInForm(Form $form): void
{
$values = $form->getValues();
if (...) { // validation logic
$form->addError('Password does not match.');
}
}
You can bind multiple functions to the event. The function is considered successful if it doesn't add any error using
$form->addError()
.
Related blog posts
News in Nette Forms 3.1
Protection against CSRF using cookies The vulnerability of a CSRF is based on the trick that the form is spoofed by an attacker and sent from his…
Economy, containers and other hot news in Nette Forms
An overview of the most important news in the nette/forms 3.0.x package. Economic checkbox lists The CheckboxList sent by the GET method is not…
What's New in Nette 2.1: Forms
Forms have always been a key part of the framework. Ever since the first version, forms have allowed developers to easily define input elements,…