Валидиране на формуляри

Задължителни елементи, които трябва да се попълнят

Контролите се маркират като задължителни с помощта на метода 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 стойност в диапазона para [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 или като използвате преводач.

В текста на съобщенията за грешка могат да се използват следните заместващи символи:

%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.

Препратка към друг елемент

Като аргумент за правило или условие можете да предадете и друг елемент от формуляра. Тогава правилото ще използва стойността, въведена по-късно от потребителя в браузъра. Това може да се използва например за динамично потвърждаване, че елементът 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', '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://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 ще стане видим. И обратното. Така че поставяме елементите на формата с адреса на получателя в контейнер с този идентификатор и когато щракнем върху квадратчето за отметка, те се скриват или показват. С това се занимава скриптът 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 се извиква само когато този контейнер е зададен за частично валидиране.

версия: 4.0