Walidacja formularzy
Elementy obowiązkowe
Elementy obowiązkowe oznaczamy metodą setRequired()
, której argumentem jest tekst komunikatu o błędzie, który wyświetli się, jeśli użytkownik nie wypełni elementu. Jeśli
nie podamy argumentu, użyty zostanie domyślny komunikat o błędzie.
$form->addText('name', 'Imię:')
->setRequired('Proszę podać imię');
Reguły
Reguły walidacji dodajemy do elementów metodą addRule()
. Pierwszy parametr to reguła, drugi to tekst komunikatu o błędzie, a trzeci to argument reguły walidacji.
$form->addPassword('password', 'Hasło:')
->addRule($form::MinLength, 'Hasło musi mieć co najmniej %d znaków', 8);
Reguły walidacji są sprawdzane tylko wtedy, gdy użytkownik wypełnił element.
Nette dostarcza całą gamę predefiniowanych reguł, których nazwy są stałymi klasy Nette\Forms\Form
. Dla
wszystkich elementów możemy użyć tych reguł:
stała | opis | typ argumentu |
---|---|---|
Required |
element obowiązkowy, alias dla setRequired() |
– |
Filled |
element obowiązkowy, alias dla setRequired() |
– |
Blank |
element nie może być wypełniony | – |
Equal |
wartość jest równa parametrowi | mixed |
NotEqual |
wartość nie jest równa parametrowi | mixed |
IsIn |
wartość jest równa jednemu z elementów w tablicy | array |
IsNotIn |
wartość nie jest równa żadnemu z elementów w tablicy | array |
Valid |
czy element jest wypełniony poprawnie? (dla Podmínky) | – |
Wejścia tekstowe
Dla elementów addText()
, addPassword()
, addTextArea()
, addEmail()
,
addInteger()
, addFloat()
można użyć również niektórych z następujących reguł:
MinLength |
minimalna długość tekstu | int |
MaxLength |
maksymalna długość tekstu | int |
Length |
długość w zakresie lub dokładna długość | para [int, int] lub int |
Email |
prawidłowy adres e-mail | – |
URL |
absolutny URL | – |
Pattern |
pasuje do wyrażenia regularnego | string |
PatternInsensitive |
jak Pattern , ale niezależne od wielkości liter |
string |
Integer |
wartość całkowita | – |
Numeric |
alias dla Integer |
– |
Float |
liczba | – |
Min |
minimalna wartość elementu numerycznego | int|float |
Max |
maksymalna wartość elementu numerycznego | int|float |
Range |
wartość w zakresie | para [int|float, int|float] |
Reguły walidacji Integer
, Numeric
i Float
od razu konwertują wartość na integer lub
float. Ponadto reguła URL
akceptuje również adres bez schematu (np. nette.org
) i dodaje schemat
(https://nette.org
). Wyrażenie w Pattern
i PatternIcase
musi pasować do całej wartości,
tzn. tak jakby było otoczone znakami ^
i $
.
Liczba elementów
Dla elementów addMultiUpload()
, addCheckboxList()
, addMultiSelect()
można użyć
również następujących reguł do ograniczenia liczby wybranych elementów lub przesłanych plików:
MinLength |
minimalna liczba | int |
MaxLength |
maksymalna liczba | int |
Length |
liczba w zakresie lub dokładna liczba | para [int, int] lub int |
Przesyłanie plików
Dla elementów addUpload()
, addMultiUpload()
można użyć również następujących reguł:
MaxFileSize |
maksymalny rozmiar pliku w bajtach | int |
MimeType |
Typ MIME, dozwolone symbole wieloznaczne ('video/*' ) |
string|string[] |
Image |
obraz JPEG, PNG, GIF, WebP, AVIF | – |
Pattern |
nazwa pliku pasuje do wyrażenia regularnego | string |
PatternInsensitive |
jak Pattern , ale niezależne od wielkości liter |
string |
MimeType
i Image
wymagają rozszerzenia PHP fileinfo
. To, czy plik lub obraz jest
wymaganego typu, wykrywają na podstawie jego sygnatury i nie weryfikują integralności całego pliku. Czy obraz nie jest
uszkodzony, można sprawdzić na przykład próbując go załadować.
Komunikaty o błędach
Wszystkie predefiniowane reguły z wyjątkiem Pattern
i PatternInsensitive
mają domyślny komunikat
o błędzie, więc można go pominąć. Jednak podanie i sformułowanie wszystkich komunikatów na miarę sprawi, że formularz
będzie bardziej przyjazny dla użytkownika.
Zmienić domyślne komunikaty można w konfiguracji, edytując
teksty w tablicy Nette\Forms\Validator::$messages
lub używając translatora.
W tekście komunikatów o błędach można używać następujących symboli zastępczych:
%d |
zastępuje kolejno argumentami reguły |
%n$d |
zastępuje n-tym argumentem reguły |
%label |
zastępuje etykietą elementu (bez dwukropka) |
%name |
zastępuje nazwą elementu (np. name ) |
%value |
zastępuje wartością wprowadzoną przez użytkownika |
$form->addText('name', 'Imię:')
->setRequired('Proszę wypełnić %label');
$form->addInteger('id', 'ID:')
->addRule($form::Range, 'co najmniej %d i co najwyżej %d', [5, 10]);
$form->addInteger('id', 'ID:')
->addRule($form::Range, 'co najwyżej %2$d i co najmniej %1$d', [5, 10]);
Warunki
Oprócz reguł można dodawać również warunki. Zapisuje się je podobnie jak reguły, tylko zamiast addRule()
używamy metody addCondition()
i oczywiście nie podajemy żadnego komunikatu o błędzie (warunek
tylko pyta):
$form->addPassword('password', 'Hasło:')
// jeśli hasło nie jest dłuższe niż 8 znaków
->addCondition($form::MaxLength, 8)
// to musi zawierać cyfrę
->addRule($form::Pattern, 'Musi zawierać cyfrę', '.*[0-9].*');
Warunek można powiązać również z innym elementem niż aktualny za pomocą addConditionOn()
. Jako pierwszy
parametr podajemy referencję do elementu. W tym przykładzie e-mail będzie obowiązkowy tylko wtedy, gdy zaznaczy się checkbox
(jego wartość będzie true):
$form->addCheckbox('newsletters', 'wysyłaj mi newslettery');
$form->addEmail('email', 'E-mail:')
// jeśli checkbox jest zaznaczony
->addConditionOn($form['newsletters'], $form::Equal, true)
// to wymagaj e-maila
->setRequired('Podaj adres e-mail');
Z warunków można tworzyć złożone struktury za pomocą elseCondition()
i endCondition()
:
$form->addText(/* ... */)
->addCondition(/* ... */) // jeśli pierwszy warunek jest spełniony
->addConditionOn(/* ... */) // i drugi warunek na innym elemencie
->addRule(/* ... */) // wymagaj tej reguły
->elseCondition() // jeśli drugi warunek nie jest spełniony
->addRule(/* ... */) // wymagaj tych reguł
->addRule(/* ... */)
->endCondition() // wracamy do pierwszego warunku
->addRule(/* ... */);
W Nette można bardzo łatwo reagować na spełnienie lub niespełnienie warunku również po stronie JavaScriptu za pomocą
metody toggle()
, zobacz Dynamiczny JavaScript.
Odwołanie do innego elementu
Jako argument reguły lub warunku można przekazać również inny element formularza. Reguła następnie użyje wartości
wprowadzonej później przez użytkownika w przeglądarce. W ten sposób można np. dynamicznie walidować, że element
password
zawiera ten sam ciąg znaków co element password_confirm
:
$form->addPassword('password', 'Hasło');
$form->addPassword('password_confirm', 'Potwierdź hasło')
->addRule($form::Equal, 'Podane hasła nie pasują', $form['password']);
Własne reguły i warunki
Czasami dochodzimy do sytuacji, gdy wbudowane reguły walidacji w Nette nam nie wystarczają i potrzebujemy zweryfikować dane od użytkownika po swojemu. W Nette jest to bardzo proste!
Metodom addRule()
lub addCondition()
można jako pierwszy parametr przekazać dowolny callback.
Przyjmuje on jako pierwszy parametr sam element i zwraca wartość boolean określającą, czy walidacja przebiegła pomyślnie.
Przy dodawaniu reguły za pomocą addRule()
można podać również dodatkowe argumenty, które są następnie
przekazywane jako drugi parametr.
Własny zestaw walidatorów możemy więc utworzyć jako klasę z metodami statycznymi:
class MyValidators
{
// testuje, czy wartość jest podzielna przez argument
public static function validateDivisibility(BaseControl $input, $arg): bool
{
return $input->getValue() % $arg === 0;
}
public static function validateEmailDomain(BaseControl $input, $domain)
{
// inne walidatory
}
}
Użycie jest następnie bardzo proste:
$form->addInteger('num')
->addRule(
[MyValidators::class, 'validateDivisibility'],
'Wartość musi być wielokrotnością liczby %d',
8,
);
Własne reguły walidacji można dodawać również do JavaScriptu. Warunkiem jest, aby reguła była metodą statyczną. Jej
nazwa dla walidatora JavaScriptowego powstaje przez połączenie nazwy klasy bez ukośników wstecznych \
,
podkreślenia _
i nazwy metody. Np. App\MyValidators::validateDivisibility
zapiszemy jako
AppMyValidators_validateDivisibility
i dodamy do obiektu Nette.validators
:
Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => {
return val % args === 0;
};
Zdarzenie onValidate
Po wysłaniu formularza przeprowadzana jest walidacja, podczas której sprawdzane są poszczególne reguły dodane za pomocą
addRule()
, a następnie wywoływane jest zdarzenie
onValidate
. Jego handler można wykorzystać do dodatkowej walidacji, typowo sprawdzenia poprawnej kombinacji
wartości w wielu elementach formularza.
Jeśli zostanie wykryty błąd, przekazujemy go do formularza metodą addError()
. Można ją wywołać albo na
konkretnym elemencie, albo bezpośrednio na formularzu.
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('Ta kombinacja nie jest możliwa.');
}
}
Błędy podczas przetwarzania
W wielu przypadkach o błędzie dowiadujemy się dopiero w momencie, gdy przetwarzamy poprawny formularz, na przykład
zapisujemy nową pozycję do bazy danych i napotykamy na duplikat kluczy. W takim przypadku błąd ponownie przekazujemy do
formularza metodą addError()
. Można ją wywołać albo na konkretnym elemencie, albo bezpośrednio na
formularzu:
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('Nieprawidłowe hasło.');
}
}
Jeśli to możliwe, zalecamy dołączenie błędu bezpośrednio do elementu formularza, ponieważ zostanie on wyświetlony obok niego przy użyciu domyślnego renderera.
$form['date']->addError('Przepraszamy, ale ta data jest już zajęta.');
Możesz wywoływać addError()
wielokrotnie i w ten sposób przekazać formularzowi lub elementowi więcej
komunikatów o błędach. Uzyskasz je za pomocą getErrors()
.
Uwaga, $form->getErrors()
zwraca podsumowanie wszystkich komunikatów o błędach, również tych, które
zostały przekazane bezpośrednio poszczególnym elementom, a nie tylko bezpośrednio formularzowi. Komunikaty o błędach
przekazane tylko formularzowi uzyskasz przez $form->getOwnErrors()
.
Modyfikacja danych wejściowych
Za pomocą metody addFilter()
możemy zmodyfikować wartość wprowadzoną przez użytkownika. W tym przykładzie
będziemy tolerować i usuwać spacje w kodzie pocztowym:
$form->addText('zip', 'Kod pocztowy:')
->addFilter(function ($value) {
return str_replace(' ', '', $value); // usuwamy spacje z kodu pocztowego
})
->addRule($form::Pattern, 'Kod pocztowy nie ma formatu pięciu cyfr', '\d{5}');
Filtr jest włączany między reguły walidacji i warunki, a zatem zależy od kolejności metod, tzn. filtr i reguła są
wywoływane w takiej kolejności, jak kolejność metod addFilter()
i addRule()
.
Walidacja JavaScript
Język do formułowania warunków i reguł jest bardzo potężny. Wszystkie konstrukcje działają zarówno po stronie
serwera, jak i po stronie JavaScriptu. Są one przekazywane w atrybutach HTML data-nette-rules
jako JSON. Samą
walidację przeprowadza następnie skrypt, który przechwytuje zdarzenie formularza submit
, przechodzi przez
poszczególne elementy i wykonuje odpowiednią walidację.
Tym skryptem jest netteForms.js
i jest dostępny z wielu możliwych źródeł:
Skrypt można wstawić bezpośrednio na stronę HTML z CDN:
<script src="https://unpkg.com/nette-forms@3"></script>
Lub skopiować lokalnie do publicznego folderu projektu (np. z
vendor/nette/forms/src/assets/netteForms.min.js
):
<script src="/path/to/netteForms.min.js"></script>
Lub zainstalować przez npm:
npm install nette-forms
A następnie załadować i uruchomić:
import netteForms from 'nette-forms';
netteForms.initOnLoad();
Alternatywnie można go załadować bezpośrednio z folderu vendor
:
import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js';
netteForms.initOnLoad();
Dynamiczny JavaScript
Chcesz wyświetlić pola do wprowadzenia adresu tylko wtedy, gdy użytkownik wybierze wysyłkę towaru pocztą? Żaden problem.
Kluczem jest para metod addCondition()
& toggle()
:
$form->addCheckbox('send_it')
->addCondition($form::Equal, true)
->toggle('#address-container');
Ten kod mówi, że gdy warunek jest spełniony, czyli gdy checkbox jest zaznaczony, widoczny będzie element HTML
#address-container
. I odwrotnie. Elementy formularza z adresem odbiorcy umieścimy więc w kontenerze o tym ID, a
po kliknięciu na checkbox ukryją się lub pokażą. Zapewnia to skrypt netteForms.js
.
Jako argument metody toggle()
można przekazać dowolny selektor. Z historycznych powodów alfanumeryczny ciąg
znaków bez dodatkowych znaków specjalnych jest traktowany jako ID elementu, czyli tak samo, jakby poprzedzał go znak
#
. Drugi, opcjonalny parametr pozwala odwrócić zachowanie, tzn. gdybyśmy użyli
toggle('#address-container', false)
, element zostałby wyświetlony tylko wtedy, gdy checkbox nie byłby
zaznaczony.
Domyślna implementacja w JavaScript zmienia właściwość hidden
elementów. Zachowanie można jednak łatwo
zmienić, na przykład dodać animację. Wystarczy w JavaScript nadpisać metodę Nette.toggle
własnym
rozwiązaniem:
Nette.toggle = (selector, visible, srcElement, event) => {
document.querySelectorAll(selector).forEach((el) => {
// ukrywamy lub pokazujemy 'el' zgodnie z wartością 'visible'
});
};
Wyłączenie walidacji
Czasami może się przydać wyłączenie walidacji. Jeśli naciśnięcie przycisku wysyłającego nie ma przeprowadzać
walidacji (odpowiednie dla przycisków Anuluj lub Podgląd), wyłączymy ją metodą
$submit->setValidationScope([])
. Jeśli ma przeprowadzać tylko częściową walidację, możemy określić,
które pola lub kontenery formularza mają być walidowane.
$form->addText('name')
->setRequired();
$details = $form->addContainer('details');
$details->addInteger('age')
->setRequired('wiek');
$details->addInteger('age2')
->setRequired('wiek2');
$form->addSubmit('send1'); // Waliduje cały formularz
$form->addSubmit('send2')
->setValidationScope([]); // Nie waliduje wcale
$form->addSubmit('send3')
->setValidationScope([$form['name']]); // Waliduje tylko element name
$form->addSubmit('send4')
->setValidationScope([$form['details']['age']]); // Waliduje tylko element age
$form->addSubmit('send5')
->setValidationScope([$form['details']]); // Waliduje kontener details
setValidationScope
nie wpływa na zdarzenie onValidate formularza, które
zostanie wywołane zawsze. Zdarzenie onValidate
kontenera zostanie wywołane tylko wtedy, gdy ten kontener jest
oznaczony do częściowej walidacji.