Formularze stosowane oddzielnie
Nette Forms znacznie ułatwiają tworzenie i przetwarzanie formularzy internetowych. Możesz ich używać w swoich aplikacjach zupełnie samodzielnie bez reszty frameworka, co zademonstrujemy w tym rozdziale.
Jeśli jednak korzystasz z Nette Application i prezenterów, to w prezenterach znajdziesz tutorial do wykorzystania.
Pierwszy formularz
Spróbujmy napisać prosty formularz rejestracyjny. Jego kod będzie następujący (pełny kod):
use Nette\Forms\Form;
$form = new Form;
$form->addText('name', 'Jméno:');
$form->addPassword('password', 'Heslo:');
$form->addSubmit('send', 'Registrovat');
Możemy ją oddać w bardzo prosty sposób:
$form->render();
i zostanie on wyświetlony w przeglądarce w następujący sposób:
Forma jest obiektem klasy Nette\Forms\Form
(klasa Nette\Application\UI\Form
jest używana w
prezenterze). Dodałem elementy nazwy, hasła i przycisku submit.
A teraz zajmiemy się animacją formularza. Poprzez zapytanie $form->isSuccess()
możemy sprawdzić, czy
formularz został przesłany i czy został poprawnie wypełniony. Jeśli tak, to wydrukujemy te dane. Zatem po definicji
formularza dodajemy:
if ($form->isSuccess()) {
echo 'Formularz został poprawnie wypełniony i przesłany';
$data = $form->getValues();
// $data->name zawiera nazwę
// $data->password zawiera hasło
var_dump($data);
}
Metoda getValues()
zwraca przekazane dane w postaci obiektu ArrayHash. Jak to zmienić pokażemy później. Obiekt $data
zawiera klucze name
i password
z danymi wypełnionymi przez użytkownika.
Zazwyczaj wysyłamy dane bezpośrednio do dalszej obróbki, którą może być np. wstawienie ich do bazy danych. Podczas
przetwarzania może jednak wystąpić błąd, na przykład nazwa użytkownika jest już zajęta. W tym przypadku przekazujemy
błąd z powrotem do formularza za pomocą addError()
i mamy go renderować ponownie, z komunikatem
o błędzie.
$form->addError('Omlouváme se, toto uživatelské jméno už někdo používá.');
Po przetworzeniu formularza przekierowujemy na kolejną stronę. Zapobiega to niezamierzonemu ponownemu przesłaniu formularza za pomocą przycisków refresh, back lub historii przeglądarki.
Domyślnie formularz jest wysyłany metodą POST na tę samą stronę. Jedno i drugie można zmienić:
$form->setAction('/submit.php');
$form->setMethod('GET');
To wszystko :-) Mamy funkcjonalny i doskonale zabezpieczony formularz.
Spróbuj dodać inne elementy formularza.
Dostęp do elementów
Formularz i jego poszczególne elementy nazywane są komponentami. Tworzą one drzewo komponentów, gdzie korzeniem jest formularz. Do każdego elementu formularza można uzyskać dostęp w następujący sposób:
$input = $form->getComponent('name');
// alternativní syntax: $input = $form['name'];
$button = $form->getComponent('send');
// alternativní syntax: $button = $form['send'];
Elementy są usuwane za pomocą unset:
unset($form['name']);
Zasady walidacji
Słowo valid, ale formularz nie ma jeszcze reguł walidacji. Naprawmy to.
Nazwa będzie obowiązkowa, więc oznaczymy ją metodą setRequired()
, której argumentem jest tekst komunikatu
o błędzie, który zostanie wyświetlony, jeśli użytkownik nie wypełni nazwy. Jeśli nie podano żadnego argumentu, użyty
zostanie domyślny komunikat o błędzie.
$form->addText('name', 'Jméno:')
->setRequired('Zadejte prosím jméno');
Spróbuj przesłać formularz bez wypełnienia nazwy, a zobaczysz, że pojawi się komunikat o błędzie, a przeglądarka lub serwer odrzuci go, dopóki nie wypełnisz pola.
Jednocześnie nie oszukuj systemu, wpisując w pole np. same spacje. Nie rób tego. Nette automatycznie usuwa zarówno lewe jak i prawe spacje. Spróbuj. Jest to rodzaj rzeczy, którą powinieneś zawsze robić z każdym jednolinijkowym wejściem, ale często się o tym zapomina. Nette robi to automatycznie (możesz spróbować oszukać formularz, aby wysłać ciąg wieloliniowy jako nazwę. Nawet tutaj Nette nie da się oszukać i zmieni podziały linii na spacje).
Formularz jest zawsze walidowany po stronie serwera, ale generuje również walidację JavaScript, która jest wykonywana w
mgnieniu oka, a użytkownik dowiaduje się o błędzie natychmiast, bez konieczności wysyłania formularza na serwer. Zajmuje
się tym skrypt netteForms.js
. Wstaw go na stronę:
<script src="https://unpkg.com/nette-forms@3"></script>
Jeśli spojrzysz na kod źródłowy strony formularza, możesz zauważyć, że Nette wstawia wymagane elementy z klasą CSS
required
. Spróbuj dodać następujący arkusz stylów do szablonu, a etykieta “Nazwa” będzie czerwona. Dzięki
temu w elegancki sposób zaznaczymy dla użytkowników elementy obowiązkowe:
<style>
.required label { color: maroon }
</style>
Dodaj więcej reguł walidacji używając metody addRule()
. Pierwszy parametr to reguła, drugi to ponownie tekst
komunikatu o błędzie, po którym może nastąpić argument reguła walidacji. Co należy przez to rozumieć?
Rozszerzamy formularz o nowe, opcjonalne pole “wiek”, które musi być liczbą całkowitą (addInteger()
), a
także mieścić się w dozwolonym zakresie ($form::Range
). W tym miejscu używamy trzeciego parametru metody
addRule()
, aby przekazać wymagany zakres do walidatora jako parę [od, do]
:
$form->addInteger('age', 'Věk:')
->addRule($form::Range, 'Věk musí být od 18 do 120', [18, 120]);
Jeśli użytkownik nie wypełni pola, reguły walidacji nie zostaną sprawdzone, ponieważ element jest opcjonalny.
Jest tu miejsce na drobną refaktoryzację. W komunikacie o błędzie i w trzecim parametrze liczby są zduplikowane, co nie
jest idealne. Jeśli tworzyliśmy formularze
wielojęzyczne, a wiadomość zawierająca liczby została przetłumaczona na wiele języków, utrudniłoby to zmianę
wartości w razie potrzeby. Z tego powodu można użyć znaków zastępczych %d
, a Nette wypełni wartości:
->addRule($form::Range, 'Věk musí být od %d do %d let', [18, 120]);
Wróćmy do elementu password
, który również czynimy obowiązkowym, i sprawdźmy minimalną długość hasła
($form::MinLength
), ponownie używając znaku wieloznacznego:
$form->addPassword('password', 'Heslo:')
->setRequired('Zvolte si heslo')
->addRule($form::MinLength, 'Heslo musí mít alespoň %d znaků', 8);
Dodajmy do formularza pole passwordVerify
, w którym użytkownik wpisuje hasło jeszcze raz, w celu weryfikacji.
Korzystając z reguł walidacji sprawdzamy, czy oba hasła są takie same ($form::Equal
). A jako parametr
umieszczamy odwołanie do pierwszego hasła za pomocą nawiasów kwadratowych:
$form->addPassword('passwordVerify', 'Heslo pro kontrolu:')
->setRequired('Zadejte prosím heslo ještě jednou pro kontrolu')
->addRule($form::Equal, 'Hesla se neshodují', $form['password'])
->setOmitted();
Za pomocą setOmitted()
zaznaczyliśmy element, którego wartość tak naprawdę nas nie obchodzi i który
istnieje tylko ze względu na walidację. Wartość nie jest przekazywana do $data
.
W ten sposób mamy w pełni funkcjonalny formularz z walidacją w PHP i JavaScript. Możliwości walidacji Nette są znacznie szersze, możesz tworzyć warunki, mieć części strony wyświetlane i ukryte zgodnie z nimi itp. Możesz dowiedzieć się wszystkiego na ten temat w rozdziale poświęconym walidacji formularzy.
Wartości domyślne
Standardowo ustawiamy wartości domyślne dla elementów formularza:
$form->addEmail('email', 'E-mail')
->setDefaultValue($lastUsedEmail);
Często przydatne jest ustawienie wartości domyślnych dla wszystkich elementów jednocześnie. Na przykład, gdy formularz jest używany do edycji rekordów. Odczytaj rekord z bazy danych i ustaw wartości domyślne:
//$row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);
Po zdefiniowaniu elementów wywołaj setDefaults()
.
Rendering formularza
Domyślnie formularz jest renderowany jako tabela. Poszczególne elementy spełniają podstawową zasadę dostępności –
wszystkie etykiety są zapisane jako <label>
i powiązany z odpowiednim elementem formularza. Po kliknięciu
na etykietę, kursor automatycznie pojawia się w polu formularza.
Dla każdego elementu możemy ustawić dowolne atrybuty HTML. Na przykład dodać placeholder:
$form->addInteger('age', 'Věk:')
->setHtmlAttribute('placeholder', 'Prosím vyplňte věk');
Istnieje wiele sposobów renderowania formularza, dlatego na temat renderowania jest osobny rozdział.
Odwzorowanie na klasy
Wróćmy do przetwarzania danych z formularza. Metoda getValues()
zwróciła przesłane dane jako obiekt
ArrayHash
. Ponieważ jest to klasa generyczna, coś jak stdClass
, zabraknie nam pewnych wygód podczas
pracy z nią, takich jak uzupełnianie kodu dla właściwości w edytorach czy statyczna analiza kodu. Można to rozwiązać
poprzez posiadanie specyficznej klasy dla każdego formularza, której właściwości reprezentują poszczególne
kontrolki. Np:
class RegistrationFormData
{
public string $name;
public int $age;
public string $password;
}
Alternatywnie można użyć konstruktora:
class RegistrationFormData
{
public function __construct(
public string $name,
public int $age,
public string $password,
) {
}
}
Właściwości klasy danych mogą być również wyliczeniami i zostaną automatycznie zmapowane.
Jak powiedzieć Nette, aby zwracała dane jako obiekty tej klasy? Łatwiejsze niż myślisz. Wystarczy, że jako parametr podasz nazwę klasy lub obiektu do nawodnienia:
$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;
Możesz również określić 'array'
jako parametr, a następnie zwrócić dane jako tablicę.
Jeśli formularze tworzą wielopoziomową strukturę składającą się z kontenerów, utwórz dla każdego z nich osobną klasę:
$form = new Form;
$person = $form->addContainer('person');
$person->addText('firstName');
/* ... */
class PersonFormData
{
public string $firstName;
public string $lastName;
}
class RegistrationFormData
{
public PersonFormData $person;
public int $age;
public string $password;
}
Mapowanie wie wtedy z typu właściwości $person
, że powinno mapować kontener do klasy
PersonFormData
. Jeśli właściwość zawierałaby tablicę kontenerów, określ typ array
i przekaż
klasę mapowania bezpośrednio do kontenera:
$person->setMappedType(PersonFormData::class);
Propozycję klasy danych formularza można wygenerować za pomocą metody
Nette\Forms\Blueprint::dataClass($form)
, która wydrukuje ją na stronie przeglądarki. Następnie wystarczy
kliknąć, aby wybrać i skopiować kod do swojego projektu.
Więcej przycisków
Jeśli formularz ma więcej niż jeden przycisk, zwykle musimy rozróżnić, który z nich został naciśnięty. Informacja ta
jest zwracana przez metodę isSubmittedBy()
button:
$form->addSubmit('save', 'Uložit');
$form->addSubmit('delete', 'Smazat');
if ($form->isSuccess()) {
if ($form['save']->isSubmittedBy()) {
// ...
}
if ($form['delete']->isSubmittedBy()) {
// ...
}
}
Nie pomijaj zapytania do $form->isSuccess()
, to zweryfikuje poprawność danych.
Po przesłaniu formularza przyciskiem Enter, jest on traktowany tak, jakby został przesłany pierwszym przyciskiem.
Ochrona przed podatnością na zagrożenia
Nette Framework przykłada dużą wagę do bezpieczeństwa i dlatego skrupulatnie dba o to, aby formularze były dobrze zabezpieczone.
Oprócz ochrony formularzy przed atakami Cross Site Scripting (XSS) i Cross-Site Request Forgery (CSRF), robi wiele małych rzeczy związanych z bezpieczeństwem, o których nie musisz myśleć.
Na przykład filtruje wszystkie znaki kontrolne z danych wejściowych i sprawdza ważność kodowania UTF-8, więc dane formularza zawsze będą czyste. W przypadku pól wyboru i arkuszy radiowych weryfikuje, czy wybrane pozycje rzeczywiście pochodziły z oferowanych i nie zostały spreparowane. Wspomnieliśmy już, że dla jednolinijkowych danych tekstowych, usuwa ona znaki końca linii, które mógłby wysłać atakujący. Dla wejść wieloliniowych normalizuje znaki dla przerw w linii. I tak dalej.
Nette rozwiązuje za Ciebie zagrożenia bezpieczeństwa, o których istnieniu wielu programistów nawet nie wie.
Wspomniany wcześniej atak CSRF polega na tym, że atakujący zwabia ofiarę na stronę, która w subtelny sposób wykonuje w przeglądarce ofiary żądanie do serwera, na którym ofiara jest zalogowana, a serwer zakłada, że żądanie zostało wykonane przez ofiarę z własnej woli. Dlatego Nette zapobiega wysyłaniu formularza POST z innej domeny. Jeśli z jakiegoś powodu chcesz wyłączyć ochronę i pozwolić na wysłanie formularza z innej domeny, użyj:
$form->allowCrossOrigin(); // UWAGA! Wyłącza ochronę!
To zabezpieczenie wykorzystuje plik cookie SameSite o nazwie _nss
. Dlatego utwórz obiekt formularza przed
wysłaniem pierwszego wyjścia, aby plik cookie mógł zostać wysłany.
Ochrona plików cookie SameSite może nie być w 100% niezawodna, dlatego zaleca się włączenie również ochrony tokenów:
$form->addProtection();
Zalecane jest zabezpieczenie formularzy w części administracyjnej witryny, które zmieniają wrażliwe dane w aplikacji.
Framework broni się przed atakami CSRF, generując i weryfikując token autoryzacji, który jest przechowywany w sesji. W
związku z tym konieczne jest, aby sesja była otwarta, zanim formularz będzie mógł być przeglądany. W części
administracyjnej witryny sesja jest już zazwyczaj rozpoczęta z powodu zalogowania się użytkownika. W przeciwnym razie
rozpocznij sesję za pomocą metody Nette\Http\Session::start()
.
Tak więc, mamy za sobą szybkie wprowadzenie do formularzy w Nette. Spróbuj ponownie spojrzeć na katalog przykładów w dystrubucji, aby uzyskać więcej inspiracji.