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; od nette/forms v2.4.9 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; od nette/forms v2.4.9 string
PATTERN_ICASE jako PATTERN, ale nezávislé na velikosti písmen; od nette/forms v2.4.9 string

MIME_TYPE a IMAGE vyžadují PHP rozšíření fileinfo. Že je soubor či obrázek požadovaného typu detekují na základě jeho signatury a neověřují integritu celého souboru. Zda není obrázek poškozený lze zjistit například pokusem o jeho načtení.

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

Pravidla je možné negovat znakem ~ (vlnovka), tj. addRule(~$form::NUMBER, ...), což je v Nette 3 zavržené, takže je potřeba použít jiné odpovídající pravidlo (např. opakem FILLED je BLANK).

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(...)

Podmínky je možné negovat znakem ~ (vlnovka), tj. addCondition(~$form::NUMBER, ...), což je v Nette 3 zavržené, takže je potřeba použít else-podmínku.

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)
	{
		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/2/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 = new Form;
	...
	$form->onValidate[] = [$this, 'validateSignInForm'];
	return $form;
}

public function validateSignInForm(Form $form)
{
	$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ů.


Související články na blogu

Vylepšit tuto stránku