Валидация форм
Обязательные для заполнения элементы
Элементы управления помечаются как обязательные с помощью метода
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
автоматически
преобразуют значение в целое (или плавающее соответственно). Более
того, правило URL
также принимает адрес без схемы (например,
nette.org
) и дополняет схему (https://nette.org
). Выражения в
Pattern
и PatternInsensitive
должны быть действительны для всего
значения, т. е. как если бы оно было обернуто в символы ^
и
$
.
Количество предметов
Для элементов 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 | – |
Pattern |
имя файла соответствует регулярному выражению | string |
PatternInsensitive |
как Pattern , но без учета регистра. |
string |
Для MimeType
и Image
требуется расширение PHP fileinfo
.
Принадлежность файла или изображения к нужному типу определяется по
его сигнатуре. Целостность всего файла не проверяется. Вы можете
узнать, не повреждено ли изображение, например, попытавшись загрузить его.
Сообщения об ошибках
Все предопределенные правила, кроме Pattern
и PatternInsensitive
,
имеют сообщение об ошибке по умолчанию, поэтому их можно опустить.
Однако, передав и сформулировав все индивидуальные сообщения, вы
сделаете форму более удобной для пользователя.
Вы можете изменить сообщения по умолчанию в configuration, изменяя тексты в массиве
Nette\Forms\Validator::$messages
или используя translator.
В тексте сообщений об ошибках можно использовать следующие символы подстановки:
%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()
. Первый параметр — это ссылка на поле. В
следующем случае электронная почта потребуется только в том случае,
если флажок установлен (т. е. его значение равно true
):
$form->addCheckbox('newsletters', 'отправлять мне информационные бюллетени');
$form->addEmail('email', 'Имейл:')
// если флажок установлен ...
->addConditionOn($form['newsletters'], $form::Equal, true)
// ... требовать электронную почту
->setRequired('Введите свой адрес электронной почты');
Условия могут быть сгруппированы в сложные структуры с помощью
методов elseCondition()
и endCondition()
.
$form->addText(/* ... */)
->addCondition(/* ... */) // если выполняется первое условие
->addConditionOn(/* ... */) // и второе условие на другом элементе тоже
->addRule(/* ... */) // требуют соблюдения этого правила
->elseCondition() // если второе условие не выполняется
->addRule(/* ... */) // требуют соблюдения этих правил
->addRule(/* ... */)
->endCondition() // мы возвращаемся к первому условию
->addRule(/* ... */);
В Nette очень легко реагировать на выполнение или невыполнение условия
на стороне JavaScript, используя метод toggle()
, см. Динамический JavaScript.
Ссылка на другой элемент
В качестве аргумента для правила или условия можно также передать
другой элемент формы. Тогда правило будет использовать значение,
введенное позже пользователем в браузере. Это можно использовать,
например, для динамической проверки того, что элемент password
содержит ту же строку, что и элемент password_confirm
:
$form->addPassword('password', 'Password');
$form->addPassword('password_confirm', 'Confirm Password')
->addRule($form::Equal, 'The passwords do not match', $form['password']);
Пользовательские правила и условия
Иногда мы сталкиваемся с ситуацией, когда встроенных правил валидации в Nette недостаточно, и нам нужно проверить данные от пользователя по-своему. В Nette это очень просто!
Вы можете передать любой обратный вызов в качестве первого параметра
в методы addRule()
или addCondition()
. Обратный вызов принимает сам
элемент в качестве первого параметра и возвращает булево значение,
указывающее на успешность проверки. При добавлении правила с помощью
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');
Этот код говорит, что при выполнении условия, то есть при установке
флажка, HTML-элемент #address-container
станет видимым. И наоборот. Итак, мы
помещаем элементы формы с адресом получателя в контейнер с этим ID, и
при нажатии на флажок они скрываются или показываются. Этим занимается
скрипт netteForms.js
.
Любой селектор может быть передан в качестве аргумента методу
toggle()
. По историческим причинам буквенно-цифровая строка без
других специальных символов рассматривается как идентификатор
элемента, так же как если бы ей предшествовал символ #
. Второй
необязательный параметр позволяет нам изменить поведение, т. е. если бы
мы использовали toggle('#address-container', false)
, элемент отображался бы
только при снятом флажке.
Реализация JavaScript по умолчанию изменяет свойство hidden
для
элементов. Однако мы можем легко изменить поведение, например, добавив
анимацию. Просто переопределите метод Nette.toggle
в JavaScript с помощью
собственного решения:
Nette.toggle = (selector, visible, srcElement, event) => {
document.querySelectorAll(selector).forEach((el) => {
// скрыть или показать 'el' в зависимости от значения 'visible'
});
};
Отключение валидации
В некоторых случаях необходимо отключить валидацию. Если кнопка submit
не должна выполнять проверку после отправки (например, кнопка
Отмена или Предварительный просмотр), вы можете отключить
проверку, вызвав $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']]); // Проверяет только поле 'имя'
$form->addSubmit('send4')
->setValidationScope([$form['details']['age']]); // Проверяется только поле 'возраст'
$form->addSubmit('send5')
->setValidationScope([$form['details']]); // Проверяет контейнер 'details'
Событие onValidate на форме вызывается всегда и не
зависит от setValidationScope
. Событие onValidate
на контейнере
вызывается только тогда, когда этот контейнер указан для частичной
валидации.