Formularvalidierung
Pflichtelemente
Elemente werden über die Methode setRequired() als Pflichtfelder gekennzeichnet. Ihr Argument ist der Text der Fehlermeldung, die angezeigt wird, wenn der Benutzer das Element nicht ausfüllt. Wird kein
Argument angegeben, wird die Standard-Fehlermeldung verwendet.
$form->addText('name', 'Name:')
->setRequired('Bitte füllen Sie Ihren Namen aus.');
Regeln
Validierungsregeln fügen wir den Elementen über die Methode addRule() hinzu. Der erste Parameter ist die Regel,
der zweite die Fehlermeldung und der dritte das Argument der Validierungsregel.
$form->addPassword('password', 'Passwort:')
->addRule($form::MinLength, 'Das Passwort muss mindestens %d Zeichen lang sein', 8);
Die Validierungsregeln werden nur geprüft, wenn der Benutzer das Element ausgefüllt hat.
Nette bringt eine Reihe vordefinierter Regeln mit, deren Namen Konstanten der Klasse Nette\Forms\Form sind. Diese
Regeln können wir auf alle Elemente anwenden:
| Konstante | Beschreibung | Typ des Arguments |
|---|---|---|
Required |
Pflichtelement, Alias für setRequired() |
– |
Filled |
Pflichtelement, Alias für setRequired() |
– |
Blank |
das Element darf nicht ausgefüllt sein | – |
Equal |
der Wert muss dem Parameter entsprechen | mixed |
NotEqual |
der Wert darf dem Parameter nicht entsprechen | mixed |
IsIn |
der Wert muss eines der Elemente des Arrays sein | array |
IsNotIn |
der Wert darf keines der Elemente des Arrays sein | array |
Valid |
ist das Element korrekt ausgefüllt? (nur in addConditionOn()) | – |
Texteingaben
Für die Elemente addText(), addPassword(), addTextArea(), addEmail(),
addInteger(), addFloat() lassen sich außerdem einige der folgenden Regeln anwenden:
MinLength |
Mindestlänge des Textes | int |
MaxLength |
maximale Länge des Textes | int |
Length |
Länge im Bereich oder genaue Länge | Paar [int, int] oder int |
Email |
gültige E-Mail-Adresse | – |
URL |
absolute URL | – |
Pattern |
passt auf einen regulären Ausdruck | string |
PatternInsensitive |
wie Pattern, aber ohne Rücksicht auf Groß-/Kleinschreibung |
string |
Integer |
ganzzahliger Wert | – |
Numeric |
nicht negative ganze Zahl (nur Ziffern) | – |
Float |
Zahl | – |
Min |
Mindestwert eines numerischen Elements | int|float |
Max |
Höchstwert eines numerischen Elements | int|float |
Range |
Wert im Bereich | Paar [int|float, int|float] |
Die Validierungsregeln Integer und Float wandeln den Wert automatisch in eine ganze bzw. eine
Fließkommazahl um. Die Regel URL akzeptiert außerdem auch eine Adresse ohne Schema (etwa nette.org)
und ergänzt das Schema (https://nette.org). Der Ausdruck in Pattern und PatternInsensitive
muss für den gesamten Wert gelten, also so, als wäre er von den Zeichen ^ und $ umschlossen.
Anzahl der Elemente
Für die Elemente addMultiUpload(), addCheckboxList(), addMultiSelect() lassen sich
außerdem die folgenden Regeln verwenden, um die Anzahl der ausgewählten Elemente oder der hochgeladenen Dateien zu
begrenzen:
MinLength |
Mindestanzahl | int |
MaxLength |
Höchstanzahl | int |
Length |
Anzahl im Bereich oder genaue Anzahl | Paar [int, int] oder int |
Hochladen von Dateien
Für die Elemente addUpload(), addMultiUpload() lassen sich außerdem die folgenden Regeln
verwenden:
MaxFileSize |
maximale Größe der Datei in Bytes | int |
MimeType |
MIME-Typ, Wildcards erlaubt ('video/*') |
string|string[] |
Image |
Bild im Format JPEG, PNG, GIF, WebP, AVIF | – |
Pattern |
der Dateiname passt auf einen regulären Ausdruck | string |
PatternInsensitive |
wie Pattern, aber ohne Rücksicht auf Groß-/Kleinschreibung |
string |
MimeType und Image erfordern die PHP-Erweiterung fileinfo. Ob eine Datei oder ein Bild
vom verlangten Typ ist, wird anhand ihrer Signatur erkannt, und die Unversehrtheit der gesamten Datei wird nicht geprüft.
Ob ein Bild beschädigt ist, lässt sich zum Beispiel durch den Versuch feststellen, es zu laden.
Fehlermeldungen
Alle vordefinierten Regeln außer Pattern und PatternInsensitive haben eine Standard-Fehlermeldung,
sie lassen sich also weglassen. Wenn Sie jedoch alle Meldungen selbst angeben und auf Ihre Bedürfnisse zuschneiden, machen Sie
das Formular benutzerfreundlicher.
Die Standardmeldungen ändern Sie in der Konfiguration, indem Sie
die Texte im Array Nette\Forms\Validator::$messages anpassen, oder über einen Übersetzer.
Im Text der Fehlermeldungen lassen sich die folgenden Platzhalter verwenden:
%d |
wird der Reihe nach durch die Argumente der Regel ersetzt |
%n$d |
wird durch das n-te Argument der Regel ersetzt |
%label |
wird durch das Label des Elements ersetzt (ohne Doppelpunkt) |
%name |
wird durch den Namen des Elements ersetzt (etwa name) |
%value |
wird durch den vom Benutzer eingegebenen Wert ersetzt |
$form->addText('name', 'Name:')
->setRequired('Bitte füllen Sie %label aus');
$form->addInteger('id', 'ID:')
->addRule($form::Range, 'mindestens %d und höchstens %d', [5, 10]);
$form->addInteger('id', 'ID:')
->addRule($form::Range, 'höchstens %2$d und mindestens %1$d', [5, 10]);
Bedingungen
Neben Regeln lassen sich auch Bedingungen hinzufügen. Sie werden ähnlich geschrieben wie Regeln, statt addRule()
verwenden wir aber die Methode addCondition(), und natürlich geben wir keine Fehlermeldung an (die Bedingung
fragt nur):
$form->addPassword('password', 'Passwort:')
// wenn die Länge des Passworts nicht größer als 8 ist
->addCondition($form::MaxLength, 8)
// dann muss es eine Ziffer enthalten
->addRule($form::Pattern, 'Muss eine Ziffer enthalten', '.*[0-9].*');
Über addConditionOn() lässt sich die Bedingung an ein anderes als das aktuelle Element knüpfen. Der erste
Parameter ist ein Verweis auf das Element. In diesem Beispiel wird die E-Mail nur dann zum Pflichtfeld, wenn die Checkbox angehakt
ist (ihr Wert also true ist):
$form->addCheckbox('newsletters', 'Newsletter zusenden');
$form->addEmail('email', 'E-Mail:')
// wenn die Checkbox angehakt ist
->addConditionOn($form['newsletters'], $form::Equal, true)
// dann die E-Mail verlangen
->setRequired('Geben Sie Ihre E-Mail-Adresse ein');
Über elseCondition() und endCondition() lassen sich Bedingungen zu komplexen Strukturen
zusammensetzen:
$form->addText(/* ... */)
->addCondition(/* ... */) // wenn die erste Bedingung erfüllt ist
->addConditionOn(/* ... */) // und auch die zweite Bedingung an einem anderen Element
->addRule(/* ... */) // verlange diese Regel
->elseCondition() // wenn die zweite Bedingung nicht erfüllt ist
->addRule(/* ... */) // verlange diese Regeln
->addRule(/* ... */)
->endCondition() // wir kehren zur ersten Bedingung zurück
->addRule(/* ... */);
Das erste Argument von addCondition() kann auch ein boolescher Wert sein. Das ist nützlich, wenn die Entscheidung
beim Bauen des Formulars bereits feststeht, etwa um eine Regel nur unter bestimmten Umständen anzuwenden:
$form->addText('nickname')
->addCondition($isRequired) // ein Wert, der beim Bauen des Formulars bekannt ist
->setRequired();
In Nette lässt sich sehr leicht über die Methode toggle() auf der JavaScript-Seite darauf reagieren, ob eine
Bedingung erfüllt ist oder nicht, siehe Dynamisches JavaScript.
Verweis auf ein anderes Element
Als Argument einer Regel oder Bedingung können Sie auch ein anderes Formularelement übergeben. Die Regel verwendet dann den
Wert, den der Benutzer später im Browser eingibt. So lässt sich zum Beispiel dynamisch prüfen, dass das Element
password denselben String enthält wie das Element password_confirm:
$form->addPassword('password', 'Passwort');
$form->addPassword('password_confirm', 'Passwort bestätigen')
->addRule($form::Equal, 'Die Passwörter stimmen nicht überein', $form['password']);
Eigene Regeln und Bedingungen
Manchmal geraten wir in Situationen, in denen die eingebauten Validierungsregeln von Nette nicht ausreichen und wir die Daten des Benutzers auf eigene Weise prüfen müssen. In Nette ist das sehr einfach!
Den Methoden addRule() und addCondition() können Sie als ersten Parameter ein beliebiges Callback
übergeben. Das Callback nimmt als ersten Parameter das Element selbst entgegen und gibt einen booleschen Wert zurück, der
angibt, ob die Validierung erfolgreich war. Beim Hinzufügen einer Regel über addRule() lassen sich weitere
Argumente angeben, die dann als zweiter Parameter übergeben werden.
Eine eigene Sammlung von Validatoren lässt sich also als Klasse mit statischen Methoden schreiben:
class MyValidators
{
// prüft, ob der Wert durch das Argument teilbar ist
public static function validateDivisibility(BaseControl $input, $arg): bool
{
return $input->getValue() % $arg === 0;
}
public static function validateEmailDomain(BaseControl $input, $domain)
{
// weitere Validatoren
}
}
Die Verwendung ist dann sehr unkompliziert:
$form->addInteger('num')
->addRule(
[MyValidators::class, 'validateDivisibility'],
'Der Wert muss ein Vielfaches von %d sein',
8,
);
Eigene Validierungsregeln lassen sich auch zu JavaScript hinzufügen. Die Bedingung ist, dass die Regel eine statische Methode
sein muss. Ihr Name für den JavaScript-Validator entsteht, indem der Klassenname ohne Backslashes \, ein Unterstrich
_ und der Name der Methode aneinandergehängt werden. App\MyValidators::validateDivisibility wird zum
Beispiel als AppMyValidators_validateDivisibility geschrieben und dem Objekt Nette.validators
hinzugefügt:
Nette.validators['AppMyValidators_validateDivisibility'] = (elem, args, val) => {
return val % args === 0;
};
Event onValidate
Nachdem das Formular abgesendet wurde, läuft die Validierung, die die einzelnen über addRule() hinzugefügten
Regeln prüft, und anschließend wird das Event onValidate
ausgelöst. Sein Handler lässt sich für eine zusätzliche Validierung nutzen, typischerweise um die richtige Kombination von
Werten in mehreren Formularelementen zu prüfen.
Wird ein Fehler festgestellt, geben wir ihn über die Methode addError() an das Formular weiter. Sie lässt sich
entweder auf einem bestimmten Element oder direkt auf dem Formular aufrufen.
protected function createComponentSignInForm(): Form
{
$form = new Form;
// ...
$form->onValidate[] = $this->validateSignInForm(...);
return $form;
}
private function validateSignInForm(Form $form, \stdClass $data): void
{
if ($data->foo > 1 && $data->bar > 5) {
$form->addError('Diese Kombination ist nicht möglich.');
}
}
Fehler verarbeiten
In vielen Fällen entdecken wir einen Fehler erst beim Verarbeiten eines gültigen Formulars, etwa wenn wir einen neuen Eintrag
in die Datenbank schreiben und auf einen doppelten Schlüssel stoßen. In einem solchen Fall geben wir den Fehler wieder über die
Methode addError() an das Formular zurück. Sie lässt sich entweder auf einem bestimmten Element oder direkt auf dem
Formular aufrufen:
try {
$data = $form->getValues();
$this->user->login($data->username, $data->password);
$this->redirect('Home:');
} catch (Nette\Security\AuthenticationException $e) {
if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) {
$form->addError('Ungültiges Passwort.');
}
}
Wenn es möglich ist, empfehlen wir, den Fehler direkt dem Formularelement hinzuzufügen, denn beim Standard-Renderer wird er dann neben ihm angezeigt.
$form['date']->addError('Dieses Datum ist leider bereits vergeben.');
Sie können addError() wiederholt aufrufen, um einem Formular oder Element mehrere Fehlermeldungen zu übergeben.
Holen lassen sie sich über getErrors().
Beachten Sie, dass $form->getErrors() eine Zusammenfassung aller Fehlermeldungen zurückgibt, auch der
Meldungen, die direkt an einzelne Elemente übergeben wurden, nicht nur der direkt an das Formular übergebenen. Die Meldungen,
die nur dem Formular übergeben wurden, holen Sie über $form->getOwnErrors().
Eingaben verändern
Über die Methode addFilter() können wir den vom Benutzer eingegebenen Wert verändern. In diesem Beispiel dulden
und entfernen wir Leerzeichen in der Postleitzahl:
$form->addText('zip', 'Postleitzahl:')
->addFilter(function ($value) {
return str_replace(' ', '', $value); // die Leerzeichen aus der Postleitzahl entfernen
})
->addRule($form::Pattern, 'Die Postleitzahl besteht nicht aus fünf Ziffern', '\d{5}');
Der Filter reiht sich zwischen die Validierungsregeln und Bedingungen ein, die Reihenfolge der Methoden ist also wichtig:
Filter und Regel werden in derselben Reihenfolge aufgerufen, in der die Methoden addFilter() und
addRule() aufgeführt sind.
Validierung in JavaScript
Die Sprache zum Formulieren von Bedingungen und Regeln ist sehr mächtig. Alle Konstrukte funktionieren sowohl auf der
Serverseite als auch auf der Clientseite in JavaScript. Übertragen werden sie in den HTML-Attributen
data-nette-rules als JSON. Die Validierung selbst übernimmt ein Skript, das das Event submit des
Formulars abfängt, die einzelnen Elemente durchläuft und die jeweilige Validierung ausführt.
Dieses Skript heißt netteForms.js und steht aus mehreren möglichen Quellen zur Verfügung:
Sie können das Skript direkt aus einem CDN in die HTML-Seite einbinden:
<script src="https://unpkg.com/nette-forms@3"></script>
Oder es lokal in das öffentliche Verzeichnis Ihres Projekts kopieren (etwa aus
vendor/nette/forms/src/assets/netteForms.min.js):
<script src="/path/to/netteForms.min.js"></script>
Oder es über npm installieren:
npm install nette-forms
Und es dann laden und starten:
import netteForms from 'nette-forms';
netteForms.initOnLoad();
Alternativ lässt es sich direkt aus dem Verzeichnis vendor laden:
import netteForms from '../path/to/vendor/nette/forms/src/assets/netteForms.js';
netteForms.initOnLoad();
Die Validierung auf der Clientseite lässt sich vollständig abschalten, indem Sie dem Formular das Attribut
novalidate geben. Das Skript netteForms.js überspringt dann die Validierung beim Absenden, sodass sie
nur auf dem Server stattfindet:
$form->setHtmlAttribute('novalidate');
Dynamisches JavaScript
Sie möchten die Felder für die Adresse nur dann anzeigen, wenn der Benutzer wählt, dass die Ware per Post zugestellt werden
soll? Kein Problem. Der Schlüssel ist das Methodenpaar addCondition() & toggle():
$form->addCheckbox('send_it')
->addCondition($form::Equal, true)
->toggle('#address-container');
Dieser Code besagt, dass bei erfüllter Bedingung (also wenn die Checkbox angehakt ist) das HTML-Element
#address-container sichtbar ist und umgekehrt. Wir setzen die Formularelemente mit der Adresse des Empfängers also
in einen Container mit dieser ID, und sie verstecken oder zeigen sich beim Klick auf die Checkbox. Dafür sorgt das Skript
netteForms.js.
Der Methode toggle() lässt sich als Argument ein beliebiger Selektor übergeben. Aus historischen Gründen gilt
ein String, der mit einem Buchstaben, einer Ziffer oder einem Unterstrich beginnt und nur Buchstaben, Ziffern, Unterstriche,
Bindestriche, Punkte und Doppelpunkte enthält, als ID eines Elements, so als stünde davor das Zeichen #. Der
zweite, optionale Parameter erlaubt es, das Verhalten umzukehren; verwendeten wir zum Beispiel
toggle('#address-container', false), würde das Element nur dann angezeigt, wenn die Checkbox nicht
angehakt ist.
Die Standardimplementierung in JavaScript ändert die Property hidden der Elemente. Wir können das Verhalten aber
leicht ändern, etwa um eine Animation zu ergänzen. Überschreiben Sie dazu einfach die Methode Nette.toggle in
JavaScript durch eine eigene Lösung:
Nette.toggle = (selector, visible, srcElement, event) => {
document.querySelectorAll(selector).forEach((el) => {
// 'el' je nach dem Wert von 'visible' verstecken oder anzeigen
});
};
Validierung abschalten
Manchmal kann es nützlich sein, die Validierung abzuschalten. Wenn das Drücken eines Absende-Buttons keine Validierung
auslösen soll (was sich für die Buttons Abbrechen oder Vorschau anbietet), schalten wir sie über die Methode
$submit->setValidationScope([]) ab. Soll sie nur teilweise validieren, können wir angeben, welche Felder oder
Formular-Container validiert werden sollen.
$form->addText('name')
->setRequired();
$details = $form->addContainer('details');
$details->addInteger('age')
->setRequired('age');
$details->addInteger('age2')
->setRequired('age2');
$form->addSubmit('send1'); // validiert das gesamte Formular
$form->addSubmit('send2')
->setValidationScope([]); // validiert nichts
$form->addSubmit('send3')
->setValidationScope([$form['name']]); // validiert nur das Element 'name'
$form->addSubmit('send4')
->setValidationScope([$form['details']['age']]); // validiert nur das Element 'age'
$form->addSubmit('send5')
->setValidationScope([$form['details']]); // validiert den Container 'details'
setValidationScope hat keinen Einfluss auf das Event onValidate des Formulars,
das immer aufgerufen wird. Das Event onValidate eines Containers wird nur dann ausgelöst, wenn dieser Container für
die teilweise Validierung markiert ist.
Die teilweise Validierung wirkt sich auch auf die Werte aus, die getValues() zurückgibt: Das Ergebnis enthält
nur die Werte der Elemente, die im Gültigkeitsbereich der Validierung liegen. Die Werte der Elemente außerhalb dieses Bereichs
entfallen.