Forms 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::MinLength, 'Password must be at least %d characters', 8);
Validation rules are checked only if the user filled in the element.
Nette comes with a number of predefined rules whose names are constants of the class Nette\Forms\Form
. We can
apply these rules to all elements:
constant | description | arguments |
---|---|---|
Required |
alias of setRequired() |
– |
Filled |
alias of setRequired() |
– |
Blank |
must not be filled | – |
Equal |
value is equal to parameter | mixed |
NotEqual |
value is not be equal to parameter | mixed |
IsIn |
value is equal to some element in the array | array |
IsNotIn |
value does not equal any element in the array | array |
Valid |
input passes validation (for conditions) | – |
Text inputs
For elements addText()
, addPassword()
, addTextArea()
, addEmail()
,
addInteger()
, addFloat()
some of the following rules can also be applied:
MinLength |
minimal string length | int |
MaxLength |
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 |
PatternInsensitive |
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 PatternInsensitive
must be valid for the whole value, i.e. as if it were wrapped in the characters ^
and $
.
Number of Items
For the elements addMultiUpload()
, addCheckboxList()
, addMultiSelect()
you can also use
the following rules to limit the number of selected items or uploaded files:
MinLength |
minimum number | int |
MaxLength |
maximum number | int |
Length |
number in range or exact number | pairs [int, int] or int |
File Upload
For controls addUpload()
, addMultiUpload()
the following rules can also be used:
MaxFileSize |
maximal file size in bytes | int |
MimeType |
MIME type, accepts wildcards ('video/*' ) |
string|string[] |
Image |
uploaded file is JPEG, PNG, GIF, WebP, AVIF | – |
Pattern |
file name matches regular expression | string |
PatternInsensitive |
like Pattern , but case-insensitive |
string |
The MimeType
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.
Error Messages
All predefined rules except Pattern
and PatternInsensitive
have a default error message, so they can
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 modifying
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::MaxLength, 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(/* ... */);
In Nette, it is very easy to react to the fulfillment or not of a condition on the JavaScript side using the
toggle()
method, see Dynamic JavaScript.
Reference to Another Element
As an argument for a rule or condition, you can also pass another form element. The rule will then use the value entered later
by the user in the browser. This can be used, for example, to dynamically validate that the password
element contains
the same string as the password_confirm
element:
$form->addPassword('password', 'Password');
$form->addPassword('password_confirm', 'Confirm Password')
->addRule($form::Equal, 'The passwords do not match', $form['password']);
Custom Rules and Conditions
Sometimes we get into a situation where the built-in validation rules in Nette are not enough and we need to validate the data from the user in our own way. In Nette this is very easy!
You can pass any callback as the first parameter to the addRule()
or addCondition()
methods. The
callback accepts the element itself as the first parameter and returns a boolean value indicating whether the validation was
successful. When adding a rule using addRule()
, additional arguments can be passed, and these are then passed as the
second parameter.
The custom set of validators can thus be created as a class with static methods:
class MyValidators
{
// tests whether the value is divisible by the argument
public static function validateDivisibility(BaseControl $input, $arg): bool
{
return $input->getValue() % $arg === 0;
}
public static function validateEmailDomain(BaseControl $input, $domain)
{
// additional validators
}
}
The usage is then very simple:
$form->addInteger('num')
->addRule(
[MyValidators::class, 'validateDivisibility'],
'The value must be a multiple of %d',
8,
);
Custom validation rules can also be added to JavaScript. The only requirement is that the rule must be a static method. Its
name for the JavaScript validator is created by concatenating the class name without backslashes \
, the underscore
_
, and the method name. For example, write App\MyValidators::validateDivisibility
as
AppMyValidators_validateDivisibility
and add it to the Nette.validators
object:
Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => {
return val % args === 0;
};
Event onValidate
After the form is submitted, validation is performed by checking the individual rules added by addRule()
and then
calling event onValidate
. Its handler can be used for
additional validation, typically to verify the correct combination of values in multiple form elements.
If an error is detected, it is passed to the form using the addError()
method. This can be called either on a
specific element or directly on the form.
protected function createComponentSignInForm(): Form
{
$form = new Form;
// ...
$form->onValidate[] = [$this, 'validateSignInForm'];
return $form;
}
public function validateSignInForm(Form $form, \stdClass $data): void
{
if ($data->foo > 1 && $data->bar > 5) {
$form->addError('This combination is not possible.');
}
}
Processing Errors
In many cases, we discover an error when we are processing a valid form, e.g. when we write a new entry to the database and
encounter a duplicate key. In this case, we pass the error back to the form using the addError()
method. This can be
called either on a specific item or directly on the form:
try {
$data = $form->getValues();
$this->user->login($data->username, $data->password);
$this->redirect('Home:');
} catch (Nette\Security\AuthenticationException $e) {
if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) {
$form->addError('Invalid password.');
}
}
If possible, we recommend adding the error directly to the form element, as it will appear next to it when using the default renderer.
$form['date']->addError('Sorry, this date is already taken.');
You can call addError()
repeatedly to pass multiple error messages to a form or element. You get them with
getErrors()
.
Note that $form->getErrors()
returns a summary of all error messages, even those passed directly to individual
elements, not just directly to the form. Error messages passed only to the form are retrieved via
$form->getOwnErrors()
.
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, i.e. the
filter and the rule are called in the same order as is the order of the addFilter()
and addRule()
methods.
JavaScript Validation
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://unpkg.com/nette-forms@3"></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();
Dynamic JavaScript
Do you only want to show the address fields only if the user chooses to send the goods by post? No problem. The key is a pair
of methods addCondition()
& toggle()
:
$form->addCheckbox('send_it')
->addCondition($form::Equal, true)
->toggle('#address-container');
This code says that when the condition is met, that is, when the checkbox is checked, the HTML element
#address-container
will be visible. And vice versa. So, we place the form elements with the recipient's address in a
container with that ID, and when the checkbox is clicked, they are hidden or shown. This is handled by the
netteForms.js
script.
Any selector can be passed as an argument to the toggle()
method. For historical reasons, an alphanumeric string
with no other special characters is treated as an element ID, the same as if it were preceded by the #
character. The
second optional parameter allows us to reverse the behavior, i.e. if we used toggle('#address-container', false)
, the
element would be displayed only if the checkbox was unchecked.
The default JavaScript implementation changes the hidden
property for elements. However, we can easily change the
behavior, for example by adding an animation. Just override the Nette.toggle
method in JavaScript with a custom
solution:
Nette.toggle = (selector, visible, srcElement, event) => {
document.querySelectorAll(selector).forEach((el) => {
// hide or show 'el' according to the value of 'visible'
});
};
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.