Nette Documentation Preview

Validace formulářů

Povinné prvky

Povinné prvky označíme metodou setRequired(), jejíž argument je text chybové hlášky, která se zobrazí, pokud uživatel prvek nevyplní. Pokud argument neuvedeme, použije se výchozí chybová hláška.

$form->addText('name', 'Jméno:')
	->setRequired('Zadejte prosím jméno');

Pravidla

Validační pravidla přidáváme prvkům metodou addRule(). První parametr je pravidlo, druhý je text chybové hlášky a třetí je argument validačního pravidla.

$form->addPassword('password', 'Heslo:')
	->addRule($form::MIN_LENGTH, 'Heslo musí mít alespoň %d znaků', 8)

Nette přichází s celou řadu předdefinovaných pravidel, jejichž názvy jsou konstanty třídy Nette\Forms\Form.

U všech prvků můžeme použít tyto pravidla:

konstanta popis typ argumentu
REQUIRED povinný prvek, alias pro setRequired()
FILLED povinný prvek, alias pro setRequired()
BLANK prvek nesmí být vyplněn
EQUAL hodnota se rovná parametru mixed
NOT_EQUAL hodnota se nerovná parametru mixed
IS_IN hodnota se rovná některé položce v poli array
IS_NOT_IN hodnota se nerovná žádné položce v poli array
VALID je prvek vyplněn správně? (pro podmínky)

U prvků addText(), addPassword(), addTextArea(), addEmail(), addInteger() lze použít i následující pravidla:

MIN_LENGTH minimální délka textu int
MAX_LENGTH maximální délka textu int
LENGTH délka v rozsahu nebo přesná délka dvojice [int, int] nebo int
EMAIL platná e-mailová adresa
URL absolutní URL
PATTERN vyhovuje regulárnímu výrazu string
PATTERN_ICASE jako PATTERN, ale nezávislé na velikosti písmen string
INTEGER celočíselná hodnota
NUMERIC alias pro INTEGER
FLOAT číslo
MIN minimální hodnota číselného prvku int|float
MAX maximální hodnota číselného prvku int|float
RANGE hodnota v rozsahu dvojice [int|float, int|float]

Validační pravidla INTEGER, NUMERIC a FLOAT rovnou převádí hodnotu na integer resp. float. A dále pravidlo URL akceptuje i adresu bez schématu (např. nette.org) a schéma doplní (http://nette.org). Výraz v PATTERN a PATTERN_ICASE musí platit pro celou hodnotu, tj. jako by byl obalen znaky ^ a $.

U prvků addUpload(), addMultiUpload() lze použít i následující pravidla:

MAX_FILE_SIZE maximální velikost souboru int
MIME_TYPE MIME type, povoleny zástupné znaky ('video/*') string|string[]
IMAGE obrázek JPEG, PNG, GIF, WebP
PATTERN jméno souboru vyhovuje regulárnímu výrazu string
PATTERN_ICASE jako PATTERN, ale nezávislé na velikosti písmen string

MIME_TYPE a IMAGE vyžadují PHP rozšíření fileinfo.

U prvků addMultiUpload(), addCheckboxList(), addMultiSelect() lze použít i následující pravidla pro omezení počtu vybraných položek resp. uploadovaných souborů:

MIN_LENGTH minimální počet int
MAX_LENGTH maximální počet int
LENGTH počet v rozsahu nebo přesný počet dvojice [int, int] nebo int

Chybové hlášky

Všechny předdefinované pravidla s výjimkou PATTERN a PATTERN_ICASE mají výchozí chybovou hlášku, takže ji lze vynechat. Nicméně uvedením a formulací všech hlášek na míru uděláte formulář uživatelsky přívětivější.

Změnit výchozí hlášky můžete v konfiguraci, úpravou textů v poli Nette\Forms\Validator::$messages nebo použitím translatoru.

V textu chybových hlášek lze používat tyto zástupné řetězce:

%d nahradí postupně za argumenty pravidla
%n$d nahradí za n-tý argument pravidla
%label nahradí za popisku prvku (bez dvojtečky)
%name nahradí za jméno prvku (např. name)
%value nahradí za uživatelem vloženou hodnotu
$form->addText('name', 'Jméno:')
	->setRequired('Vyplňte prosím %label');

$form->addInteger('id', 'ID:')
	->addRule($form::RANGE, 'nejméně %d a nejvíce %d', [5, 10]);

$form->addInteger('id', 'ID:')
	->addRule($form::RANGE, 'nejvíce %2$d a nejméně %1$d', [5, 10]);

Podmínky

Kromě pravidel lze přidávat také podmínky. Ty se zapisují podobně jako pravidla, jen místo addRule() použijeme metodu addCondition() a pochopitelně neuvádíme žádnou chybovou zprávu (podmínka se jen ptá):

$form->addPassword('password', 'Heslo:')
	// pokud není heslo delší než 8 znaků
	->addCondition($form::MAX_LENGTH, 8)
		// pak musí obsahovat číslici
		->addRule($form::PATTERN, 'Musí obsahovat číslici', '.*[0-9].*');

Podmínku je možné vázat i na jiný prvek než aktuální pomocí addConditionOn(). Jako první parametr uvedeme referenci na prvek. V této ukázce bude e-mail povinný jen tehdy, zaškrtne-li se checkbox (jeho hodnota bude true):

$form->addCheckbox('newsletters', 'zasílejte mi newslettery');

$form->addEmail('email', 'E-mail:')
	// pokud je checkbox zaškrtnut
	->addConditionOn($form['newsletters'], $form::EQUAL, true)
		// pak vyžaduj e-mail
		->setRequired('Zadejte e-mailovou adresu');

Z podmínek lze vytvářet komplexní struktury za pomoci elseCondition() a endCondition():

$form->addText(...)
	->addCondition(...) // je-li splněna první podmínka
		->addConditionOn(...) // a druhá podmínka na jiném prvku
			->addRule(...) // vyžaduj toto pravidlo
		->elseCondition() // pokud druhá podmínka splněná není
			->addRule(...) // vyžaduj tato pravidla
			->addRule(...)
		->endCondition() // vracíme se k první podmínce
		->addRule(...)

Reference mezi prvky

Jako argument pravidla či podmínky lze uvádět referenci na jiný prvek. Takto lze např. dynamicky validovat, že prvek text má tolik znaků, jako je hodnota prvku length:

$form->addInteger('length');
$form->addText('text')
	->addRule($form::LENGTH, null, $form['length']);

Vlastní pravidla a podmínky

Můžeme vytvářet vlastní pravidla nebo podmínky. Jako pravidlo předáme libovolný PHP callback.

class MyValidators
{
	// testuje, zda je hodnota dělitelná argumentem
	public static function divisibilityValidator(BaseControl $input, $arg): bool
	{
		return $input->getValue() % $arg === 0;
	}
}

$input->addRule(
	'MyValidator::divisibilityValidator',
	'Hodnota musí být násobkem čísla %d',
	8
);

Úprava vstupu

Pomocí metody addFilter() můžeme pozměnit uživatelem vloženou hodnotu. V této ukázce budeme tolerovat a odstraňovat mezery v PSČ:

$form->addText('zip', 'PSČ:')
	->addFilter(function ($value) {
		return str_replace(' ', '', $value); // odstraníme mezery z PSČ
	})
	->addRule($form::PATTERN, 'PSČ není ve tvaru pěti číslic', '\d{5}');

Filtr se začlení mezi validační pravidla a podmínky a tedy záleží na pořadí metod, tj. filtr a pravidlo se volají v takovém pořadí, jako je pořadí metod addFilter() a addRule().

JavaScript

Jazyk pro formulování podmínek a pravidel je velice silný. Všechny konstrukce přitom fungují jak na straně serveru, tak i na straně JavaScriptu. Přenáší se v HTML atributech data-nette-rules jako JSON. Samotnou validaci pak provádí skript, který odchytí událost formuláře submit, projde jednotlivé prvky a vykoná příslušnou validaci.

Tím skriptem je netteForms.js a je dostupný z více možných zdrojů:

Skript můžete vložit přímo do HTML stránky ze CDN:

<script src="https://nette.github.io/resources/js/3/netteForms.min.js"></script>

Nebo zkopírovat lokálně do veřejné složky projektu (např. z vendor/nette/forms/src/assets/netteForms.min.js):

<script src="/path/to/netteForms.min.js"></script>

Nebo nainstalovat přes npm:

npm install nette-forms

A následně načíst a spustit:

import netteForms from 'nette-forms';
netteForms.initOnLoad();

Alternativně jej můžete načíst přímo ze složky vendor:

import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js';
netteForms.initOnLoad();

Vlastní pravidla v JS

Vlastní validační pravidla lze přidávat i do JavaScriptu. Podmínkou je, aby callback vlastního pravidla na straně PHP byla globální funkce nebo statická metoda. Potom její JavaScriptovou obdobu přidáme do objektu Nette.validators:

<script>
Nette.validators.divisibilityValidator = function(elem, args, val) {
	return val % args === 0;
};
</script>

Pokud je validační callback v PHP statická metoda, tak název pro JavaScriptový validátor vznikne odstraněním zpětných lomítek \ a nahrazením čtyřtečky za podtržítko _, např. App\MyValidator::divisibilityValidator zapíšeme jako AppMyValidator_divisibilityValidator.

Vypnutí validace

Někdy se může hodit validaci vypnout. Pokud stisknutí odesílacího tlačítka nemá provádět validaci (vhodné pro tlačítka Cancel nebo Preview), vypneme ji metodou $submit->setValidationScope([]). Pokud má provádět validaci jen částečnou, můžeme určit které pole nebo formulářové kontejnery se mají validovat.

$form->addText('name')
	->setRequired();

$details = $form->addContainer('details');
$details->addInteger('age')
	->setRequired('age');
$details->addInteger('age2')
	->setRequired('age2');

$form->addSubmit('send1'); // Validuje celý formuláře
$form->addSubmit('send2')
	->setValidationScope([]); // Nevaliduje vůbec
$form->addSubmit('send3')
	->setValidationScope([$form['name']]); // Validuje pouze prvek name
$form->addSubmit('send4')
	->setValidationScope([$form['details']['age']]); // Validuje pouze prvek age
$form->addSubmit('send5')
	->setValidationScope([$form['details']]); // Validuje kontejner details

setValidationScope neovlivní událost onValidate u formuláře, která bude zavolána vždy. Událost onValidate u kontejneru bude vyvolána pouze pokud je tento kontejner označen pro částečnou validaci.

Chyby při zpracování

V mnoha případech se o chybě dozvíme až ve chvíli, kdy zpracováváme platný formulář, například zapisujeme novou položku do databáze a narazíme na duplicitu klíčů. V takovém případě chybu zpětně předáme do formuláře metodou addError(). Tu lze volat buď na konkrétním prvku, nebo přímo na formuláři:

try {
	$values = $form->getValues();
	$this->user->login($values->username, $values->password);
	$this->redirect('Homepage:');

} catch (Nette\Security\AuthenticationException $e) {
	if ($e->getCode() === Nette\Security\IAuthenticator::INVALID_CREDENTIAL) {
		$form->addError('Neplatné heslo.');
	}
	...
}

Doporučuje se připojit chybu přímo k prvku formuláře, protože se zobrazí vedle něj při použití výchozího rendereru.

$form['date']->addError('Omlouváme se, ale toto datum již je zabrané.');

Můžete addError() volat opakovaně a tak předat formuláři nebo prvku více chybových zpráv. Získáte je pomocí getErrors().

Pozor, $form->getErrors() vrací sumář všech chybových zpráv, i těch, co byly předány přímo jednotlivým prvkům, nejen přímo formuláři. Chybové zprávy předané pouze formuláři získáte přes $form->getOwnErrors().

Událost onValidate

Při validování se vyvolá událost onValidate, jejíž handler lze využít k doplňkové validaci, typicky ověření správné kombinace hodnot ve více prvcích formuláře:

protected function createComponentSignInForm(): Form
{
	$form = new Form;
	...
	$form->onValidate[] = [$this, 'validateSignInForm'];
	return $form;
}

public function validateSignInForm(Form $form): void
{
	$values = $form->getValues();

	if (...) { // validační podmínka
		$form->addError('Tato kombinace není možná.');
	}
}

Na událost onValidate můžete registrovat libovolný počet handlerů.