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);
Validační pravidla se ověřují pouze v případě, že uživatel prvek vyplnil.
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) | – |
Textové vstupy
U prvků addText()
, addPassword()
, addTextArea()
, addEmail()
,
addInteger()
, addFloat()
lze použít i některá 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í
(https://nette.org
). Výraz v PATTERN
a PATTERN_ICASE
musí platit pro celou hodnotu, tj.
jako by byl obalen znaky ^
a $
.
Počet položek
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 |
Upload souborů
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
. Ž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í.
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(/* ... */);
V Nette lze velmi snadno reagovat na splnění či nesplnění podmínky i na straně JavaScriptu pomocí metody
toggle()
, viz dynamický JavaScript.
Reference na jiný prvek
Jako argument pravidla či podmínky lze předat i jiný prvek formuláře. Pravidlo potom použije hodnotu vloženou později
uživatelem v prohlížeči. Takto lze např. dynamicky validovat, že prvek password
obsahuje stejný řetězec
jako prvek password_confirm
:
$form->addPassword('password', 'Heslo');
$form->addPassword('password_confirm', 'Potvrďte heslo')
->addRule($form::EQUAL, 'Zadaná hesla se neshodují', $form['password']);
Vlastní pravidla a podmínky
Občas se dostaneme do situace, kdy nám vestavěná validační pravidla v Nette nestačí a potřebujeme data od uživatele validovat po svém. V Nette je to velmi jednoduché!
Metodám addRule()
či addCondition()
lze jako první parametr předat libovolný callback. Ten
přijímá jako první parametr samotný prvek a vrací boolean hodnotu určující, zda validace proběhla v pořádku. Při
přidávání pravidla pomocí addRule()
je možné zadat i další argumenty, ty jsou pak předány jako druhý
parametr.
Vlastní sadu validátorů tak můžeme vytvořit jako třídu se statickými metodami:
class MyValidators
{
// testuje, zda je hodnota dělitelná argumentem
public static function validateDivisibility(BaseControl $input, $arg): bool
{
return $input->getValue() % $arg === 0;
}
public static function validateEmailDomain(BaseControl $input, $domain)
{
// další validátory
}
}
Použití je pak velmi jednoduché:
$form->addInteger('num')
->addRule(
[MyValidators::class, 'validateDivisibility'],
'Hodnota musí být násobkem čísla %d',
8
);
Vlastní validační pravidla lze přidávat i do JavaScriptu. Podmínkou je, aby pravidlo byla statická metoda. Její název
pro JavaScriptový validátor vznikne spojením názvu třídy bez zpětných lomítek \
, podtržítka _
a názvu metody. Např. App\MyValidators::validateDivisibility
zapíšeme jako
AppMyValidators_validateDivisibility
a přidáme do objektu Nette.validators
:
Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => {
return val % args === 0;
};
Událost onValidate
Po odeslání formuláře se provádí validace, kdy se zkontrolují jednotlivá pravidla přidaná pomocí
addRule()
a následně 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.
Pokud se odhalí chyba, předáme ji do formuláře metodou addError()
. Tu lze volat buď na konkrétním prvku,
nebo přímo na formuláři.
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('Tato kombinace není možná.');
}
}
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 opět předáme do formuláře metodou
addError()
. Tu lze volat buď na konkrétním prvku, nebo přímo na formuláři:
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::INVALID_CREDENTIAL) {
$form->addError('Neplatné heslo.');
}
}
Pokud je to možné, doporučujeme 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()
.
Ú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()
.
JavaScriptová validace
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://unpkg.com/nette-forms@3"></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();
Dynamický JavaScript
Chcete zobrazit políčka pro zadání adresy pouze pokud uživatel zvolí zboží zaslat poštou? Žádný problém. Klíčem
je dvojice metod addCondition()
& toggle()
:
$form->addCheckbox('send_it')
->addCondition($form::EQUAL, true)
->toggle('#address-container');
Tento kód říká, že když je podmínka splněná, tedy když je zaškrtnutý checkbox, bude viditelný HTML element
#address-container
. A obráceně. Prvky formuláře s adresou příjemce tak umístíme do kontejneru s tímto ID
a při kliknutí na checkbox se skryjí nebo zobrazí. To zajišťuje skript netteForms.js
.
Jako argument metody toggle()
je možné předat libovolný selektor. Z historických důvodů se alfanumerický
řetězec bez dalších speciálních znaků chápe jako ID prvku, tedy stejně, jako by mu předcházel znak #
.
Druhý nepovinný parametr umožňuje obrátit chování, tj. pokud bychom použili
toggle('#address-container', false)
, element by se naopak zobrazil pouze tehdy, pokud by checkbox
zaškrtnutý nebyl.
Výchozí implementace v JavaScriptu mění elementům property hidden
. Chování však můžeme snadno změnit,
například přidat animaci. Stačí v JavaScriptu přepsat metodu Nette.toggle
vlastním řešením:
Nette.toggle = (selector, visible, srcElement, event) => {
document.querySelectorAll(selector).forEach((el) => {
// skryjeme nebo zobrazime 'el' dle hodnoty 'visible'
});
};
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.