-
-
Notifications
You must be signed in to change notification settings - Fork 277
Expand file tree
/
Copy pathvalidation.texy
More file actions
376 lines (268 loc) Β· 15.4 KB
/
validation.texy
File metadata and controls
376 lines (268 loc) Β· 15.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
Forms Validation
****************
Required Controls
=================
Controls are marked as required using the `setRequired()` method. Its argument is the text of the [error message |#Error Messages] that will be displayed if the user does not fill in the control. If no argument is provided, the default error message is used.
```php
$form->addText('name', 'Name:')
->setRequired('Please fill in your name.');
```
Rules
=====
We add validation rules to controls using the `addRule()` method. The first parameter is the rule, the second is the [error message |#Error Messages], and the third is the validation rule argument.
```php
$form->addPassword('password', 'Password:')
->addRule($form::MinLength, 'Password must be at least %d characters long', 8);
```
**Validation rules are checked only if the user has filled in the control.**
Nette comes with several predefined rules whose names are constants of the `Nette\Forms\Form` class. We can apply these rules to all controls:
| constant | description | argument type
|-------
| `Required` | required control, alias for `setRequired()` | -
| `Filled` | required control, alias for `setRequired()` | -
| `Blank` | control must not be filled | -
| `Equal` | value must be equal to the parameter | `mixed`
| `NotEqual` | value must not be equal to the parameter | `mixed`
| `IsIn` | value must be one of the items in the array | `array`
| `IsNotIn` | value must not be any of the items in the array | `array`
| `Valid` | is the control filled correctly? (for [#Conditions]) | -
Text inputs
-----------
For controls `addText()`, `addPassword()`, `addTextArea()`, `addEmail()`, `addInteger()`, `addFloat()`, some of the following rules can also be applied:
| `MinLength` | minimum text length | `int`
| `MaxLength` | maximum text length | `int`
| `Length` | length in range or exact length | pair `[int, int]` or `int`
| `Email` | valid email address | -
| `URL` | absolute URL | -
| `Pattern` | matches regular expression | `string`
| `PatternInsensitive` | like `Pattern`, but case-insensitive | `string`
| `Integer` | integer value | -
| `Numeric` | alias for `Integer` | -
| `Float` | number | -
| `Min` | minimum value of a numeric control | `int\|float`
| `Max` | maximum value of a numeric control | `int\|float`
| `Range` | value in range | pair `[int\|float, int\|float]`
The validation rules `Integer`, `Numeric`, and `Float` automatically convert the value to an integer or float, respectively. Furthermore, the `URL` rule also accepts an address without a scheme (e.g., `nette.org`) and completes the scheme (`https://nette.org`). The expression in `Pattern` and `PatternInsensitive` must be valid for the entire value, i.e., as if it were wrapped in `^` and `$` characters.
Number of Items
---------------
For controls `addMultiUpload()`, `addCheckboxList()`, `addMultiSelect()`, you can also use the following rules to limit the number of selected items or uploaded files:
| `MinLength` | minimum count | `int`
| `MaxLength` | maximum count | `int`
| `Length` | count in range or exact count | pair `[int, int]` or `int`
File Upload
-----------
For controls `addUpload()`, `addMultiUpload()`, the following rules can also be used:
| `MaxFileSize` | maximum file size in bytes | `int`
| `MimeType` | MIME type, wildcards allowed (`'video/*'`) | `string\|string[]`
| `Image` | JPEG, PNG, GIF, WebP, AVIF image | -
| `Pattern` | file name matches regular expression | `string`
| `PatternInsensitive` | like `Pattern`, but case-insensitive | `string`
`MimeType` and `Image` require the PHP extension `fileinfo`. Whether a file or image is of the required type is detected based on its signature, and **the integrity of the entire file is not checked.** You can determine if an image is corrupted, for example, by trying to [load it |http:request#toImage].
Error Messages
==============
All predefined rules except `Pattern` and `PatternInsensitive` have a default error message, so they can be omitted. However, by providing and formulating all custom messages tailored to your needs, you will make the form more user-friendly.
You can change the default messages in the [configuration |forms:configuration], by modifying the texts in the `Nette\Forms\Validator::$messages` array, or by using a [translator |rendering#Translating].
The following placeholder strings can be used in the text of error messages:
| `%d` | replaced sequentially by rule arguments
| `%n$d` | replaced by the n-th rule argument
| `%label` | replaced by the control label (without the colon)
| `%name` | replaced by the control name (e.g., `name`)
| `%value` | replaced by the value entered by the user
```php
$form->addText('name', 'Name:')
->setRequired('Please fill in %label');
$form->addInteger('id', 'ID:')
->addRule($form::Range, 'at least %d and at most %d', [5, 10]);
$form->addInteger('id', 'ID:')
->addRule($form::Range, 'at most %2$d and at least %1$d', [5, 10]);
```
Conditions
==========
In addition to rules, conditions can also be added. They are written similarly to rules, but instead of `addRule()`, we use the `addCondition()` method, and naturally, we don't provide an error message (the condition only asks):
```php
$form->addPassword('password', 'Password:')
// if the password length is not greater than 8
->addCondition($form::MaxLength, 8)
// then it must contain a digit
->addRule($form::Pattern, 'Must contain a digit', '.*[0-9].*');
```
The condition can be linked to a control other than the current one using `addConditionOn()`. The first parameter is a reference to the control. In this example, the email will be required only if the checkbox is checked (i.e., its value is true):
```php
$form->addCheckbox('newsletters', 'Send me newsletters');
$form->addEmail('email', 'Email:')
// if the checkbox is checked
->addConditionOn($form['newsletters'], $form::Equal, true)
// then require the email
->setRequired('Enter your email address');
```
Conditions can be formed into complex structures using `elseCondition()` and `endCondition()`:
```php
$form->addText(/* ... */)
->addCondition(/* ... */) // if the first condition is met
->addConditionOn(/* ... */) // and the second condition on another control is also met
->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 non-fulfillment of a condition on the JavaScript side using the `toggle()` method, see [#Dynamic JavaScript].
Reference to Another Control
============================
You can also pass another form control as an argument to a rule or condition. 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` control contains the same string as the `password_confirm` control:
```php
$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 encounter situations where the built-in validation rules in Nette are insufficient, and we need to validate user data in our own way. In Nette, this is very simple!
You can pass any callback as the first parameter to the `addRule()` or `addCondition()` methods. The callback accepts the control 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 provided, which are then passed as the second parameter.
A custom set of validators can thus be created as a class with static methods:
```php
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)
{
// other validators
}
}
```
Usage is then very straightforward:
```php
$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 condition is that the rule must be a static method. Its name for the JavaScript validator is formed by concatenating the class name without backslashes `\`, an underscore `_`, and the method name. For example, `App\MyValidators::validateDivisibility` is written as `AppMyValidators_validateDivisibility` and added to the `Nette.validators` object:
```js
Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => {
return val % args === 0;
};
```
Event onValidate
================
After the form is submitted, validation is performed, checking the individual rules added using `addRule()`, and subsequently, the `onValidate` [event |nette:glossary#Events] is triggered. Its handler can be used for additional validation, typically verifying the correct combination of values in multiple form controls.
If an error is detected, it is passed to the form using the `addError()` method. This can be called either on a specific control or directly on the form.
```php
protected function createComponentSignInForm(): Form
{
$form = new Form;
// ...
$form->onValidate[] = $this->validateSignInForm(...);
return $form;
}
private 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 only discover an error when processing a valid form, for example, when writing a new entry to the database and encountering a duplicate key. In such a case, we again pass the error back to the form using the `addError()` method. This can be called either on a specific control or directly on the form:
```php
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 control, as it will be displayed next to it when using the default renderer.
```php
$form['date']->addError('Sorry, this date is already taken.');
```
You can call `addError()` repeatedly to pass multiple error messages to a form or control. You can retrieve them using `getErrors()`.
Note that `$form->getErrors()` returns a summary of all error messages, including those passed directly to individual controls, not just directly to the form. Error messages passed only to the form can be 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 postal code:
```php
$form->addText('zip', 'Postal Code:')
->addFilter(function ($value) {
return str_replace(' ', '', $value); // remove spaces from the postal code
})
->addRule($form::Pattern, 'Postal code is not five digits', '\d{5}');
```
The filter is integrated among validation rules and conditions, and thus the order of methods matters, i.e., the filter and rule are called in the same order as the `addFilter()` and `addRule()` methods are listed.
JavaScript Validation
=====================
The language for formulating conditions and rules is very powerful. All constructs work both on the server side and on the client side in JavaScript. They are transferred in HTML attributes `data-nette-rules` as JSON. The validation itself is handled by a script that intercepts the form's `submit` event, iterates through the individual controls, and performs the corresponding validation.
This script is `netteForms.js`, and it is available from several possible sources:
You can embed the script directly into the HTML page from a CDN:
```latte
<script src="https://unpkg.com/nette-forms@3"></script>
```
Or copy it locally to the public folder of your project (e.g., from `vendor/nette/forms/src/assets/netteForms.min.js`):
```latte
<script src="/path/to/netteForms.min.js"></script>
```
Or install it via [npm |https://www.npmjs.com/package/nette-forms]:
```shell
npm install nette-forms
```
And then load and run it:
```js
import netteForms from 'nette-forms';
netteForms.initOnLoad();
```
Alternatively, you can load it directly from the `vendor` folder:
```js
import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js';
netteForms.initOnLoad();
```
Dynamic JavaScript
==================
Want to display the address fields only if the user chooses to have the goods sent by post? No problem. The key is the pair of methods `addCondition()` & `toggle()`:
```php
$form->addCheckbox('send_it')
->addCondition($form::Equal, true)
->toggle('#address-container');
```
This code states that when the condition is met (i.e., when the checkbox is checked), the HTML element `#address-container` will be visible, and vice versa. So, we place the form controls with the recipient's address in a container with this ID, and they will hide or show when the checkbox is clicked. 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 without other special characters is treated as an element ID, just as if it were preceded by the `#` character. The second optional parameter allows reversing the behavior; for instance, if we used `toggle('#address-container', false)`, the element would be displayed only if the checkbox was *not* checked.
The default JavaScript implementation changes the `hidden` property of the 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:
```js
Nette.toggle = (selector, visible, srcElement, event) => {
document.querySelectorAll(selector).forEach((el) => {
// hide or show 'el' according to the value of 'visible'
});
};
```
Disabling Validation
====================
Sometimes it might be useful to disable validation. If pressing a submit button should not perform validation (suitable for *Cancel* or *Preview* buttons), we disable it using the `$submit->setValidationScope([])` method. If it should perform only partial validation, we can specify which fields or form containers should be validated.
```php
$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 the 'name' control
$form->addSubmit('send4')
->setValidationScope([$form['details']['age']]); // Validates only the 'age' control
$form->addSubmit('send5')
->setValidationScope([$form['details']]); // Validates the 'details' container
```
`setValidationScope` does not affect the [#Event onValidate] on the form, which will always be called. The `onValidate` event on a container will only be triggered if that container is marked for partial validation.