Formulare separat verwenden
Nette Forms erleichtert die Erstellung und Verarbeitung von Webformularen erheblich. Sie können sie in Ihren Anwendungen völlig unabhängig vom Rest des Frameworks verwenden, was wir in diesem Kapitel zeigen werden.
Wenn Sie jedoch Nette Application und Presenter verwenden, ist die Anleitung zur Verwendung in Presentern für Sie bestimmt.
Erstes Formular
Versuchen wir, ein einfaches Registrierungsformular zu schreiben. Sein Code wird wie folgt aussehen (Gesamter Code):
use Nette\Forms\Form;
$form = new Form;
$form->addText('name', 'Name:');
$form->addPassword('password', 'Passwort:');
$form->addSubmit('send', 'Registrieren');
Wir können es sehr einfach rendern:
$form->render();
und im Browser wird es so angezeigt:

Das Formular ist ein Objekt der Klasse Nette\Forms\Form
(die Klasse Nette\Application\UI\Form
wird in
Presentern verwendet). Wir haben sogenannte Elemente hinzugefügt: Name, Passwort und einen Senden-Button.
Und jetzt beleben wir das Formular. Durch Abfrage von $form->isSuccess()
stellen wir fest, ob das Formular
gesendet wurde und ob es gültig ausgefüllt wurde. Wenn ja, geben wir die Daten aus. Hinter die Formulardefinition fügen wir
also hinzu:
if ($form->isSuccess()) {
echo 'Formular wurde korrekt ausgefüllt und gesendet';
$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 in Form eines ArrayHash Objekts zurück. Wie man das ändert, zeigen wir später. Das Objekt $data
enthält die Schlüssel name
und
password
mit den vom Benutzer eingegebenen Daten.
Normalerweise senden wir die Daten direkt zur weiteren Verarbeitung, was zum Beispiel das Einfügen in eine Datenbank sein
kann. Während der Verarbeitung kann jedoch ein Fehler auftreten, z. B. ist der Benutzername bereits vergeben. In diesem Fall
geben wir den Fehler mit addError()
an das Formular zurück und lassen es erneut rendern, zusammen mit der
Fehlermeldung.
$form->addError('Entschuldigung, dieser Benutzername wird bereits verwendet.');
Nach der Verarbeitung des Formulars leiten wir auf die nächste Seite weiter. Dies verhindert das unbeabsichtigte erneute Senden des Formulars durch die Schaltflächen Aktualisieren, Zurück oder durch die Navigation im Browserverlauf.
Das Formular wird standardmäßig per POST-Methode an dieselbe Seite gesendet. Beides kann geändert werden:
$form->setAction('/submit.php');
$form->setMethod('GET');
Und das ist eigentlich alles :-) Wir haben ein funktionierendes und perfekt gesichertes Formular.
Versuchen Sie, auch weitere Formularelemente hinzuzufügen.
Zugriff auf Elemente
Das Formular und seine einzelnen Elemente nennen wir Komponenten. Sie bilden einen Komponentenbaum, dessen Wurzel das Formular ist. Auf die einzelnen Elemente des Formulars greifen wir folgendermaßen zu:
$input = $form->getComponent('name');
// alternative Syntax: $input = $form['name'];
$button = $form->getComponent('send');
// alternative Syntax: $button = $form['send'];
Elemente werden mit unset entfernt:
unset($form['name']);
Validierungsregeln
Das Wort gültig wurde erwähnt, aber das Formular hat noch keine Validierungsregeln. Ändern wir das.
Der Name ist ein Pflichtfeld, daher markieren wir ihn mit der Methode setRequired()
, deren Argument der Text der
Fehlermeldung ist, die angezeigt wird, wenn der Benutzer den Namen nicht eingibt. Wenn kein Argument angegeben wird, wird die
Standardfehlermeldung verwendet.
$form->addText('name', 'Name:')
->setRequired('Bitte geben Sie einen Namen ein');
Versuchen Sie, das Formular ohne ausgefüllten Namen zu senden, und Sie werden sehen, dass eine Fehlermeldung angezeigt wird und der Browser oder Server es ablehnt, bis Sie das Feld ausfüllen.
Gleichzeitig können Sie das System nicht austricksen, indem Sie beispielsweise nur Leerzeichen in das Feld eingeben. Nein. Nette entfernt automatisch führende und nachfolgende Leerzeichen. Probieren Sie es aus. Das ist etwas, was Sie immer bei jedem einzeiligen Input tun sollten, aber oft vergessen wird. Nette erledigt das automatisch. (Sie können versuchen, das Formular zu täuschen und einen mehrzeiligen String als Namen zu senden. Auch hier lässt sich Nette nicht täuschen und wandelt Zeilenumbrüche in Leerzeichen um.)
Das Formular wird immer serverseitig validiert, aber es wird auch eine JavaScript-Validierung generiert, die blitzschnell
abläuft und der Benutzer sofort über den Fehler informiert wird, ohne das Formular an den Server senden zu müssen. Dafür sorgt
das Skript netteForms.js
. Fügen 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, können Sie feststellen, dass Nette Pflichtelemente in Elemente
mit der CSS-Klasse required
einfügt. Versuchen Sie, das folgende Stylesheet in die Vorlage einzufügen, und die
Beschriftung „Name“ wird rot. So kennzeichnen wir elegant die Pflichtelemente für die Benutzer:
<style>
.required label { color: maroon }
</style>
Weitere Validierungsregeln fügen wir mit der Methode addRule()
hinzu. Der erste Parameter ist die Regel, der
zweite ist wieder der Text der Fehlermeldung, und es kann noch ein Argument für die Validierungsregel folgen. Was ist damit
gemeint?
Wir erweitern das Formular um ein neues optionales Feld „Alter“, das eine ganze Zahl sein muss (addInteger()
)
und zusätzlich in einem erlaubten Bereich liegen muss ($form::Range
). Und hier verwenden wir den dritten Parameter
der Methode addRule()
, mit dem wir dem Validator den gewünschten Bereich als Paar [von, bis]
übergeben:
$form->addInteger('age', 'Alter:')
->addRule($form::Range, 'Das Alter muss zwischen 18 und 120 liegen', [18, 120]);
Wenn der Benutzer das Feld nicht ausfüllt, werden die Validierungsregeln nicht überprüft, da das Element optional ist.
Hier entsteht Raum für ein kleines Refactoring. In der Fehlermeldung und im dritten Parameter sind die Zahlen doppelt
aufgeführt, was nicht ideal ist. Wenn wir mehrsprachige
Formulare erstellen würden und die Meldung, die Zahlen enthält, in mehrere Sprachen übersetzt würde, würde eine spätere
Änderung der Werte erschwert. Aus diesem Grund können Platzhalter %d
verwendet werden, und Nette füllt die
Werte ein:
->addRule($form::Range, 'Das Alter muss zwischen %d und %d Jahren liegen', [18, 120]);
Kehren wir zum Element password
zurück, das wir ebenfalls als Pflichtfeld definieren und zusätzlich die
Mindestlänge des Passworts überprüfen ($form::MinLength
), wieder unter Verwendung des Platzhalters:
$form->addPassword('password', 'Passwort:')
->setRequired('Wählen Sie ein Passwort')
->addRule($form::MinLength, 'Das Passwort muss mindestens %d Zeichen lang sein', 8);
Wir fügen dem Formular noch das Feld passwordVerify
hinzu, in das der Benutzer das Passwort zur Kontrolle erneut
eingibt. Mithilfe von Validierungsregeln überprüfen wir, ob beide Passwörter übereinstimmen ($form::Equal
). Und
als Parameter geben wir einen Verweis auf das erste Passwort mithilfe von eckigen
Klammern:
$form->addPassword('passwordVerify', 'Passwort zur Kontrolle:')
->setRequired('Bitte 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 markiert, dessen Wert uns eigentlich egal ist und das nur aus
Validierungsgründen existiert. Der Wert wird nicht an $data
übergeben.
Damit haben wir ein voll funktionsfähiges Formular mit Validierung in PHP und JavaScript. Die Validierungsfähigkeiten von Nette sind weitaus umfangreicher, es können Bedingungen erstellt, Teile der Seite darauf basierend ein- und ausgeblendet werden usw. Alles erfahren Sie im Kapitel über Formularvalidierung.
Standardwerte
Elementen des Formulars weisen wir üblicherweise Standardwerte zu:
$form->addEmail('email', 'E-mail')
->setDefaultValue($lastUsedEmail);
Oft ist es nützlich, allen Elementen gleichzeitig Standardwerte zuzuweisen. Zum Beispiel, wenn das Formular zur Bearbeitung von Datensätzen dient. Wir lesen den Datensatz aus der Datenbank und setzen die Standardwerte:
//$row = ['name' => 'John', 'age' => '33', /* ... */];
$form->setDefaults($row);
Rufen Sie setDefaults()
erst nach der Definition der Elemente auf.
Rendering des Formulars
Standardmäßig wird das Formular als Tabelle gerendert. Die einzelnen Elemente erfüllen die grundlegende
Zugänglichkeitsregel – alle Beschriftungen sind als <label>
geschrieben und mit dem entsprechenden
Formularelement verknüpft. Beim Klicken auf die Beschriftung erscheint der Cursor automatisch im Formularfeld.
Jedem Element können wir beliebige HTML-Attribute zuweisen. Zum Beispiel einen Platzhalter hinzufügen:
$form->addInteger('age', 'Alter:')
->setHtmlAttribute('placeholder', 'Bitte geben Sie das Alter ein');
Es gibt wirklich viele Möglichkeiten, ein Formular zu rendern, daher gibt es ein eigenes Kapitel zum Rendering.
Mapping auf Klassen
Kehren wir zur Verarbeitung der Formulardaten zurück. Die Methode getValues()
gab uns die gesendeten Daten als
ArrayHash
-Objekt zurück. Da es sich um eine generische Klasse handelt, ähnlich wie stdClass
, fehlt uns
bei der Arbeit damit ein gewisser Komfort, wie z.B. die Code-Vervollständigung für Properties in Editoren oder die statische
Codeanalyse. Dies könnte gelöst werden, indem wir für jedes Formular eine spezifische Klasse hätten, deren Properties die
einzelnen Elemente repräsentieren. Z.B.:
class RegistrationFormData
{
public string $name;
public ?int $age;
public string $password;
}
Alternativ können Sie einen 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 und werden automatisch zugeordnet.
Wie sagen wir Nette, dass es die Daten als Objekte dieser Klasse zurückgeben soll? Einfacher als Sie denken. Es reicht aus, den Klassennamen oder das zu hydratisierende Objekt als Parameter anzugeben:
$data = $form->getValues(RegistrationFormData::class);
$name = $data->name;
Als Parameter kann auch 'array'
angegeben werden, dann werden die Daten als Array zurückgegeben.
Wenn Formulare eine mehrstufige Struktur aus Containern bilden, erstellen Sie für jeden eine separate 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 erkennt dann am Typ der Property $person
, dass der Container der Klasse PersonFormData
zugeordnet werden soll. Wenn die Property ein Array von Containern enthält, geben Sie den Typ array
an und
übergeben Sie die zuzuordnende Klasse direkt an den Container:
$person->setMappedType(PersonFormData::class);
Den Entwurf der Datenklasse des Formulars können Sie sich mit der Methode
Nette\Forms\Blueprint::dataClass($form)
generieren lassen, die ihn auf der Browserseite ausgibt. Den Code können Sie
dann einfach per Klick markieren und in Ihr Projekt kopieren.
Mehrere Buttons
Wenn ein Formular mehr als einen Button hat, müssen wir in der Regel unterscheiden, welcher davon gedrückt wurde. Diese
Information liefert uns die Methode isSubmittedBy()
des Buttons:
$form->addSubmit('save', 'Speichern');
$form->addSubmit('delete', 'Löschen');
if ($form->isSuccess()) {
if ($form['save']->isSubmittedBy()) {
// ...
}
if ($form['delete']->isSubmittedBy()) {
// ...
}
}
Lassen Sie die Abfrage $form->isSuccess()
nicht aus, sie überprüft die Gültigkeit der Daten.
Wenn das Formular durch Drücken von Enter gesendet wird, wird dies so behandelt, als ob der erste Button gesendet wurde.
Schutz vor Schwachstellen
Das Nette Framework legt großen Wert auf Sicherheit und achtet daher sorgfältig auf die gute Absicherung von Formularen.
Neben dem Schutz der Formulare vor Cross Site Scripting (XSS) und Cross-Site Request Forgery (CSRF) Angriffen, führt es viele kleine Sicherheitsmaßnahmen durch, an die Sie nicht mehr denken müssen.
So filtert es beispielsweise alle Steuerzeichen aus den Eingaben und überprüft die Gültigkeit der UTF-8-Kodierung, sodass die Daten aus dem Formular immer sauber sind. Bei Select-Boxen und Radio-Listen wird überprüft, ob die ausgewählten Elemente tatsächlich aus den angebotenen stammten und keine Fälschung stattgefunden hat. Wir haben bereits erwähnt, dass bei einzeiligen Texteingaben Zeilenumbruchzeichen entfernt werden, die ein Angreifer dort hätte senden können. Bei mehrzeiligen Eingaben werden die Zeilenumbruchzeichen normalisiert. Und so weiter.
Nette löst für Sie Sicherheitsrisiken, von denen viele Programmierer nicht einmal wissen, dass sie existieren.
Der erwähnte CSRF-Angriff besteht darin, dass ein Angreifer das Opfer auf eine Seite lockt, die unauffällig im Browser des Opfers eine Anfrage an den Server stellt, bei dem das Opfer angemeldet ist, und der Server annimmt, dass die Anfrage vom Opfer aus freiem Willen ausgeführt wurde. Daher verhindert Nette das Senden von POST-Formularen von einer anderen Domain. Wenn Sie aus irgendeinem Grund den Schutz deaktivieren und das Senden von Formularen von einer anderen Domain erlauben möchten, verwenden Sie:
$form->allowCrossOrigin(); // VORSICHT! Deaktiviert den Schutz!
Dieser Schutz verwendet ein SameSite-Cookie namens _nss
. Erstellen Sie daher das Formularobjekt, bevor die erste
Ausgabe gesendet wird, damit das Cookie gesendet werden kann.
Der Schutz durch das SameSite-Cookie ist möglicherweise nicht 100% zuverlässig, daher ist es ratsam, zusätzlich den Schutz durch ein Token zu aktivieren:
$form->addProtection();
Wir empfehlen, Formulare im Administrationsbereich der Website, die sensible Daten in der Anwendung ändern, auf diese Weise zu
schützen. Das Framework wehrt sich gegen CSRF-Angriffe, indem es ein Autorisierungstoken generiert und überprüft, das in der
Session gespeichert wird. Daher muss die Session vor dem Anzeigen des Formulars geöffnet sein. Im Administrationsbereich der
Website ist die Session normalerweise bereits aufgrund der Benutzeranmeldung gestartet. Andernfalls starten Sie die Session mit
der Methode Nette\Http\Session::start()
.
So, das war eine schnelle Einführung in Formulare in Nette. Werfen Sie noch einen Blick in das Verzeichnis examples in der Distribution, dort finden Sie weitere Inspiration.