Валідація форм
Обов'язкові елементи
Обов'язкові елементи позначаємо методом setRequired()
, аргументом
якого є текст Повідомлення про помилки, який
відобразиться, якщо користувач не заповнить елемент. Якщо аргумент не
вказано, використовується стандартне повідомлення про помилку.
$form->addText('name', 'Ім\'я:')
->setRequired('Будь ласка, введіть ім\'я');
Правила
Правила валідації додаємо до елементів методом addRule()
. Перший
параметр — це правило, другий — текст Повідомлення
про помилки, а третій — аргумент правила валідації.
$form->addPassword('password', 'Пароль:')
->addRule($form::MinLength, 'Пароль повинен мати щонайменше %d символів', 8);
Правила валідації перевіряються лише в тому випадку, якщо користувач заповнив елемент.
Nette постачається з цілою низкою передбачених правил, назви яких є
константами класу Nette\Forms\Form
. Для всіх елементів ми можемо
використовувати ці правила:
константа | опис | тип аргументу |
---|---|---|
Required |
обов'язковий елемент, псевдонім для setRequired() |
– |
Filled |
обов'язковий елемент, псевдонім для setRequired() |
– |
Blank |
елемент не повинен бути заповнений | – |
Equal |
значення дорівнює параметру | mixed |
NotEqual |
значення не дорівнює параметру | mixed |
IsIn |
значення дорівнює одному з елементів у масиві | array |
IsNotIn |
значення не дорівнює жодному з елементів у масиві | array |
Valid |
чи елемент заповнений правильно? (для Умови) | – |
Текстові поля
Для елементів addText()
, addPassword()
, addTextArea()
,
addEmail()
, addInteger()
, addFloat()
можна також використовувати
деякі з наступних правил:
MinLength |
мінімальна довжина тексту | int |
MaxLength |
максимальна довжина тексту | int |
Length |
довжина в діапазоні або точна довжина | пара [int, int] або int |
Email |
дійсна електронна адреса | – |
URL |
абсолютний URL | – |
Pattern |
відповідає регулярному виразу | string |
PatternInsensitive |
як Pattern , але нечутливий до регістру |
string |
Integer |
цілочисельне значення | – |
Numeric |
псевдонім для Integer |
– |
Float |
число | – |
Min |
мінімальне значення числового елемента | int|float |
Max |
максимальне значення числового елемента | int|float |
Range |
значення в діапазоні | пара [int|float, int|float] |
Правила валідації Integer
, Numeric
та Float
одразу
перетворюють значення на integer відповідно float. Крім того, правило
URL
приймає також адресу без схеми (наприклад, nette.org
) і
доповнює схему (https://nette.org
). Вираз у Pattern
та PatternIcase
повинен відповідати всьому значенню, тобто ніби він був обгорнутий
символами ^
та $
.
Кількість елементів
Для елементів addMultiUpload()
, addCheckboxList()
, addMultiSelect()
можна також використовувати наступні правила для обмеження кількості
вибраних елементів або завантажених файлів:
MinLength |
мінімальна кількість | int |
MaxLength |
максимальна кількість | int |
Length |
кількість у діапазоні або точна кількість | пара [int, int] або int |
Завантаження файлів
Для елементів addUpload()
, addMultiUpload()
можна також
використовувати наступні правила:
MaxFileSize |
максимальний розмір файлу в байтах | int |
MimeType |
MIME-тип, дозволені плейсхолдери ('video/*' ) |
string|string[] |
Image |
зображення JPEG, PNG, GIF, WebP, AVIF | – |
Pattern |
ім'я файлу відповідає регулярному виразу | string |
PatternInsensitive |
як Pattern , але нечутливий до регістру |
string |
MimeType
та Image
вимагають PHP-розширення fileinfo
. Те, що
файл чи зображення є потрібного типу, визначається на основі його
сигнатури, і не перевіряється цілісність усього файлу. Чи не
пошкоджене зображення, можна з'ясувати, наприклад, спробувавши його завантажити.
Повідомлення про помилки
Усі передбачені правила, за винятком Pattern
та PatternInsensitive
,
мають стандартне повідомлення про помилку, тому його можна пропустити.
Однак, вказавши та сформулювавши всі повідомлення індивідуально, ви
зробите форму більш зручною для користувача.
Змінити стандартні повідомлення можна в конфігурації, змінивши тексти в масиві
Nette\Forms\Validator::$messages
або використовуючи перекладач.
У тексті повідомлень про помилки можна використовувати ці рядки-заповнювачі:
%d |
замінюється послідовно на аргументи правила |
%n$d |
замінюється на n-й аргумент правила |
%label |
замінюється на мітку елемента (без двокрапки) |
%name |
замінюється на ім'я елемента (наприклад, name ) |
%value |
замінюється на значення, введене користувачем |
$form->addText('name', 'Ім\'я:')
->setRequired('Заповніть, будь ласка, %label');
$form->addInteger('id', 'ID:')
->addRule($form::Range, 'щонайменше %d і щонайбільше %d', [5, 10]);
$form->addInteger('id', 'ID:')
->addRule($form::Range, 'щонайбільше %2$d і щонайменше %1$d', [5, 10]);
Умови
Крім правил, можна додавати також умови. Вони записуються подібно до
правил, тільки замість addRule()
використовуємо метод
addCondition()
і, зрозуміло, не вказуємо жодного повідомлення про
помилку (умова лише запитує):
$form->addPassword('password', 'Пароль:')
// якщо пароль не довший за 8 символів
->addCondition($form::MaxLength, 8)
// тоді він повинен містити цифру
->addRule($form::Pattern, 'Повинен містити цифру', '.*[0-9].*');
Умову можна прив'язати і до іншого елемента, ніж поточний, за
допомогою addConditionOn()
. Як перший параметр вкажемо посилання на
елемент. У цьому прикладі e-mail буде обов'язковим лише тоді, коли буде
відмічено checkbox (його значення буде true):
$form->addCheckbox('newsletters', 'надсилайте мені розсилки');
$form->addEmail('email', 'E-mail:')
// якщо checkbox відмічено
->addConditionOn($form['newsletters'], $form::Equal, true)
// тоді вимагай e-mail
->setRequired('Введіть адресу електронної пошти');
З умов можна створювати складні структури за допомогою
elseCondition()
та endCondition()
:
$form->addText(/* ... */)
->addCondition(/* ... */) // якщо виконана перша умова
->addConditionOn(/* ... */) // і друга умова на іншому елементі
->addRule(/* ... */) // вимагай це правило
->elseCondition() // якщо друга умова не виконана
->addRule(/* ... */) // вимагай ці правила
->addRule(/* ... */)
->endCondition() // повертаємося до першої умови
->addRule(/* ... */);
У Nette можна дуже легко реагувати на виконання чи невиконання умови
також на стороні JavaScript за допомогою методу toggle()
, див. Динамічний JavaScript.
Посилання на інший елемент
Як аргумент правила чи умови можна передати й інший елемент форми.
Правило тоді використає значення, введене пізніше користувачем у
браузері. Таким чином можна, наприклад, динамічно валідувати, що
елемент password
містить той самий рядок, що й елемент
password_confirm
:
$form->addPassword('password', 'Пароль');
$form->addPassword('password_confirm', 'Підтвердіть пароль')
->addRule($form::Equal, 'Введені паролі не співпадають', $form['password']);
Власні правила та умови
Іноді ми потрапляємо в ситуацію, коли вбудованих правил валідації в Nette недостатньо, і нам потрібно валідувати дані від користувача по-своєму. У Nette це дуже просто!
Методам addRule()
чи addCondition()
можна як перший параметр
передати будь-який callback. Він приймає як перший параметр сам елемент і
повертає булеве значення, що визначає, чи валідація пройшла успішно.
При додаванні правила за допомогою addRule()
можна вказати й інші
аргументи, які потім передаються як другий параметр.
Власний набір валідаторів ми можемо створити як клас зі статичними методами:
class MyValidators
{
// перевіряє, чи значення ділиться на аргумент
public static function validateDivisibility(BaseControl $input, $arg): bool
{
return $input->getValue() % $arg === 0;
}
public static function validateEmailDomain(BaseControl $input, $domain)
{
// інші валідатори
}
}
Використання тоді дуже просте:
$form->addInteger('num')
->addRule(
[MyValidators::class, 'validateDivisibility'],
'Значення має бути кратним числу %d',
8,
);
Власні правила валідації можна додавати і до JavaScript. Умовою є те, що
правило має бути статичним методом. Його назва для JavaScript-валідатора
утворюється шляхом об'єднання назви класу без зворотних слешів
\
, підкреслення _
та назви методу. Наприклад,
App\MyValidators::validateDivisibility
запишемо як AppMyValidators_validateDivisibility
і
додамо до об'єкта Nette.validators
:
Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => {
return val % args === 0;
};
Подія onValidate
Після надсилання форми проводиться валідація, під час якої
перевіряються окремі правила, додані за допомогою addRule()
, а потім
викликається подія onValidate
. Її
обробник можна використовувати для додаткової валідації, зазвичай для
перевірки правильної комбінації значень у кількох елементах форми.
Якщо виявлено помилку, передаємо її до форми методом addError()
.
Його можна викликати або на конкретному елементі, або безпосередньо
на формі.
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('Ця комбінація неможлива.');
}
}
Помилки під час обробки
У багатьох випадках про помилку ми дізнаємося лише тоді, коли
обробляємо валідну форму, наприклад, записуємо новий елемент у базу
даних і натрапляємо на дублювання ключів. У такому випадку помилку
знову передаємо до форми методом addError()
. Його можна викликати
або на конкретному елементі, або безпосередньо на формі:
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('Неправильний пароль.');
}
}
Якщо можливо, рекомендуємо прикріпити помилку безпосередньо до елемента форми, оскільки вона відобразиться поруч із ним при використанні стандартного візуалізатора.
$form['date']->addError('Вибачте, але ця дата вже зайнята.');
Ви можете викликати addError()
повторно і таким чином передати
формі або елементу кілька повідомлень про помилки. Отримати їх можна
за допомогою getErrors()
.
Увага, $form->getErrors()
повертає зведення всіх повідомлень про
помилки, включаючи ті, що були передані безпосередньо окремим
елементам, а не лише безпосередньо формі. Повідомлення про помилки,
передані лише формі, можна отримати через $form->getOwnErrors()
.
Зміна вводу
За допомогою методу addFilter()
ми можемо змінити значення, введене
користувачем. У цьому прикладі ми будемо толерувати та видаляти
пробіли в поштовому індексі:
$form->addText('zip', 'Поштовий індекс:')
->addFilter(function ($value) {
return str_replace(' ', '', $value); // видалимо пробіли з поштового індексу
})
->addRule($form::Pattern, 'Поштовий індекс не у форматі п\'яти цифр', '\d{5}');
Фільтр включається між правилами валідації та умовами, тому порядок
методів має значення, тобто фільтр і правило викликаються в тому
порядку, в якому вказані методи addFilter()
та addRule()
.
JavaScript валідація
Мова для формулювання умов і правил дуже потужна. Усі конструкції при
цьому працюють як на стороні сервера, так і на стороні JavaScript. Вони
передаються в HTML-атрибутах data-nette-rules
як JSON. Саму валідацію потім
виконує скрипт, який перехоплює подію форми submit
, проходить по
окремих елементах і виконує відповідну валідацію.
Цим скриптом є netteForms.js
, і він доступний з кількох можливих
джерел:
Скрипт можна вставити безпосередньо в HTML-сторінку з CDN:
<script src="https://unpkg.com/nette-forms@3"></script>
Або скопіювати локально в публічний каталог проекту (наприклад, з
vendor/nette/forms/src/assets/netteForms.min.js
):
<script src="/path/to/netteForms.min.js"></script>
Або встановити через npm:
npm install nette-forms
А потім завантажити та запустити:
import netteForms from 'nette-forms';
netteForms.initOnLoad();
Альтернативно, його можна завантажити безпосередньо з каталогу
vendor
:
import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js';
netteForms.initOnLoad();
Динамічний JavaScript
Хочете відображати поля для введення адреси лише якщо користувач
вибере доставку товару поштою? Без проблем. Ключем є пара методів
addCondition()
& toggle()
:
$form->addCheckbox('send_it')
->addCondition($form::Equal, true)
->toggle('#address-container');
Цей код говорить, що коли умова виконана, тобто коли відмічено checkbox,
буде видимим HTML-елемент #address-container
. І навпаки. Елементи форми з
адресою одержувача ми розмістимо в контейнері з цим ID, і при кліку на
checkbox вони будуть приховані або показані. Це забезпечує скрипт
netteForms.js
.
Як аргумент методу toggle()
можна передати будь-який селектор. З
історичних причин буквено-цифровий рядок без інших спеціальних
символів розуміється як ID елемента, тобто так само, якби йому передував
символ #
. Другий необов'язковий параметр дозволяє інвертувати
поведінку, тобто якби ми використали toggle('#address-container', false)
,
елемент би, навпаки, відображався лише тоді, коли checkbox не був би
відмічений.
Стандартна реалізація в JavaScript змінює властивість hidden
елементів. Однак поведінку можна легко змінити, наприклад, додати
анімацію. Достатньо в JavaScript перезаписати метод Nette.toggle
власним
рішенням:
Nette.toggle = (selector, visible, srcElement, event) => {
document.querySelectorAll(selector).forEach((el) => {
// приховаємо або покажемо 'el' залежно від значення 'visible'
});
};
Вимкнення валідації
Іноді може знадобитися вимкнути валідацію. Якщо натискання кнопки
відправки не повинно виконувати валідацію (підходить для кнопок
Cancel або Preview), вимкнемо її методом $submit->setValidationScope([])
.
Якщо вона повинна виконувати лише часткову валідацію, ми можемо
вказати, які поля або контейнери форми мають валідуватися.
$form->addText('name')
->setRequired();
$details = $form->addContainer('details');
$details->addInteger('age')
->setRequired('age');
$details->addInteger('age2')
->setRequired('age2');
$form->addSubmit('send1'); // Валідує всю форму
$form->addSubmit('send2')
->setValidationScope([]); // Не валідує взагалі
$form->addSubmit('send3')
->setValidationScope([$form['name']]); // Валідує лише елемент name
$form->addSubmit('send4')
->setValidationScope([$form['details']['age']]); // Валідує лише елемент age
$form->addSubmit('send5')
->setValidationScope([$form['details']]); // Валідує контейнер details
setValidationScope
не впливає на подія onValidate у
формі, яка буде викликана завжди. Подія onValidate
у контейнері буде
викликана лише якщо цей контейнер позначений для часткової
валідації.