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.

Version: 4.x