Валидиране на формуляри
Задължителни елементи, които трябва да се попълнят
Контролите се маркират като задължителни с помощта на метода
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()
могат да се използват и следните правила:
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 |
стойност в диапазона | para [int|float, int|float] |
Правилата Integer
, Numeric
и Float
автоматично
преобразуват стойността в цяло число (или съответно в число с плаваща
запетая). Освен това правилото URL
приема и адрес без схема (напр.
nette.org
) и допълва схемата (https://nette.org
). Изразите в
Pattern
и PatternInsensitive
трябва да са валидни за цялата стойност,
т.е. все едно са обвити в ^
и $
.
Следните правила могат да се използват и за контроли addUpload()
,
addMultiUpload()
:
MaxFileSize |
максимален размер на файла | int |
MimeType |
Тип MIME, приема заместващи символи ('video/*' ) |
string|string[] |
Image |
изтегленият файл е JPEG, PNG, GIF, WebP | – |
Pattern |
името на файла отговаря на регулярен израз | string |
PatternInsensitive |
като Pattern , но без да се отчитат малките и големите букви. |
string |
За MimeType
и Image
се изисква разширението на PHP fileinfo
.
Правилният тип на файла или изображението се определя от неговия
подпис. Целостта на целия файл не се проверява. Можете да проверите
дали дадено изображение е повредено, например като се опитате да го заредите.
За контролите addMultiUpload()
, addCheckboxList()
, addMultiSelect()
могат да се използват и следните правила, за да се ограничи броят на
избраните елементи, съответно изтеглените файлове:
MinLength |
минимален брой | int |
MaxLength |
максимален брой | int |
Length |
сума в диапазон или точна сума | параграф [int, int] или int |
Съобщения за грешки
Всички предварително дефинирани правила, с изключение на Pattern
и PatternInsensitive
, имат съобщение за грешка по подразбиране, така че
можете да ги пропуснете. Въпреки това, като предавате и формулирате
всички отделни съобщения, ще направите формуляра по-удобен за
ползване.
Можете да промените съобщенията по подразбиране в configuration, като промените текстовете в
масива 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]);
Условия
В допълнение към правилата за валидиране могат да се задават и
условия. Те се задават по същия начин като правилата, но вместо
addCondition()
използваме addRule()
и, разбира се, ги оставяме без
съобщение за грешка (условието просто пита):
$form->addPassword('password', 'Password:')
// ако паролата не е по-дълга от 8 символа ...
->addCondition($form::MaxLength, 8)
// ... тогава трябва да съдържа число
->addRule($form::Pattern, 'Must contain a number', '.*[0-9].*';
Условието може да бъде обвързано с елемент, различен от текущия, с
помощта на addConditionOn()
. Първият параметър е връзка към полето. В
следния случай имейлът ще се изисква само ако полето е маркирано (т.е.
стойността му е true
):
$form->addCheckbox('newsletters', 'send me newsletters');
$form->addEmail('email', 'Email:')
// ако квадратчето е маркирано ...
->addConditionOn($form['newsletters'], $form::Equal, true)
// ... изискване на имейл
->setRequired('Въведете вашия имейл адрес');
Условията могат да бъдат групирани в сложни структури с помощта на
методите elseCondition()
и endCondition()
.
$form->addText(/* ... */)
->addCondition(/* ... */) // ако първото условие е изпълнено
->addConditionOn(/* ... */) // и второто условие и за другия елемент
->addRule(/* ... */) // изисква това правило
->elseCondition() // ако второто условие не е изпълнено
->addRule(/* ... */) // изисква спазването на тези правила
->addRule(/* ... */)
->endCondition() // връщаме се към първото условие
->addRule(/* ... */);
В Nette е много лесно да се реагира на изпълнението или неизпълнението
на дадено условие от страна на JavaScript, като се използва методът
toggle()
, вж. раздел Динамичен JavaScript.
Връзки между контролите
Аргумент на правило или условие може да бъде препратка към друг
елемент. Например можете динамично да потвърдите, че text
има
толкова знаци, колкото са посочени в полето length
:
$form->addInteger('length');
$form->addText('text')
->addRule($form::Length, null, $form['length']);
Потребителски правила и условия
Понякога се сблъскваме със ситуация, в която вградените правила за валидиране в 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', 'postcode:')
->addFilter(function ($value) {
return str_replace(' ', '', $value); // премахване на интервалите от индекса zip
})
->addRule($form::Pattern, 'Postcode is not five digits', '\d{5}';
Филтърът е включен между правилата за валидиране и условията и
следователно зависи от реда на методите, т.е. филтърът и правилото се
извикват в същия ред като реда на методите addFilter()
и
addRule()
.
Удостоверяване на JavaScript
Езикът на правилата и условията за валидиране е мощен. Въпреки че
всички конструкции работят както от страна на сървъра, така и от страна
на клиента, в JavaScript. Правилата се предават в атрибути на HTML
data-nette-rules
като JSON. Самото валидиране се извършва от друг скрипт,
който прихваща всички събития на формуляра submit
, преминава през
всички входове и изпълнява съответните валидирания.
Този скрипт е netteForms.js
, който е достъпен от няколко възможни
източника:
Можете да вградите скрипта директно в HTML страница от CDN:
<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></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
ще стане видим. И
обратното. Така че поставяме елементите на формата с адреса на
получателя в контейнер с този идентификатор и когато щракнем върху
квадратчето за отметка, те се скриват или показват. С това се занимава
скриптът 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'
});
};
Деактивиране на валидирането
В някои случаи е необходимо да се деактивира валидирането. Ако
бутонът за изпращане не трябва да се валидира след изпращане (като
например бутон 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'
Събитието onValidate на формата се извиква винаги и
не зависи от setValidationScope
. Събитието onValidate
on container се
извиква само когато този контейнер е зададен за частично валидиране.