Formulare eigenständig verwenden
Nette Forms vereinfachen das Erstellen und Verarbeiten von Webformularen dramatisch. Sie können sie in Ihren Anwendungen völlig eigenständig ohne den Rest des Frameworks verwenden, wie dieses Kapitel zeigt.
Wenn Sie jedoch Nette Application und Presenter verwenden, gibt es für Sie eine eigene Anleitung: Formulare in Presentern.
Das erste Formular
Bevor Sie loslegen, installieren Sie das Paket über Composer:
composer require nette/forms
Versuchen wir, ein einfaches Registrierungsformular zu schreiben. Sein Code sieht so aus (vollständiger Code):
use Nette\Forms\Form;
$form = new Form;
$form->addText('name', 'Name:');
$form->addPassword('password', 'Passwort:');
$form->addSubmit('send', 'Registrieren');
Und rendern wir es ganz einfach:
$form->render();
Das Ergebnis sollte im Browser so aussehen:

Das Formular ist ein Objekt der Klasse Nette\Forms\Form (in Presentern wird die Klasse
Nette\Application\UI\Form verwendet). Wir haben ihm Elemente namens “name” und “password” sowie einen
Absende-Button hinzugefügt.
Jetzt hauchen wir dem Formular Leben ein. Über die Abfrage $form->isSuccess() stellen wir fest, ob das
Formular abgesendet und gültig ausgefüllt wurde. Wenn ja, geben wir die Daten aus. Ergänzen Sie hinter der Definition des
Formulars:
if ($form->isSuccess()) {
echo 'Das Formular wurde ausgefüllt und erfolgreich abgesendet';
$data = $form->getValues();
// $data->name enthält den Namen
// $data->password enthält das Passwort
var_dump($data);
}
Die Methode getValues() gibt die gesendeten Daten als Objekt ArrayHash zurück. Wie sich das ändern lässt, zeigen wir später. Das Objekt $data enthält die Schlüssel name und
password mit den Daten, die der Benutzer eingegeben hat.
Üblicherweise geben wir die Daten direkt zur weiteren Verarbeitung weiter, etwa zum Einfügen in eine Datenbank. Bei der
Verarbeitung kann jedoch ein Fehler auftreten, zum Beispiel wenn der Benutzername schon vergeben ist. In diesem Fall geben wir den
Fehler über addError() an das Formular zurück und lassen es samt Fehlermeldung erneut rendern.
$form->addError('Dieser Benutzername ist leider bereits vergeben.');
Nach dem Verarbeiten des Formulars leiten wir auf die nächste Seite weiter. Das verhindert, dass das Formular ungewollt erneut abgesendet wird, wenn der Benutzer Aktualisieren oder Zurück drückt oder in der Browser-Historie navigiert.
Standardmäßig wird das Formular mit der Methode POST an dieselbe Seite gesendet. Beides lässt sich ändern:
$form->setAction('/submit.php');
$form->setMethod('GET');
Und das ist im Grunde alles :-) Wir haben ein funktionierendes und bestens abgesichertes Formular.
Versuchen Sie, auch weitere Formularelemente hinzuzufügen.
Zugriff auf die Elemente
Das Formular und seine einzelnen Elemente heißen Komponenten. Sie bilden einen Komponentenbaum, dessen Wurzel das Formular ist. Auf die einzelnen Formularelemente greifen Sie so zu:
$input = $form->getComponent('name');
// alternative Schreibweise: $input = $form['name'];
$button = $form->getComponent('send');
// alternative Schreibweise: $button = $form['send'];
Entfernt werden Elemente über unset:
unset($form['name']);
Validierungsregeln
Das Wort gültig ist gefallen, aber das Formular hat noch keine Validierungsregeln. Bringen wir das in Ordnung.
Der Name wird zum Pflichtfeld, wir kennzeichnen ihn also mit der Methode setRequired(). Ihr Argument ist der Text
der Fehlermeldung, die angezeigt wird, wenn der Benutzer den Namen nicht ausfüllt. Wird kein Argument angegeben, wird die
Standard-Fehlermeldung verwendet.
$form->addText('name', 'Name:')
->setRequired('Bitte geben Sie einen Namen ein.');
Versuchen Sie, das Formular ohne ausgefüllten Namen abzusenden, und Sie sehen eine Fehlermeldung erscheinen. Der Browser oder der Server weist es ab, bis Sie das Feld ausfüllen.
Zugleich können Sie das System nicht austricksen, indem Sie nur Leerzeichen in das Feld schreiben. Auf keinen Fall. Nette schneidet Leerraum am Anfang und Ende automatisch ab. Probieren Sie es aus. Das sollten Sie bei jeder einzeiligen Eingabe immer tun, es wird aber oft vergessen. Nette macht es automatisch. (Sie können versuchen, das Formular hereinzulegen und als Namen einen mehrzeiligen String zu senden. Auch hier lässt sich Nette nicht täuschen, die Zeilenumbrüche werden in Leerzeichen umgewandelt.)
Das Formular wird immer auf der Serverseite validiert, es wird aber auch eine JavaScript-Validierung erzeugt. Sie läuft
sofort, und der Benutzer erfährt unmittelbar von Fehlern, ohne dass das Formular an den Server gesendet werden muss. Dafür sorgt
das Skript netteForms.js. Binden Sie es in die Seite ein:
<script src="https://unpkg.com/nette-forms@3"></script>
Wenn Sie sich den Quellcode der Seite mit dem Formular ansehen, fällt Ihnen vielleicht auf, dass Nette die Pflichtelemente in
Elemente mit der CSS-Klasse required setzt. Fügen Sie dem Template das folgende Stylesheet hinzu, und das Label
“Name” wird rot. So heben Sie Pflichtelemente für die Benutzer elegant hervor:
<style>
.required label { color: maroon }
</style>
Weitere Validierungsregeln fügen wir über die Methode addRule() hinzu. Der erste Parameter ist die Regel, der
zweite wiederum der Text der Fehlermeldung, und danach kann ein optionales Argument der Validierungsregel folgen. Was
bedeutet das?
Erweitern wir das Formular um ein neues optionales Feld “Alter”, das eine ganze Zahl sein muss (addInteger())
und in einem erlaubten Bereich liegen muss ($form::Range). Hier verwenden wir den dritten Parameter der Methode
addRule(), um dem Validator den verlangten Bereich als Paar [min, max] zu übergeben:
$form->addInteger('age', 'Alter:')
->addRule($form::Range, 'Das Alter muss zwischen 18 und 120 liegen.', [18, 120]);
Füllt der Benutzer das Feld nicht aus, werden die Validierungsregeln nicht geprüft, denn das Element ist optional.
Damit entsteht Raum für ein kleines Refactoring. In der Fehlermeldung und im dritten Parameter stehen die Zahlen doppelt, was
nicht ideal ist. Würden wir mehrsprachige Formulare bauen
und die Meldung mit den Zahlen in mehrere Sprachen übersetzen, wäre das Ändern der Werte mühsam. Aus diesem Grund lassen sich
die Platzhalter %d verwenden, und Nette setzt die Werte ein:
->addRule($form::Range, 'Das Alter muss zwischen %d und %d Jahren liegen.', [18, 120]);
Kehren wir zum Element password zurück, machen es ebenfalls zum Pflichtfeld und prüfen außerdem die
Mindestlänge des Passworts ($form::MinLength), wieder mit einem Platzhalter in der Meldung:
$form->addPassword('password', 'Passwort:')
->setRequired('Wählen Sie ein Passwort')
->addRule($form::MinLength, 'Das Passwort muss mindestens %d Zeichen lang sein', 8);
Fügen wir dem Formular noch ein Feld passwordVerify hinzu, in dem der Benutzer das Passwort zur Kontrolle erneut
eingibt. Über Validierungsregeln prüfen wir, ob beide Passwörter gleich sind ($form::Equal). Als Parameter geben
wir über eckige Klammern einen Verweis auf das erste Passwort an:
$form->addPassword('passwordVerify', 'Passwort erneut:')
->setRequired('Geben Sie das Passwort zur Kontrolle erneut ein')
->addRule($form::Equal, 'Die Passwörter stimmen nicht überein', $form['password'])
->setOmitted();
Mit setOmitted() haben wir ein Element gekennzeichnet, dessen Wert uns eigentlich nicht interessiert und das nur
zur Validierung da ist. Sein Wert wird nicht an $data übergeben.
Damit haben wir ein voll funktionsfähiges Formular mit Validierung in PHP und in JavaScript. Die Fähigkeiten von Nette zur Validierung reichen viel weiter; Sie können Bedingungen erstellen, anhand derer sich Teile der Seite ein- und ausblenden lassen, und so weiter. Alles erfahren Sie im Kapitel über die Validierung von Formularen.
Standardwerte
Für Formularelemente setzen wir oft Standardwerte:
$form->addEmail('email', 'E-Mail')
->setDefaultValue($lastUsedEmail);
Oft ist es nützlich, die Standardwerte für alle Elemente auf einmal zu setzen, zum Beispiel wenn das Formular zum Bearbeiten von Datensätzen dient. Wir lesen den Datensatz aus der Datenbank und setzen seine Werte als Standardwerte:
// $row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);
Rufen Sie setDefaults() nach dem Definieren der Elemente auf.
Bei einem bereits abgesendeten Formular hat setDefaults() keine Wirkung – es überschreibt nicht, was der
Benutzer ausgefüllt hat, es ist also gefahrlos, es in der Factory des Formulars ohne Bedingung aufzurufen. Wenn Sie die Werte
auch nach dem Absenden erzwingen müssen, verwenden Sie stattdessen setValues().
Rendering des Formulars
Standardmäßig wird das Formular als Tabelle gerendert. Die einzelnen Elemente halten die grundlegenden Richtlinien der
Barrierefreiheit ein – alle Labels werden als <label>-Elemente erzeugt und dem jeweiligen Formularelement
zugeordnet. Ein Klick auf ein Label setzt den Cursor automatisch in das Formularfeld.
Für jedes Element können wir beliebige HTML-Attribute setzen. Fügen wir zum Beispiel einen Platzhalter hinzu:
$form->addInteger('age', 'Alter:')
->setHtmlAttribute('placeholder', 'Bitte geben Sie das Alter an');
Es gibt viele Wege, ein Formular zu rendern, deshalb ist dem Rendering ein eigenes Kapitel gewidmet.
Rendering mit Latte
Wenn Sie die Templating-Engine Latte zur Hand haben, können Sie sie das Formular rendern lassen und die volle Kontrolle über das entstehende HTML gewinnen. Sie erzeugen die Engine, registrieren die Extension für Formulare und übergeben das Formular als Variable an das Template:
$latte = new Latte\Engine;
$latte->addExtension(new Nette\Bridges\FormsLatte\FormsExtension);
$latte->render('form.latte', ['form' => $form]);
Im Template arbeiten Sie dann über die Variable $form und Tags wie {input}, {label}
oder n:name mit dem Formular. Ein vollständiges Beispiel samt Template finden Sie im Verzeichnis examples (die Dateien latte.php und
latte/). Die einzelnen Tags beschreibt das Kapitel über das Rendering.
Mapping auf Klassen
Kehren wir zur Verarbeitung der Daten des Formulars zurück. Die Methode getValues() gab die gesendeten Daten als
Objekt ArrayHash zurück. Weil es eine generische Klasse ist wie stdClass, fehlt uns bei der Arbeit
damit einiger Komfort, etwa die Vervollständigung der Properties im Editor oder die statische Analyse des Codes. Lösen ließe
sich das, indem es für jedes Formular eine eigene Klasse gibt, deren Properties die einzelnen Elemente darstellen. Zum
Beispiel:
class RegistrationFormData
{
public string $name;
public ?int $age;
public string $password;
}
Alternativ können Sie den Konstruktor verwenden:
class RegistrationFormData
{
public function __construct(
public string $name,
public ?int $age,
public string $password,
) {
}
}
Die Properties der Datenklasse können auch Enums sein, sie werden automatisch gemappt.
Wie sagen wir Nette, dass es die Daten als Objekte dieser Klasse zurückgeben soll? Einfacher, als Sie denken. Sie müssen nur den Namen der Klasse oder das zu füllende Objekt als Parameter angeben:
$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;
Als Parameter lässt sich auch 'array' angeben, dann werden die Daten als Array zurückgegeben.
Bestehen die Formulare aus einer mehrstufigen Struktur von Containern, legen Sie für jeden eine eigene Klasse an:
$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 aus dem Typ der Property $person, dass es den Container auf die Klasse
PersonFormData mappen soll. Würde die Property ein Array von Containern enthalten, geben Sie den Typ
array an und übergeben die zu mappende Klasse direkt dem Container:
$person->setMappedType(PersonFormData::class);
Einen Vorschlag für die Datenklasse des Formulars können Sie sich über die Methode
Nette\Forms\Blueprint::dataClass($form) erzeugen lassen, die ihn auf der Seite im Browser ausgibt. Dann markieren Sie
den Code einfach mit einem Klick und kopieren ihn in Ihr Projekt.
Mehrere Absende-Buttons
Hat ein Formular mehr als einen Button, müssen wir üblicherweise unterscheiden, welcher davon gedrückt wurde. Diese
Information gibt die Methode isSubmittedBy() des Buttons zurück:
$form->addSubmit('save', 'Speichern');
$form->addSubmit('delete', 'Löschen');
if ($form->isSuccess()) {
if ($form['save']->isSubmittedBy()) {
// ...
}
if ($form['delete']->isSubmittedBy()) {
// ...
}
}
Lassen Sie die Prüfung $form->isSuccess() nicht weg; sie stellt die Gültigkeit der Daten sicher.
Wird ein Formular durch Drücken der Taste Enter abgesendet, wird das behandelt, als wäre es über den ersten Button abgesendet worden.
Schutz vor Sicherheitslücken
Das Nette Framework legt großen Wert auf Sicherheit und achtet deshalb gewissenhaft auf die richtige Absicherung von Formularen.
Neben dem Schutz von Formularen vor bekannten Sicherheitslücken wie Cross-Site Scripting (XSS) und Cross-Site Request Forgery (CSRF) trifft es viele kleine Sicherheitsmaßnahmen, an die Sie nicht mehr denken müssen.
So filtert es zum Beispiel alle Steuerzeichen aus den Eingaben und prüft die Gültigkeit der UTF-8-Kodierung, sodass die Daten aus dem Formular immer sauber sind. Bei Select-Boxen und Radio-Listen prüft es, dass die ausgewählten Elemente tatsächlich zu den angebotenen gehörten und keine Fälschung stattgefunden hat. Wir haben bereits erwähnt, dass es bei einzeiligen Textfeldern die Zeilenumbruchzeichen, die ein Angreifer senden könnte, durch Leerzeichen ersetzt. Bei mehrzeiligen Eingaben vereinheitlicht es die Zeilenumbruchzeichen. Und so weiter.
Nette kümmert sich für Sie um Sicherheitsrisiken, von deren Existenz viele Programmierer nicht einmal wissen.
Der erwähnte CSRF-Angriff besteht darin, dass ein Angreifer ein Opfer auf eine Seite lockt, die im Browser des Opfers unbemerkt einen Request an den Server ausführt, auf dem das Opfer gerade angemeldet ist. Der Server glaubt, das Opfer habe den Request freiwillig ausgelöst. Nette weist deshalb POST-Formulare ab, die von einem fremden Origin abgesendet wurden; auch eine andere Subdomain derselben Website gilt als fremd. Wenn Sie das Absenden von einem anderen Origin erlauben müssen, schalten Sie den Schutz ab mit:
$form->allowCrossOrigin(); // ACHTUNG! Schaltet den Schutz vollständig ab!
Damit ist der Schutz allerdings für jeden Origin abgeschaltet. Um nur bestimmte Origins zu erlauben, schalten Sie den Schutz
ab und prüfen den Header Origin selbst gegen eine eigene Allowlist.
Der Schutz stützt sich auf den Header Sec-Fetch-Site des Browsers (Fetch Metadata), den der Browser automatisch
sendet und der sich selbst mit einer XSS-Lücke nicht fälschen lässt. Ältere Browser, die diese Header nicht senden, bestehen
die Prüfung nicht. Der Artikel The browser finally solves CSRF
beschreibt das ausführlich.
Der frühere Schutz über ein Autorisierungstoken in der Session, der über $form->addProtection()
aktiviert wurde, ist nicht mehr nötig und seit Version 3.3 veraltet.
Damit haben wir eine kurze Einführung in Formulare in Nette hinter uns. Schauen Sie sich für weitere Anregungen das Verzeichnis examples in der Distribution an.