Nette Documentation Preview

syntax
Formularze stosowane oddzielnie
*******************************

.[perex]
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|in-presenter] znajdziesz tutorial do [wykorzystania |in-presenter].


Pierwszy formularz .[#toc-first-form]
=====================================

Spróbujmy napisać prosty formularz rejestracyjny. Jego kod będzie następujący ("pełny kod":https://gist.github.com/dg/57878c1a413ae8ef0c1d83f02c43ef3f):

```php
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:

```php
$form->render();
```

i zostanie on wyświetlony w przeglądarce w następujący sposób:

[* form-cs.webp *]

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:

```php
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 |utils:arrays#ArrayHash]. Jak to zmienić pokażemy [później |#Mapping-to-Classes]. 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.

```php
$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ć:

```php
$form->setAction('/submit.php');
$form->setMethod('GET');
```

To wszystko :-) Mamy funkcjonalny i doskonale [zabezpieczony |#Vulnerability-Protection] formularz.

Spróbuj dodać inne [elementy formularza |controls].


Dostęp do elementów .[#toc-access-to-controls]
==============================================

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:

```php
$input = $form->getComponent('name');
// alternativní syntax: $input = $form['name'];

$button = $form->getComponent('send');
// alternativní syntax: $button = $form['send'];
```

Elementy są usuwane za pomocą unset:

```php
unset($form['name']);
```


Zasady walidacji .[#toc-validation-rules]
=========================================

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.

```php
$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ę:

```latte
<script src="https://unpkg.com/nette-forms@3/src/assets/netteForms.js"></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:

```latte
<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]`:

```php
$form->addInteger('age', 'Věk:')
	->addRule($form::Range, 'Věk musí být od 18 do 120', [18, 120]);
```

.[tip]
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 |rendering#translating], 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:

```php
	->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:

```php
$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 |#Access-to-Controls]:

```php
$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 |validation].


Wartości domyślne .[#toc-default-values]
========================================

Standardowo ustawiamy wartości domyślne dla elementów formularza:

```php
$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:

```php
//$row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);
```

Po zdefiniowaniu elementów wywołaj `setDefaults()`.


Rendering formularza .[#toc-rendering-the-form]
===============================================

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:

```php
$form->addInteger('age', 'Věk:')
	->setHtmlAttribute('placeholder', 'Prosím vyplňte věk');
```

Istnieje wiele sposobów renderowania formularza, dlatego [na temat renderowania |rendering] jest [osobny rozdział |rendering].


Odwzorowanie na klasy .[#toc-mapping-to-classes]
================================================

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:

```php
class RegistrationFormData
{
	public string $name;
	public int $age;
	public string $password;
}
```

Od PHP 8.0 możesz użyć tej eleganckiej notacji, która wykorzystuje konstruktor:

```php
class RegistrationFormData
{
	public function __construct(
		public string $name,
		public int $age,
		public string $password,
	) {
	}
}
```

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:

```php
$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ę:

```php
$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:

```php
$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. .{data-version:3.1.15}


Więcej przycisków .[#toc-multiple-submit-buttons]
=================================================

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:

```php
$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 <kbd>Enter</kbd>, jest on traktowany tak, jakby został przesłany pierwszym przyciskiem.


Ochrona przed podatnością na zagrożenia .[#toc-vulnerability-protection]
========================================================================

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) |nette:glossary#Cross-Site-Scripting-XSS] i [Cross-Site Request Forgery (CSRF) |nette:glossary#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:

```php
$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:

```php
$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 |https://github.com/nette/forms/tree/master/examples] w dystrubucji, aby uzyskać więcej inspiracji.

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/src/assets/netteForms.js"></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;
}

Od PHP 8.0 możesz użyć tej eleganckiej notacji, która wykorzystuje konstruktor:

class RegistrationFormData
{
	public function __construct(
		public string $name,
		public int $age,
		public string $password,
	) {
	}
}

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