Validace formulářů

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 úplně 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ějakému prvku v poli array
IS_NOT_IN hodnota se nerovná žádnému prvku 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 pár [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 pár [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.

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

MIN_LENGTH minimální počet souborů int
MAX_LENGTH maximální počet souborů int
LENGTH počet souborů v rozsahu nebo přesný počet pár [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 je lze u metod addRule() a setRequired() 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');

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 bude muset 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 provádí v rámci validačních pravidel, takže záleží na pořadí jednotlivých metod.

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

Validace celého formuláře

Pokud z nějakého důvodu potřebujete přidat validační funkcionalitu, typicky ověření správné kombinace hodnot ve více prvcích formuláře, můžete si napsat vlastní validační funkce. Ty pak na formulář navážete pomocí události onValidate:

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

public function validateSignInForm($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 funkcí. Funkce je chápána jako úspěšná, pokud nepřidá do formuláře chybu pomocí $form->addError().

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(false); // Nevaliduje vůbec
$form->addSubmit('send3')->setValidationScope([$form['name']]); // Validuje pouze pole name
$form->addSubmit('send4')->setValidationScope([$form['details']['age']]); // Validuje pouze pole 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é.");

Související články na blogu

Vylepšit tuto stránku