Nette Documentation Preview

syntax
Eigenständig verwendete Formulare
*********************************

.[perex]
Nette Forms vereinfacht die Erstellung und Verarbeitung von Webformularen erheblich. Sie können sie in Ihren Anwendungen völlig eigenständig ohne den Rest des Frameworks verwenden, was wir in diesem Kapitel demonstrieren werden.

Wenn Sie jedoch Nette Application und Presenter verwenden, gibt es eine Anleitung für Sie: [Formulare in Presentern |in-presenter].


Erstes Formular .[#toc-first-form]
==================================

Wir werden versuchen, ein einfaches Registrierungsformular zu schreiben. Sein Code wird wie folgt aussehen ("vollständiger Code":https://gist.github.com/dg/370a7e3094d9ba9a9e913b8e2a2dc851):

```php
use Nette\Forms\Form;

$form = new Form;
$form->addText('name', 'Name:');
$form->addPassword('password', 'Password:');
$form->addSubmit('send', 'Sign up');
```

Und nun wollen wir es rendern:

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

und das Ergebnis sollte so aussehen:

[* form-en.webp *]

Das Formular ist ein Objekt der Klasse `Nette\Forms\Form` (die Klasse `Nette\Application\UI\Form` wird in Presentern verwendet). Wir haben die Steuerelemente Name, Kennwort und Schaltfläche "Senden" hinzugefügt.

Nun werden wir das Formular erneut aufrufen. Indem wir `$form->isSuccess()` befragen, finden wir heraus, ob das Formular abgeschickt wurde und ob es gültig ausgefüllt wurde. Wenn ja, werden wir die Daten ausgeben. Nach der Definition des Formulars werden wir hinzufügen:

```php
if ($form->isSuccess()) {
	echo 'Das Formular wurde korrekt ausgefüllt und übermittelt';
	$data = $form->getValues();
	// $data->name enthält Name
	// $data->password enthält das Passwort
	var_dump($data);
}
```

Die Methode `getValues()` gibt die gesendeten Daten in Form eines Objekts [ArrayHash |utils:arrays#ArrayHash] zurück. Wir werden [später |#Mapping to Classes] zeigen, wie man dies ändern kann. Die Variable `$data` enthält die Schlüssel `name` und `password` mit den vom Benutzer eingegebenen Daten.

Normalerweise senden wir die Daten direkt zur weiteren Verarbeitung, die z.B. das Einfügen in die Datenbank sein kann. Bei der Verarbeitung kann jedoch ein Fehler auftreten, z. B. wenn der Benutzername bereits vergeben ist. In diesem Fall geben wir den Fehler mit `addError()` an das Formular zurück und lassen es neu zeichnen, mit einer Fehlermeldung:

```php
$form->addError('Sorry, username is already in use.');
```

Nach der Verarbeitung des Formulars leiten wir auf die nächste Seite weiter. Dadurch wird verhindert, dass das Formular durch Klicken auf die Schaltfläche *Aktualisieren* oder *Zurück* oder durch Verschieben des Browserverlaufs unbeabsichtigt erneut gesendet wird.

Standardmäßig wird das Formular mit der POST-Methode an dieselbe Seite gesendet. Beides kann geändert werden:

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

Und das ist alles :-) Wir haben ein funktionierendes und perfekt [gesichertes |#Vulnerability Protection] Formular.

Versuchen Sie, weitere [Formularsteuerelemente |controls] hinzuzufügen.


Zugang zu Steuerelementen .[#toc-access-to-controls]
====================================================

Das Formular und seine einzelnen Steuerelemente werden als Komponenten bezeichnet. Sie bilden einen Komponentenbaum, dessen Wurzel das Formular ist. Sie können auf die einzelnen Steuerelemente wie folgt zugreifen:

```php
$input = $form->getComponent('name');
// alternative Syntax: $input = $form['name'];

$button = $form->getComponent('send');
// alternative Syntax: $button = $form['send'];
```

Steuerelemente werden mit unset entfernt:

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


Überprüfungsregeln .[#toc-validation-rules]
===========================================

Das Wort *valid* wurde hier verwendet, aber das Formular hat noch keine Validierungsregeln. Das müssen wir ändern.

Der Name wird obligatorisch sein, also werden wir ihn mit der Methode `setRequired()` markieren, deren Argument der Text der Fehlermeldung ist, die angezeigt wird, wenn der Benutzer ihn nicht ausfüllt. Wenn kein Argument angegeben wird, wird die Standard-Fehlermeldung verwendet.

```php
$form->addText('name', 'Name:')
	->setRequired('Please enter a name.');
```

Versuchen Sie, das Formular abzuschicken, ohne den Namen auszufüllen, und Sie werden sehen, dass eine Fehlermeldung angezeigt wird und der Browser oder Server das Formular ablehnt, bis Sie es ausgefüllt haben.

Gleichzeitig können Sie das System nicht austricksen, indem Sie z. B. nur Leerzeichen in die Eingabe eintippen. Das geht nicht. Nette schneidet automatisch linke und rechte Leerzeichen ab. Probieren Sie es aus. Das sollten Sie bei jeder einzeiligen Eingabe immer tun, aber das wird oft vergessen. Nette macht es automatisch. (Sie können versuchen, die Formulare zu täuschen und eine mehrzeilige Zeichenkette als Namen zu senden. Auch hier lässt sich Nette nicht täuschen und die Zeilenumbrüche werden in Leerzeichen umgewandelt.)

Das Formular wird immer serverseitig validiert, aber es wird auch eine JavaScript-Validierung generiert, die schnell ist und dem Benutzer den Fehler sofort anzeigt, ohne dass das Formular an den Server gesendet werden muss. Dies wird durch das Skript `netteForms.js` erledigt.
Fügen Sie es in die Seite ein:

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

Wenn Sie sich den Quellcode der Seite mit dem Formular ansehen, werden Sie feststellen, dass Nette die erforderlichen Felder in Elemente mit einer CSS-Klasse `required` einfügt. Versuchen Sie, der Vorlage den folgenden Stil hinzuzufügen, und die Bezeichnung "Name" wird rot sein. Auf elegante Weise markieren wir die erforderlichen Felder für die Benutzer:

```latte
<style>
.required label { color: maroon }
</style>
```

Zusätzliche Validierungsregeln werden mit der Methode `addRule()` hinzugefügt. Der erste Parameter ist rule, der zweite ist wieder der Text der Fehlermeldung, und das optionale Argument validation rule kann folgen. Was bedeutet das?

Das Formular erhält eine weitere optionale Eingabe *Alter* mit der Bedingung, dass es eine Zahl sein muss (`addInteger()`) und in bestimmten Grenzen (`$form::Range`). Und hier werden wir das dritte Argument von `addRule()` verwenden, den Bereich selbst:

```php
$form->addInteger('age', 'Age:')
	->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]);
```

.[tip]
Wenn der Benutzer das Feld nicht ausfüllt, werden die Validierungsregeln nicht überprüft, da das Feld optional ist.

Offensichtlich ist Raum für ein kleines Refactoring vorhanden. In der Fehlermeldung und im dritten Parameter sind die Zahlen doppelt aufgeführt, was nicht ideal ist. Wenn wir ein [mehrsprachiges Formular |rendering#translating] erstellen würden und die Meldung mit den Zahlen in mehrere Sprachen übersetzt werden müsste, würde dies die Änderung der Werte erschweren. Aus diesem Grund können die Ersatzzeichen `%d` verwendet werden:

```php
	->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]);
```

Kehren wir zum Feld *Kennwort* zurück, machen es *erforderlich* und überprüfen die Mindestlänge des Kennworts (`$form::MinLength`), wiederum unter Verwendung der Ersatzzeichen in der Nachricht:

```php
$form->addPassword('password', 'Password:')
	->setRequired('Pick a password')
	->addRule($form::MinLength, 'Your password has to be at least %d long', 8);
```

Wir fügen dem Formular ein Feld `passwordVerify` hinzu, in das der Benutzer das Kennwort erneut eingibt, um es zu überprüfen. Mit Hilfe von Validierungsregeln überprüfen wir, ob beide Passwörter gleich sind (`$form::Equal`). Und als Argument geben wir in [eckigen Klammern |#Access to Controls] einen Verweis auf das erste Kennwort an:

```php
$form->addPassword('passwordVerify', 'Password again:')
	->setRequired('Fill your password again to check for typo')
	->addRule($form::Equal, 'Password mismatch', $form['password'])
	->setOmitted();
```

Mit `setOmitted()` markieren wir ein Element, dessen Wert uns nicht wirklich interessiert und das nur zur Validierung existiert. Sein Wert wird nicht an `$data` übergeben.

Wir haben ein voll funktionsfähiges Formular mit Validierung in PHP und JavaScript. Die Validierungsmöglichkeiten von Nette sind viel umfangreicher, Sie können Bedingungen erstellen, Teile einer Seite entsprechend anzeigen und ausblenden usw. Sie können alles im Kapitel über [Formularvalidierung |validation] nachlesen.


Standardwerte .[#toc-default-values]
====================================

Wir setzen oft Standardwerte für Formularsteuerelemente:

```php
$form->addEmail('email', 'Email')
	->setDefaultValue($lastUsedEmail);
```

Oft ist es sinnvoll, Standardwerte für alle Steuerelemente gleichzeitig festzulegen. Zum Beispiel, wenn das Formular verwendet wird, um Datensätze zu bearbeiten. Wir lesen den Datensatz aus der Datenbank und legen ihn als Standardwerte fest:

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

Rufen Sie `setDefaults()` auf, nachdem Sie die Steuerelemente definiert haben.


Rendering des Formulars .[#toc-rendering-the-form]
==================================================

Standardmäßig wird das Formular als Tabelle gerendert. Die einzelnen Steuerelemente entsprechen den grundlegenden Richtlinien für die Barrierefreiheit im Web. Alle Beschriftungen werden als `<label>` Elemente generiert und sind mit ihren Eingaben verknüpft; ein Klick auf die Beschriftung bewegt den Cursor auf die Eingabe.

Wir können für jedes Element beliebige HTML-Attribute festlegen. Fügen Sie zum Beispiel einen Platzhalter hinzu:

```php
$form->addInteger('age', 'Age:')
	->setHtmlAttribute('placeholder', 'Please fill in the age');
```

Es gibt wirklich viele Möglichkeiten, ein Formular zu rendern, daher ist dieses Kapitel dem [Rendering |rendering] gewidmet.


Mapping auf Klassen .[#toc-mapping-to-classes]
==============================================

Kehren wir zur Verarbeitung der Formulardaten zurück. Die Methode `getValues()` gibt die übermittelten Daten als `ArrayHash` Objekt zurück. Da es sich hierbei um eine generische Klasse handelt, ähnlich wie `stdClass`, fehlen uns einige Annehmlichkeiten bei der Arbeit damit, wie z. B. die Codevervollständigung für Eigenschaften in Editoren oder die statische Codeanalyse. Dies könnte durch eine spezifische Klasse für jedes Formular gelöst werden, deren Eigenschaften die einzelnen Steuerelemente darstellen. Z.B.:

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

Ab PHP 8.0 können Sie diese elegante Notation verwenden, die einen Konstruktor benutzt:

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

Wie kann man Nette anweisen, Daten als Objekte dieser Klasse zurückzugeben? Einfacher als Sie denken. Alles, was Sie tun müssen, ist, den Namen der Klasse oder des zu hydrierenden Objekts als Parameter anzugeben:

```php
$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;
```

Ein `'array'` kann ebenfalls als Parameter angegeben werden, und die Daten werden dann als Array zurückgegeben.

Wenn die Formulare aus einer mehrstufigen Struktur bestehen, die sich aus Containern zusammensetzt, erstellen Sie für jeden Container eine eigene Klasse:

```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;
}
```

Das Mapping weiß dann anhand des Eigenschaftstyps `$person`, dass es den Container auf die Klasse `PersonFormData` abbilden soll. Wenn die Eigenschaft ein Array von Containern enthalten würde, geben Sie den Typ `array` an und übergeben Sie die Klasse, die direkt auf den Container abgebildet werden soll:

```php
$person->setMappedType(PersonFormData::class);
```

Mit der Methode `Nette\Forms\Blueprint::dataClass($form)` können Sie einen Vorschlag für die Datenklasse eines Formulars erzeugen, der auf der Browserseite ausgedruckt wird. Sie können dann einfach auf den Code klicken, um ihn auszuwählen und in Ihr Projekt zu kopieren. .{data-version:3.1.15}


Mehrere Submit-Buttons .[#toc-multiple-submit-buttons]
======================================================

Wenn das Formular mehr als eine Schaltfläche hat, müssen wir normalerweise unterscheiden, welche Schaltfläche gedrückt wurde. Die Methode `isSubmittedBy()` der Schaltfläche gibt uns diese Information zurück:

```php
$form->addSubmit('save', 'Speichern');
$form->addSubmit('löschen', 'Löschen');

if ($form->isSuccess()) {
	if ($form['save']->isSubmittedBy()) {
		// ...
	}

	if ($form['delete']->isSubmittedBy()) {
		// ...
	}
}
```

Lassen Sie die `$form->isSuccess()` nicht aus, um die Gültigkeit der Daten zu überprüfen.

Wenn ein Formular mit der <kbd>Eingabetaste</kbd> abgeschickt wird, wird es so behandelt, als ob es mit der ersten Schaltfläche abgeschickt worden wäre.


Schutz vor Schwachstellen .[#toc-vulnerability-protection]
==========================================================

Nette Framework gibt sich große Mühe, sicher zu sein, und da Formulare die häufigste Benutzereingabe sind, sind Nette-Formulare so gut wie unangreifbar.

Zusätzlich zum Schutz der Formulare gegen bekannte Schwachstellen wie [Cross-Site Scripting (XSS) |nette:glossary#cross-site-scripting-xss] und [Cross-Site Request Forgery (CSRF |nette:glossary#cross-site-request-forgery-csrf] ) übernimmt es viele kleine Sicherheitsaufgaben, an die Sie nicht mehr denken müssen.

So filtert es beispielsweise alle Steuerzeichen aus den Eingaben heraus und überprüft die Gültigkeit der UTF-8-Kodierung, so dass die Daten aus dem Formular immer sauber sind. Bei Auswahlfeldern und Auswahllisten wird überprüft, ob die ausgewählten Elemente tatsächlich aus den angebotenen ausgewählt wurden und keine Fälschung vorliegt. Wie bereits erwähnt, werden bei einzeiligen Texteingaben Zeichen am Zeilenende entfernt, die ein Angreifer dort einfügen könnte. Bei mehrzeiligen Eingaben werden die Zeichen am Zeilenende normalisiert. Und so weiter.

Nette behebt für Sie Sicherheitslücken, von deren Existenz die meisten Programmierer keine Ahnung haben.

Der erwähnte CSRF-Angriff besteht darin, dass ein Angreifer das Opfer dazu verleitet, eine Seite zu besuchen, die im Browser des Opfers unbemerkt eine Anfrage an den Server ausführt, bei dem das Opfer gerade angemeldet ist, und der Server glaubt, dass die Anfrage vom Opfer absichtlich gestellt wurde. Daher verhindert Nette, dass das Formular per POST von einer anderen Domäne übermittelt wird. Wenn Sie aus irgendeinem Grund den Schutz ausschalten und die Übermittlung des Formulars von einer anderen Domäne aus zulassen möchten, verwenden Sie:

```php
$form->allowCrossOrigin(); // ACHTUNG! Schaltet den Schutz aus!
```

Dieser Schutz verwendet ein SameSite-Cookie namens `_nss`. Erstellen Sie daher ein Formular, bevor Sie die erste Ausgabe spülen, damit das Cookie gesendet werden kann.

Der SameSite-Cookie-Schutz ist möglicherweise nicht zu 100 % zuverlässig, weshalb es ratsam ist, den Token-Schutz zu aktivieren:

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

Es wird dringend empfohlen, diesen Schutz auf die Formulare in einem administrativen Teil Ihrer Anwendung anzuwenden, in dem sensible Daten geändert werden. Das Framework schützt vor einem CSRF-Angriff, indem es ein Authentifizierungs-Token generiert und validiert, das in einer Session gespeichert wird (das Argument ist die Fehlermeldung, die angezeigt wird, wenn das Token abgelaufen ist). Aus diesem Grund muss vor der Anzeige des Formulars eine Session gestartet werden. Im Verwaltungsteil der Website ist die Session in der Regel bereits durch die Anmeldung des Benutzers gestartet.
Ansonsten starten Sie die Session mit der Methode `Nette\Http\Session::start()`.

So, das war eine kurze Einführung in Formulare in Nette. Schauen Sie in das Verzeichnis der [Beispiele |https://github.com/nette/forms/tree/master/examples] in der Distribution, um weitere Anregungen zu erhalten.

Eigenständig verwendete Formulare

Nette Forms vereinfacht die Erstellung und Verarbeitung von Webformularen erheblich. Sie können sie in Ihren Anwendungen völlig eigenständig ohne den Rest des Frameworks verwenden, was wir in diesem Kapitel demonstrieren werden.

Wenn Sie jedoch Nette Application und Presenter verwenden, gibt es eine Anleitung für Sie: Formulare in Presentern.

Erstes Formular

Wir werden versuchen, ein einfaches Registrierungsformular zu schreiben. Sein Code wird wie folgt aussehen (vollständiger Code):

use Nette\Forms\Form;

$form = new Form;
$form->addText('name', 'Name:');
$form->addPassword('password', 'Password:');
$form->addSubmit('send', 'Sign up');

Und nun wollen wir es rendern:

$form->render();

und das Ergebnis sollte so aussehen:

Das Formular ist ein Objekt der Klasse Nette\Forms\Form (die Klasse Nette\Application\UI\Form wird in Presentern verwendet). Wir haben die Steuerelemente Name, Kennwort und Schaltfläche „Senden“ hinzugefügt.

Nun werden wir das Formular erneut aufrufen. Indem wir $form->isSuccess() befragen, finden wir heraus, ob das Formular abgeschickt wurde und ob es gültig ausgefüllt wurde. Wenn ja, werden wir die Daten ausgeben. Nach der Definition des Formulars werden wir hinzufügen:

if ($form->isSuccess()) {
	echo 'Das Formular wurde korrekt ausgefüllt und übermittelt';
	$data = $form->getValues();
	// $data->name enthält Name
	// $data->password enthält das Passwort
	var_dump($data);
}

Die Methode getValues() gibt die gesendeten Daten in Form eines Objekts ArrayHash zurück. Wir werden später zeigen, wie man dies ändern kann. Die Variable $data enthält die Schlüssel name und password mit den vom Benutzer eingegebenen Daten.

Normalerweise senden wir die Daten direkt zur weiteren Verarbeitung, die z.B. das Einfügen in die Datenbank sein kann. Bei der Verarbeitung kann jedoch ein Fehler auftreten, z. B. wenn der Benutzername bereits vergeben ist. In diesem Fall geben wir den Fehler mit addError() an das Formular zurück und lassen es neu zeichnen, mit einer Fehlermeldung:

$form->addError('Sorry, username is already in use.');

Nach der Verarbeitung des Formulars leiten wir auf die nächste Seite weiter. Dadurch wird verhindert, dass das Formular durch Klicken auf die Schaltfläche Aktualisieren oder Zurück oder durch Verschieben des Browserverlaufs unbeabsichtigt erneut gesendet wird.

Standardmäßig wird das Formular mit der POST-Methode an dieselbe Seite gesendet. Beides kann geändert werden:

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

Und das ist alles :-) Wir haben ein funktionierendes und perfekt gesichertes Formular.

Versuchen Sie, weitere Formularsteuerelemente hinzuzufügen.

Zugang zu Steuerelementen

Das Formular und seine einzelnen Steuerelemente werden als Komponenten bezeichnet. Sie bilden einen Komponentenbaum, dessen Wurzel das Formular ist. Sie können auf die einzelnen Steuerelemente wie folgt zugreifen:

$input = $form->getComponent('name');
// alternative Syntax: $input = $form['name'];

$button = $form->getComponent('send');
// alternative Syntax: $button = $form['send'];

Steuerelemente werden mit unset entfernt:

unset($form['name']);

Überprüfungsregeln

Das Wort valid wurde hier verwendet, aber das Formular hat noch keine Validierungsregeln. Das müssen wir ändern.

Der Name wird obligatorisch sein, also werden wir ihn mit der Methode setRequired() markieren, deren Argument der Text der Fehlermeldung ist, die angezeigt wird, wenn der Benutzer ihn nicht ausfüllt. Wenn kein Argument angegeben wird, wird die Standard-Fehlermeldung verwendet.

$form->addText('name', 'Name:')
	->setRequired('Please enter a name.');

Versuchen Sie, das Formular abzuschicken, ohne den Namen auszufüllen, und Sie werden sehen, dass eine Fehlermeldung angezeigt wird und der Browser oder Server das Formular ablehnt, bis Sie es ausgefüllt haben.

Gleichzeitig können Sie das System nicht austricksen, indem Sie z. B. nur Leerzeichen in die Eingabe eintippen. Das geht nicht. Nette schneidet automatisch linke und rechte Leerzeichen ab. Probieren Sie es aus. Das sollten Sie bei jeder einzeiligen Eingabe immer tun, aber das wird oft vergessen. Nette macht es automatisch. (Sie können versuchen, die Formulare zu täuschen und eine mehrzeilige Zeichenkette als Namen zu senden. Auch hier lässt sich Nette nicht täuschen und die Zeilenumbrüche werden in Leerzeichen umgewandelt.)

Das Formular wird immer serverseitig validiert, aber es wird auch eine JavaScript-Validierung generiert, die schnell ist und dem Benutzer den Fehler sofort anzeigt, ohne dass das Formular an den Server gesendet werden muss. Dies wird durch das Skript netteForms.js erledigt. Fügen Sie es in die Seite ein:

<script src="https://unpkg.com/nette-forms@3/src/assets/netteForms.js"></script>

Wenn Sie sich den Quellcode der Seite mit dem Formular ansehen, werden Sie feststellen, dass Nette die erforderlichen Felder in Elemente mit einer CSS-Klasse required einfügt. Versuchen Sie, der Vorlage den folgenden Stil hinzuzufügen, und die Bezeichnung „Name“ wird rot sein. Auf elegante Weise markieren wir die erforderlichen Felder für die Benutzer:

<style>
.required label { color: maroon }
</style>

Zusätzliche Validierungsregeln werden mit der Methode addRule() hinzugefügt. Der erste Parameter ist rule, der zweite ist wieder der Text der Fehlermeldung, und das optionale Argument validation rule kann folgen. Was bedeutet das?

Das Formular erhält eine weitere optionale Eingabe Alter mit der Bedingung, dass es eine Zahl sein muss (addInteger()) und in bestimmten Grenzen ($form::Range). Und hier werden wir das dritte Argument von addRule() verwenden, den Bereich selbst:

$form->addInteger('age', 'Age:')
	->addRule($form::Range, 'You must be older 18 years and be under 120.', [18, 120]);

Wenn der Benutzer das Feld nicht ausfüllt, werden die Validierungsregeln nicht überprüft, da das Feld optional ist.

Offensichtlich ist Raum für ein kleines Refactoring vorhanden. In der Fehlermeldung und im dritten Parameter sind die Zahlen doppelt aufgeführt, was nicht ideal ist. Wenn wir ein mehrsprachiges Formular erstellen würden und die Meldung mit den Zahlen in mehrere Sprachen übersetzt werden müsste, würde dies die Änderung der Werte erschweren. Aus diesem Grund können die Ersatzzeichen %d verwendet werden:

	->addRule($form::Range, 'You must be older %d years and be under %d.', [18, 120]);

Kehren wir zum Feld Kennwort zurück, machen es erforderlich und überprüfen die Mindestlänge des Kennworts ($form::MinLength), wiederum unter Verwendung der Ersatzzeichen in der Nachricht:

$form->addPassword('password', 'Password:')
	->setRequired('Pick a password')
	->addRule($form::MinLength, 'Your password has to be at least %d long', 8);

Wir fügen dem Formular ein Feld passwordVerify hinzu, in das der Benutzer das Kennwort erneut eingibt, um es zu überprüfen. Mit Hilfe von Validierungsregeln überprüfen wir, ob beide Passwörter gleich sind ($form::Equal). Und als Argument geben wir in eckigen Klammern einen Verweis auf das erste Kennwort an:

$form->addPassword('passwordVerify', 'Password again:')
	->setRequired('Fill your password again to check for typo')
	->addRule($form::Equal, 'Password mismatch', $form['password'])
	->setOmitted();

Mit setOmitted() markieren wir ein Element, dessen Wert uns nicht wirklich interessiert und das nur zur Validierung existiert. Sein Wert wird nicht an $data übergeben.

Wir haben ein voll funktionsfähiges Formular mit Validierung in PHP und JavaScript. Die Validierungsmöglichkeiten von Nette sind viel umfangreicher, Sie können Bedingungen erstellen, Teile einer Seite entsprechend anzeigen und ausblenden usw. Sie können alles im Kapitel über Formularvalidierung nachlesen.

Standardwerte

Wir setzen oft Standardwerte für Formularsteuerelemente:

$form->addEmail('email', 'Email')
	->setDefaultValue($lastUsedEmail);

Oft ist es sinnvoll, Standardwerte für alle Steuerelemente gleichzeitig festzulegen. Zum Beispiel, wenn das Formular verwendet wird, um Datensätze zu bearbeiten. Wir lesen den Datensatz aus der Datenbank und legen ihn als Standardwerte fest:

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

Rufen Sie setDefaults() auf, nachdem Sie die Steuerelemente definiert haben.

Rendering des Formulars

Standardmäßig wird das Formular als Tabelle gerendert. Die einzelnen Steuerelemente entsprechen den grundlegenden Richtlinien für die Barrierefreiheit im Web. Alle Beschriftungen werden als <label> Elemente generiert und sind mit ihren Eingaben verknüpft; ein Klick auf die Beschriftung bewegt den Cursor auf die Eingabe.

Wir können für jedes Element beliebige HTML-Attribute festlegen. Fügen Sie zum Beispiel einen Platzhalter hinzu:

$form->addInteger('age', 'Age:')
	->setHtmlAttribute('placeholder', 'Please fill in the age');

Es gibt wirklich viele Möglichkeiten, ein Formular zu rendern, daher ist dieses Kapitel dem Rendering gewidmet.

Mapping auf Klassen

Kehren wir zur Verarbeitung der Formulardaten zurück. Die Methode getValues() gibt die übermittelten Daten als ArrayHash Objekt zurück. Da es sich hierbei um eine generische Klasse handelt, ähnlich wie stdClass, fehlen uns einige Annehmlichkeiten bei der Arbeit damit, wie z. B. die Codevervollständigung für Eigenschaften in Editoren oder die statische Codeanalyse. Dies könnte durch eine spezifische Klasse für jedes Formular gelöst werden, deren Eigenschaften die einzelnen Steuerelemente darstellen. Z.B.:

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

Ab PHP 8.0 können Sie diese elegante Notation verwenden, die einen Konstruktor benutzt:

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

Wie kann man Nette anweisen, Daten als Objekte dieser Klasse zurückzugeben? Einfacher als Sie denken. Alles, was Sie tun müssen, ist, den Namen der Klasse oder des zu hydrierenden Objekts als Parameter anzugeben:

$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;

Ein 'array' kann ebenfalls als Parameter angegeben werden, und die Daten werden dann als Array zurückgegeben.

Wenn die Formulare aus einer mehrstufigen Struktur bestehen, die sich aus Containern zusammensetzt, erstellen Sie für jeden Container eine eigene Klasse:

$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;
}

Das Mapping weiß dann anhand des Eigenschaftstyps $person, dass es den Container auf die Klasse PersonFormData abbilden soll. Wenn die Eigenschaft ein Array von Containern enthalten würde, geben Sie den Typ array an und übergeben Sie die Klasse, die direkt auf den Container abgebildet werden soll:

$person->setMappedType(PersonFormData::class);

Mit der Methode Nette\Forms\Blueprint::dataClass($form) können Sie einen Vorschlag für die Datenklasse eines Formulars erzeugen, der auf der Browserseite ausgedruckt wird. Sie können dann einfach auf den Code klicken, um ihn auszuwählen und in Ihr Projekt zu kopieren.

Mehrere Submit-Buttons

Wenn das Formular mehr als eine Schaltfläche hat, müssen wir normalerweise unterscheiden, welche Schaltfläche gedrückt wurde. Die Methode isSubmittedBy() der Schaltfläche gibt uns diese Information zurück:

$form->addSubmit('save', 'Speichern');
$form->addSubmit('löschen', 'Löschen');

if ($form->isSuccess()) {
	if ($form['save']->isSubmittedBy()) {
		// ...
	}

	if ($form['delete']->isSubmittedBy()) {
		// ...
	}
}

Lassen Sie die $form->isSuccess() nicht aus, um die Gültigkeit der Daten zu überprüfen.

Wenn ein Formular mit der Eingabetaste abgeschickt wird, wird es so behandelt, als ob es mit der ersten Schaltfläche abgeschickt worden wäre.

Schutz vor Schwachstellen

Nette Framework gibt sich große Mühe, sicher zu sein, und da Formulare die häufigste Benutzereingabe sind, sind Nette-Formulare so gut wie unangreifbar.

Zusätzlich zum Schutz der Formulare gegen bekannte Schwachstellen wie Cross-Site Scripting (XSS) und Cross-Site Request Forgery (CSRF ) übernimmt es viele kleine Sicherheitsaufgaben, an die Sie nicht mehr denken müssen.

So filtert es beispielsweise alle Steuerzeichen aus den Eingaben heraus und überprüft die Gültigkeit der UTF-8-Kodierung, so dass die Daten aus dem Formular immer sauber sind. Bei Auswahlfeldern und Auswahllisten wird überprüft, ob die ausgewählten Elemente tatsächlich aus den angebotenen ausgewählt wurden und keine Fälschung vorliegt. Wie bereits erwähnt, werden bei einzeiligen Texteingaben Zeichen am Zeilenende entfernt, die ein Angreifer dort einfügen könnte. Bei mehrzeiligen Eingaben werden die Zeichen am Zeilenende normalisiert. Und so weiter.

Nette behebt für Sie Sicherheitslücken, von deren Existenz die meisten Programmierer keine Ahnung haben.

Der erwähnte CSRF-Angriff besteht darin, dass ein Angreifer das Opfer dazu verleitet, eine Seite zu besuchen, die im Browser des Opfers unbemerkt eine Anfrage an den Server ausführt, bei dem das Opfer gerade angemeldet ist, und der Server glaubt, dass die Anfrage vom Opfer absichtlich gestellt wurde. Daher verhindert Nette, dass das Formular per POST von einer anderen Domäne übermittelt wird. Wenn Sie aus irgendeinem Grund den Schutz ausschalten und die Übermittlung des Formulars von einer anderen Domäne aus zulassen möchten, verwenden Sie:

$form->allowCrossOrigin(); // ACHTUNG! Schaltet den Schutz aus!

Dieser Schutz verwendet ein SameSite-Cookie namens _nss. Erstellen Sie daher ein Formular, bevor Sie die erste Ausgabe spülen, damit das Cookie gesendet werden kann.

Der SameSite-Cookie-Schutz ist möglicherweise nicht zu 100 % zuverlässig, weshalb es ratsam ist, den Token-Schutz zu aktivieren:

$form->addProtection();

Es wird dringend empfohlen, diesen Schutz auf die Formulare in einem administrativen Teil Ihrer Anwendung anzuwenden, in dem sensible Daten geändert werden. Das Framework schützt vor einem CSRF-Angriff, indem es ein Authentifizierungs-Token generiert und validiert, das in einer Session gespeichert wird (das Argument ist die Fehlermeldung, die angezeigt wird, wenn das Token abgelaufen ist). Aus diesem Grund muss vor der Anzeige des Formulars eine Session gestartet werden. Im Verwaltungsteil der Website ist die Session in der Regel bereits durch die Anmeldung des Benutzers gestartet. Ansonsten starten Sie die Session mit der Methode Nette\Http\Session::start().

So, das war eine kurze Einführung in Formulare in Nette. Schauen Sie in das Verzeichnis der Beispiele in der Distribution, um weitere Anregungen zu erhalten.