Software & Praxis

Formulare im Browser prüfen und auf dem Server absichern

Native Formularprüfung und fachliche Serverregeln an einem tatsächlich ausgeführten PHP-Beispiel verstehen.

Zwei hochwertige Prüflupen mit den Beschriftungen Browser und Server stehen auf einem Formular mit der technischen Projektkennung AZ-2026. Sie veranschaulichen getrennte Prüfungen derselben Eingaben.

Ein Formular braucht zwei Arten von Prüfung: Der Browser hilft Menschen beim richtigen Ausfüllen und der Server entscheidet unabhängig über die angenommenen Daten. Beide Ebenen sollten denselben fachlichen Auftrag beschreiben. Sie dürfen unterschiedliche technische Mittel verwenden. Eine Eingabe wird aber nicht allein dadurch vertrauenswürdig, dass sie zuvor ein Pflichtfeld im Browser durchlaufen hat.

Dieser Beitrag zeigt das an einem kleinen Projektbriefing mit Projektkennung, Projektart und geplanter Anzahl von Ansichten. Das Beispiel erzeugt aus gültigen Angaben eine formatierte Zusammenstellung. Es verschickt keine Nachricht und speichert keinen Auftrag. Dadurch können wir Eingaberegeln, Fehlerrückmeldungen und das Verhalten ohne JavaScript untersuchen, ohne eine tatsächliche Kundenaktion vorzutäuschen. Beide Sprachfassungen wurden mit PHP und einem echten Browser geprüft.

Den fachlichen Vertrag zuerst aufschreiben

Vor dem ersten HTML-Attribut muss feststehen, welche Angaben erforderlich sind. Die Projektkennung ist in diesem Lernbeispiel eine technische Referenz und kein Personenname. Sie besteht aus drei bis sechzehn ASCII-Zeichen. Am Anfang steht ein Großbuchstabe, danach sind Großbuchstaben, Ziffern und Bindestriche erlaubt. Diese Einschränkung wäre für einen echten Namen ungeeignet. Hier gehört sie ausdrücklich zur Bedeutung des technischen Felds.

Die Projektart besitzt genau zwei erlaubte Werte: Webseite und Webanwendung. Die Zahl der geplanten Ansichten liegt zwischen eins und fünfzig. Für diese Zahl verwenden wir zusätzlich eine kanonische Schreibweise aus Dezimalziffern ohne führende Null oder Exponent. Das ist eine bewusst gewählte Beispielregel. Andere Anwendungen können wissenschaftliche Zahlenschreibweise zulassen oder Werte normalisieren. Entscheidend ist, dass der gewünschte Umgang vorher festgelegt wird.

FeldFachliche RegelBeispiele
Projektkennung3 bis 16 Zeichen im beschriebenen technischen FormatAZ-2026 zulässig, az-2026 nicht zulässig
ProjektartEiner von zwei bekannten Wertenwebsite oder app als technische Werte
Geplante AnsichtenKanonische ganze Zahl von 1 bis 5010 zulässig, 01 und 1e1 nicht zulässig

Mit diesem Vertrag lassen sich Fehler später präzise zuordnen. Eine fehlende Projektart ist ein anderes Problem als eine unbekannte zusätzliche Option. Ein leerer Zahlenwert ist etwas anderes als eine Zahl oberhalb der Obergrenze. Trotzdem müssen Nutzer nicht die interne Fehlerklassifikation lernen. Die Rückmeldung sollte jeweils erklären, wie die konkrete Eingabe korrigiert werden kann.

Die Hinweise benennen außerdem bewusst keine allgemeine Kategorie „unsichere Zeichen“. Entscheidend ist die Passung zur Aufgabe. Eine Kleinbuchstabenkennung ist hier unpassend, aber nicht allein deshalb gefährlich. Diese Wortwahl hilft dabei, eine verständliche Fachregel zu erklären, ohne normale menschliche Eingaben unnötig als Angriff darzustellen.

Mit nativen Formularelementen beginnen

Die Oberfläche verwendet ein echtes Formular mit POST-Methode und eine Schaltfläche des Typs submit. Jedes Eingabefeld besitzt eine zugeordnete sichtbare Beschriftung. Für die technische Kennung kommen required, minlength, maxlength und pattern zum Einsatz. Für die Ansichtenanzahl werden type number sowie min, max und step verwendet. Die Auswahlliste erhält einen leeren Anfangswert und required.

Diese Grundlage funktioniert bereits ohne eigenes JavaScript. Der Browser kann fehlende Angaben, eine unpassende Kennung und viele Zahlenfehler vor dem Absenden anzeigen. Die MDN-Erläuterung zur Constraint Validation beschreibt die verfügbaren Prüfmechanismen. Ihre Aufgabe im Beispiel ist eine schnellere Rückmeldung. Welche Darstellung eine native Fehlermeldung erhält, hängt weiterhin vom verwendeten Browser und seiner Sprache ab.

Die sichtbaren Hinweise stehen direkt beim jeweiligen Feld. Ein Platzhalter allein wäre kein geeigneter Ersatz, weil er beim Tippen verschwinden kann. Auch die Auswahlliste erhält genügend Abstand zwischen ihrem Text und dem Pfeil. Solche Details wirken klein, beeinflussen aber die Nutzbarkeit auf schmalen Geräten. Eine formal richtige Regel hilft wenig, wenn ihr Hinweis abgeschnitten wird oder der Fokus kaum erkennbar ist.

Ein Muster nicht mit freier Textsprache verwechseln

Das Muster der Projektkennung ist absichtlich eng. Es beschreibt einen technischen Bezeichner und keine allgemeine Vorstellung davon, welche Schriftzeichen sicher seien. Würde ein Formular stattdessen Kundennamen abfragen, wären andere Regeln erforderlich. Apostrophe, Umlaute und viele weitere Schriftzeichen sind in solchen Namen normale Daten. Sie pauschal zu entfernen, könnte die fachliche Bedeutung verfälschen.

Die Dokumentation zum pattern-Attribut erläutert die Prüfung des gesamten Werts und die Bedeutung verständlicher Musterhinweise. Im Beispiel wird der Bindestrich innerhalb der HTML-Zeichenklasse ausdrücklich maskiert. Die Browserregel und der PHP-Ausdruck sind nicht einfach derselbe beliebige Text mit anderem Dateinamen. Beide müssen unter ihrer jeweiligen Syntax tatsächlich das gewünschte Format erkennen.

Für die verwendeten ASCII-Kennungen fallen verschiedene gebräuchliche Zeichenzählungen zusammen. Das erleichtert diesen Versuch, ist aber keine Aussage über sämtliche Unicode-Texte. Bei freien Texteingaben müssen etwa Byteumfang, Zeichenanzahl und sichtbare Zeichenfolgen getrennt betrachtet werden. Eine spätere Erweiterung um längere Namen oder Beschreibungen benötigt deshalb eine eigene begründete Längenregel.

Eine bewusst strengere Zahlenschreibweise ergänzen

Ein numerisches HTML-Feld versteht mehr als eine Folge einfacher Dezimalziffern. Im tatsächlichen Browsertest konnte die Schreibweise 1e1 ohne Zusatz-JavaScript die nativen Bedingungen erfüllen. Ihr Zahlenwert entspricht zehn. Unser fachlicher Beispielvertrag verlangt jedoch ausdrücklich die Schreibweise 10. Deshalb wird die gewünschte kanonische Darstellung zusätzlich geprüft. Es handelt sich nicht um einen allgemeinen Fehler des Browsers.

Die ergänzende Funktion setzt mit setCustomValidity eine passende Meldung, wenn eine nichtleere Schreibweise nicht dem vereinbarten Format entspricht. Ein leerer Wert wird weiterhin über required behandelt. Bei jeder Eingabe wird die benutzerdefinierte Meldung neu berechnet. Sobald die Zahl passt, wird sie durch einen leeren Text entfernt. Andernfalls könnte eine längst korrigierte Eingabe irrtümlich ungültig bleiben.

Diese Erweiterung verändert den normalen Versandweg des Formulars nicht. Es gibt keinen eigenen fetch-Aufruf und keinen zusätzlichen zweiten Absendehandler. Der Browser sendet weiterhin ein reguläres Formular an dieselbe PHP-Datei. Das hält die Zuständigkeit im Beispiel überschaubar. Wenn JavaScript ausfällt oder bewusst deaktiviert ist, prüft der Server die vollständige Regel trotzdem und erklärt eine abgelehnte Schreibweise.

Warum die Serverprüfung unabhängig bleibt

Die HTML-Spezifikation zur Formularprüfung stellt ausdrücklich klar, dass Clientprüfungen keinen Sicherheitsmechanismus bilden. Eine HTTP-Anfrage kann aus einem anderen Programm stammen. Attribute lassen sich ändern und Skripte können Prüfungen umgehen. Der Server kennt deshalb nicht automatisch die Vorgeschichte einer angekommenen Zeichenfolge.

Im PHP-Beispiel wird zuerst die Anfrageart betrachtet. GET und HEAD liefern die Formularansicht, POST wird verarbeitet und andere Methoden werden mit 405 beantwortet. Anschließend werden die drei vorgesehenen Felder einzeln gelesen. Jedes muss als Zeichenfolge vorliegen und vor der weiteren Prüfung eine kleine Byteobergrenze einhalten. Ein übermitteltes Array darf nicht versehentlich in eine normale Feldzeichenfolge umgewandelt werden.

Die Werte werden danach ausdrücklich gegen ihren fachlichen Vertrag geprüft. Die Projektart verwendet eine Positivliste mit strengem Vergleich. Kennung und Anzahl werden über vollständige Muster begrenzt. Zusätzliche unbekannte Felder führen in diesem Beispiel zu einer allgemeinen Fehlerrückmeldung. Es gibt keine Massenübernahme eines fremden POST-Objekts in ein späteres Datenmodell und keine aus dem Formular abgeleitete Administratorrolle.

Vollständiges PHP-Lernbeispiel

Die folgende Datei enthält Formular, Serverprüfung und kleine Browserergänzung zusammen. Die Übersetzungen sind im Beispiel enthalten, damit beide tatsächlich geprüften Fassungen nachvollziehbar bleiben. Zum Ausführen wird PHP benötigt. Die Datei enthält keine Verbindung zu einem Maildienst und keine Datenbank. Das sichtbare Ergebnis ist ausschließlich das aus den validierten Werten erzeugte Projektbriefing.

<?php
declare(strict_types=1);
$locale = 'de';
$translations = [
 'de' => ['title'=>'Projektbriefing vorbereiten','reference'=>'Projektkennung','reference_help'=>'3 bis 16 Zeichen: Großbuchstabe zuerst, danach Großbuchstaben, Ziffern oder Bindestriche.','service'=>'Projektart','choose'=>'Bitte wählen','website'=>'Webseite','app'=>'Webanwendung','units'=>'Geplante Ansichten','units_help'=>'Ganze Zahl von 1 bis 50, ohne Exponent und führende Null.','submit'=>'Briefing erstellen','errors'=>'Bitte prüfen Sie Ihre Angaben','reference_error'=>'Geben Sie eine Projektkennung im beschriebenen Format ein.','service_error'=>'Wählen Sie eine der angebotenen Projektarten.','units_error'=>'Geben Sie eine ganze Zahl von 1 bis 50 ohne Exponent oder führende Null ein.','form_error'=>'Bitte verwenden Sie die vorgesehenen Eingabefelder.','result'=>'Projektbriefing','method'=>'Diese Anfrageart wird nicht unterstützt.'],
 'en' => ['title'=>'Prepare a project brief','reference'=>'Project reference','reference_help'=>'3 to 16 characters: start with an uppercase letter, followed by uppercase letters, digits or hyphens.','service'=>'Project type','choose'=>'Please choose','website'=>'Website','app'=>'Web application','units'=>'Planned views','units_help'=>'A whole number from 1 to 50, without an exponent or leading zero.','submit'=>'Create brief','errors'=>'Please check your entries','reference_error'=>'Enter a project reference in the described format.','service_error'=>'Choose one of the offered project types.','units_error'=>'Enter a whole number from 1 to 50 without an exponent or leading zero.','form_error'=>'Please use the provided fields.','result'=>'Project brief','method'=>'This request method is not supported.']
];
$t = $translations[$locale];
header('Content-Type: text/html; charset=UTF-8');
header('Cache-Control: no-store');
function h(string $value): string { return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
if (!in_array($method, ['GET','HEAD','POST'], true)) {
 http_response_code(405); header('Allow: GET, HEAD, POST'); echo h($t['method']); exit;
}
$errors = []; $values = ['reference'=>'','service'=>'','units'=>'5']; $result = null;
if ($method === 'POST') {
 foreach ($values as $name => $_) {
  $raw = $_POST[$name] ?? null;
  if (!is_string($raw) || strlen($raw) > 128) {
   $errors[$name] = $t[$name.'_error']; $values[$name] = '';
  } else { $values[$name] = $raw; }
 }
 if (array_diff(array_keys($_POST), array_keys($values))) { $errors['form'] = $t['form_error']; }
 if (!preg_match('/\A[A-Z][A-Z0-9-]{2,15}\z/', $values['reference'])) { $errors['reference'] = $t['reference_error']; }
 if (!in_array($values['service'], ['website','app'], true)) { $errors['service'] = $t['service_error']; }
 if (!preg_match('/\A(?:[1-9]|[1-4][0-9]|50)\z/', $values['units'])) { $errors['units'] = $t['units_error']; }
 if ($errors) { http_response_code(422); }
 else { $result = ['reference'=>$values['reference'],'service'=>$t[$values['service']],'units'=>(int)$values['units']]; }
}
function described(string $name, array $errors): string { return $name.'-help'.(isset($errors[$name]) ? ' '.$name.'-error' : ''); }
function invalid(string $name, array $errors): string { return isset($errors[$name]) ? ' aria-invalid="true"' : ''; }
?>
<!doctype html><html lang="<?= h($locale) ?>"><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= h($t['title']) ?></title><style>
*{box-sizing:border-box}body{margin:0;background:#f6f1e8;color:#222129;font:18px/1.5 Arial,sans-serif}main{max-width:760px;margin:auto;padding:30px 24px}h1{font-size:32px;line-height:1.2}label{display:block;font-weight:bold;margin-top:22px}input,select,button{font:inherit;width:100%;height:52px;border:1px solid #777;border-radius:7px;padding:10px 14px;background:white;color:#222}select{appearance:none;padding-right:48px;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M3 5l5 5 5-5' fill='none' stroke='%23222' stroke-width='2'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 16px center}button{margin-top:26px;background:#bd162a;color:white;border-color:#bd162a;font-weight:bold;height:auto;min-height:52px;cursor:pointer}:focus-visible{outline:3px solid #24242c;outline-offset:4px}.help{font-size:15px;margin:8px 0}.error{color:#9e1024;font-weight:bold;margin:8px 0}[aria-invalid=true]{border:2px solid #a71227}.summary{padding:18px 22px;border:2px solid #b51328;background:#fff8f7;border-radius:8px}.summary h2{font-size:23px}.summary a{color:#8e0c1d}.result{padding:20px 25px;background:white;border-left:6px solid #bf162a;margin-top:30px}.result dt{font-weight:bold}.result dd{margin:0 0 12px}
</style><main><h1><?= h($t['title']) ?></h1>
<?php if ($errors): ?><section id="errors" class="summary" tabindex="-1" role="alert"><h2><?= h($t['errors']) ?></h2><ul><?php foreach ($errors as $name=>$message): ?><li><a href="#<?= h($name === 'form' ? 'brief' : $name) ?>"><?= h($message) ?></a></li><?php endforeach ?></ul></section><?php endif ?>
<form id="brief" method="post">
<label for="reference"><?= h($t['reference']) ?></label>
<input id="reference" name="reference" type="text" required minlength="3" maxlength="16" pattern="[A-Z][A-Z0-9\-]{2,15}" value="<?= h($values['reference']) ?>" aria-describedby="<?= h(described('reference',$errors)) ?>"<?= invalid('reference',$errors) ?>>
<p class="help" id="reference-help"><?= h($t['reference_help']) ?></p><?php if (isset($errors['reference'])): ?><p class="error" id="reference-error"><?= h($errors['reference']) ?></p><?php endif ?>
<label for="service"><?= h($t['service']) ?></label>
<select id="service" name="service" required aria-describedby="service-help<?= isset($errors['service']) ? ' service-error' : '' ?>"<?= invalid('service',$errors) ?>>
<option value=""><?= h($t['choose']) ?></option><?php foreach (['website','app'] as $service): ?><option value="<?= h($service) ?>"<?= $values['service']===$service ? ' selected' : '' ?>><?= h($t[$service]) ?></option><?php endforeach ?></select>
<p class="help" id="service-help"><?= h($t['service_error']) ?></p><?php if (isset($errors['service'])): ?><p class="error" id="service-error"><?= h($errors['service']) ?></p><?php endif ?>
<label for="units"><?= h($t['units']) ?></label>
<input id="units" name="units" type="number" min="1" max="50" step="1" required value="<?= h($values['units']) ?>" aria-describedby="<?= h(described('units',$errors)) ?>"<?= invalid('units',$errors) ?>>
<p class="help" id="units-help"><?= h($t['units_help']) ?></p><?php if (isset($errors['units'])): ?><p class="error" id="units-error"><?= h($errors['units']) ?></p><?php endif ?>
<button type="submit"><?= h($t['submit']) ?></button></form>
<?php if ($result): ?><section id="result" class="result" tabindex="-1" role="status"><h2><?= h($t['result']) ?></h2><dl><?php foreach ($result as $name=>$value): ?><dt><?= h($t[$name]) ?></dt><dd><?= h((string)$value) ?></dd><?php endforeach ?></dl></section><?php endif ?>
<script>
const units = document.querySelector('#units');
const message = <?= json_encode($t['units_error'], JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOT|JSON_THROW_ON_ERROR) ?>;
function validateUnits() {
 units.setCustomValidity(units.value === '' || /^(?:[1-9]|[1-4][0-9]|50)$/.test(units.value) ? '' : message);
}
units.addEventListener('input', validateUnits);
validateUnits();
document.querySelector('#errors, #result')?.focus();
</script></main></html>

Nach erfolgreichen Prüfungen werden die Werte nicht nochmals aus ungeprüften Nebenfeldern zusammengesucht. Das Ergebnisobjekt entsteht gezielt aus den angenommenen Angaben. Die Ansichtenanzahl wird erst nach der Formatprüfung in eine Ganzzahl umgewandelt. Eine vorschnelle Umwandlung hätte verschiedene unerlaubte Schreibweisen auf einen scheinbar passenden Zahlenwert reduzieren können. Die Reihenfolge gehört deshalb zum Vertrag des Beispiels.

Validierung und sichere Ausgabe getrennt halten

Auch abgelehnte Eingaben können in einem Formular erneut angezeigt werden. Die Ausgabe benötigt daher ihre eigene passende Behandlung. Der Helfer h verwendet htmlspecialchars mit ENT_QUOTES, ENT_SUBSTITUTE und ausdrücklich UTF-8. So werden Zeichen für die hier verwendeten HTML-Text- und Attributkontexte behandelt. Die PHP-Dokumentation beschreibt diese Optionen einschließlich des Umgangs mit ungültigen Zeichenfolgen.

Das macht aus der Funktion keinen universellen Schutz für jede Art von Ausgabe. Eine SQL-Abfrage, ein JavaScript-Ausdruck und ein beliebiger URL-Kontext besitzen andere Regeln. Im Beispiel wird die feste übersetzte JavaScript-Meldung deshalb mit json_encode und entsprechenden HEX-Optionen eingefügt. Die beiden Ausgabewege werden nach ihrem tatsächlichen Ziel gewählt und nicht unter dem pauschalen Wort „bereinigt“ zusammengefasst.

Die OWASP-Empfehlungen zur Eingabeprüfung unterscheiden feldbezogene Annahmeregeln von weiter erforderlichen Schutzmaßnahmen. Für unser Formular bedeutet das: Ein gültiger Projektbezeichner ist ein gültiger Bezeichner. Er ist dadurch weder ein Berechtigungsnachweis noch eine Einwilligung zu beliebigen späteren Aktionen. Diese Unterscheidung bleibt auch dann wichtig, wenn die Oberfläche sehr sorgfältig gestaltet ist.

Tatsächliche Fehleransicht des Projektbriefings nach umgangener Browserprüfung. Die Kennung bad und die Anzahl 99 werden vom Server abgelehnt. Erläuterungen verbinden Absendeweg, Feldregeln, Fehlermeldungszuordnung und den zusätzlich geprüften Fall 1e1.
Die vollständige Fehleransicht stammt aus dem ausgeführten Browserversuch. Nach dem direkten Absenden prüfen die PHP-Regeln Kennung und Anzahl unabhängig. Der Fokus folgt hier dem Fehlerlink zur Kennung. Der zusätzliche Fall 1e1 zeigt die bewusst engere Serverregel bei deaktiviertem JavaScript. Die Darstellung erklärt konkrete Beobachtungen und belegt keine umfassende Prüfung aller Hilfstechnologien.

Fehler an der richtigen Stelle erklären

Ein Serverfehler führt hier nicht zu einer leeren neuen Formularseite. Über dem Formular erscheint eine Zusammenfassung mit Links zu den betroffenen Feldern. Direkt bei jedem betroffenen Feld steht zusätzlich eine konkrete Meldung. Die Eingaben bleiben im erlaubten Umfang erhalten. So muss eine Person nicht alle bereits passenden Angaben erneut eingeben, nur weil eine Zahl korrigiert werden muss.

Die fehlerhaften Steuerelemente erhalten aria-invalid und eine Verbindung zu ihrem Meldungstext über aria-describedby. Bestehende allgemeine Hinweise bleiben Teil dieser Zuordnung. Fehler werden nicht ausschließlich durch eine rote Farbe vermittelt. Die WAI-Anleitung zu Formularrückmeldungen beschreibt die Kombination aus zusammenfassender und feldbezogener Rückmeldung. Im Beispiel wird dieser Zusammenhang auch im tatsächlichen DOM geprüft.

Nach einer Serverantwort richtet die kleine Browserergänzung den Fokus auf die Fehlerzusammenfassung beziehungsweise das erzeugte Briefing. Ohne JavaScript steht die Zusammenfassung weiterhin vor dem Formular in der natürlichen Lesereihenfolge. Das ist eine sinnvolle Rückfallebene, aber kein Nachweis einer vollständigen Prüfung mit allen Hilfstechnologien. Die ausgeführten Versuche enthalten keine eigene Screenreader-Nutzerstudie.

Den normalen Absendeweg nicht versehentlich umgehen

Bei einer späteren Erweiterung kann ein Skript das Formular auslösen müssen. Dabei sind submit und requestSubmit nicht austauschbar. Die MDN-Dokumentation zu requestSubmit erklärt den Unterschied: requestSubmit folgt dem Weg einer Absendehandlung einschließlich Prüfung. Der direkte submit-Aufruf löst diese Browserprüfung dagegen nicht aus.

Genau diese Umgehung wurde im Test bewusst verwendet. Das Skript setzte eine unpassende Kennung und eine zu große Anzahl ein und rief den direkten Absendeweg auf. Der Server antwortete mit den vorgesehenen Fehlermeldungen. Ein solcher Versuch soll nicht beweisen, dass alle Angriffe abgewehrt wurden. Er prüft die konkrete wichtige Eigenschaft, dass der Server für seine Annahmeregeln keine vorausgegangene Browserkontrolle benötigt.

Ohne JavaScript sinnvoll weiterarbeiten

Für die Prüfung wurde ein eigener Browserkontext mit deaktiviertem JavaScript geöffnet. Die nativen Pflicht- und Musterbedingungen blieben wirksam. Gültige Angaben führten weiterhin zum Projektbriefing. Bei der weiter gefassten nativen Zahlenschreibweise 1e1 kam eine echte Serverantwort mit Status 422 und einer zugeordneten Zahlenmeldung zurück. Anschließend ließ sich der Wert korrigieren und das Briefing regulär erzeugen.

Damit ist nicht bewiesen, dass jede Funktion jeder denkbaren Anwendung ohne JavaScript identisch aussehen muss. Unser konkreter Auftrag benötigt keine ausschließlich clientseitige Fähigkeit. Es ist deshalb sinnvoll, den grundlegenden Vorgang unabhängig vom Zusatzskript ausführbar zu halten. Eine andere Anwendung kann andere Voraussetzungen haben. Diese sollten dann aus ihrem tatsächlichen Nutzen folgen und nicht aus einer versehentlich doppelt gebauten Formularlogik.

Was die ausgeführten Tests belegen

Die beiden Fassungen bestanden 228 einzelne Prüfungen in 56 dokumentierten Fällen. Verwendet wurden PHP 8.4.27 und ein echter Edge-Browser. Die Versuche umfassten direkte HTTP-Anfragen und tatsächliche Formularhandlungen. Geprüft wurden unter anderem Unter- und Obergrenzen, fehlende Angaben, Arrays anstelle einzelner Werte, unbekannte Optionen, zusätzliche Felder und die Rückmeldung nach umgangener Browserprüfung.

SituationBeobachtetes ErgebnisVerantwortliche Ebene
Leere PflichtkennungNormale Absendehandlung wird vor POST blockiertNative Browserprüfung
1e1 mit ZusatzskriptBenutzerdefinierte Meldung verhindert das AbsendenBrowserergänzung
1e1 ohne JavaScript422 und konkrete ZahlenmeldungServerprüfung
Direkt gesendetes ArrayKeine erfolgreiche BriefingerstellungServerprüfung
Korrigierte gültige WerteFormatierte Zusammenstellung erscheintGeprüfter Serverablauf

Zusätzlich wurden die Verbindungen zwischen Fehlern und Eingaben, der tatsächliche Fokuswechsel und die Darstellung bei 390 Pixel Ansichtsbreite kontrolliert. In der schmalen Darstellung trat kein horizontaler Seitenüberlauf auf. Die Dropdownpfeile besitzen einen sichtbaren Innenabstand. Diese Befunde beschreiben die untersuchte Oberfläche. Sie ersetzen keine systematische Prüfung aller Browser, Bedienhilfen und später hinzukommenden Formularvarianten.

Vor echten Änderungen zusätzliche Grenzen prüfen

Das Lernformular formatiert nur Daten. Es besitzt keinen angemeldeten Nutzer und verändert keinen bestehenden Geschäftsdatensatz. Sobald ein reales Formular etwas speichert oder eine berechtigte Handlung auslöst, kommen weitere Fragen hinzu. Dazu gehören die Zuständigkeit des Nutzers, Schutz gegen ungewollt ausgelöste Aktionen, Wiederholungen und ein nachvollziehbarer Erfolg nach der tatsächlichen Ausführung. Eine bestandene Feldprüfung beantwortet diese Fragen nicht.

Ebenso liegen Grenzen für Anfragegröße und Missbrauch nicht vollständig im gezeigten PHP-Feldhelfer. Der Webserver und der Eingangsparser haben die Anfrage bereits verarbeitet, bevor die Feldlänge betrachtet wird. Für Dateiübertragungen, große Formulare oder öffentlich stark beanspruchte Endpunkte muss der vollständige Eingangsweg angemessen begrenzt werden. Ein kleines reguläres Muster ist kein Ersatz für ein durchdachtes Betriebsmodell.

Spätere Änderungen nachvollziehbar halten

Der fachliche Feldvertrag sollte auch bei einer Erweiterung sichtbar bleiben. Wenn die Obergrenze von fünfzig auf einen anderen Wert steigt, betrifft das Hilfetext, HTML-Attribut, ergänzenden Ausdruck, PHP-Regel und Grenztests. Eine Änderung nur an einer Stelle erzeugt vermeidbare Widersprüche. In diesem kleinen Beispiel lassen sich diese Stellen gemeinsam betrachten. Größere Anwendungen benötigen dafür eine ausdrücklich geregelte Pflege der zusammengehörenden Definitionen.

Übersetzungen erfordern dieselbe Sorgfalt. Die angenommenen technischen Werte können gleich bleiben, während die sichtbaren Bezeichnungen wechseln. Ein übersetzter Oberflächentext sollte nicht stillschweigend zu einem neuen Backendwert werden. Die Fehlermeldung muss weiterhin die tatsächlich durchgesetzte Regel beschreiben. Zusätzlich wird die gerenderte Ansicht betrachtet, weil auch eine sprachlich richtige Erklärung in der verfügbaren Breite ungünstig umbrechen kann.

Schließlich wird eine fachliche Ablehnung von einem fehlgeschlagenen Vorgang unterschieden. Unsere Antwort mit Status 422 folgt auf unpassende Eingaben und enthält kein fertiges Briefing. Ein späterer Speicherfehler nach gültigen Angaben wäre ein anderes Ereignis. Rückmeldung und Wiederholungsstrategie müssten dazu passen. Die pauschale Aufforderung, Eingabefelder zu prüfen, kann sonst dazu führen, dass Menschen richtige Daten ändern, obwohl die eigentliche Ursache an einer anderen Stelle liegt.

Grenzen des Beispielauftrags sichtbar lassen

Eine angenommene Projektkennung ist noch kein Beweis, dass ein Projekt mit dieser Kennung existiert. Dafür müsste ein zuständiger Datenbestand befragt werden. Auch die passende Projektart bedeutet nicht, dass die auswählende Person zu einem echten Projekt Zugriff besitzt. Der Beispielvertrag prüft lediglich das Format der übermittelten Angaben. Verwechslungen zwischen Format, Existenz und Berechtigung sind besonders dann problematisch, wenn aus einem kleinen Formular später eine Kundenverwaltung wird.

Ebenso entsteht aus fünfzig geplanten Ansichten keine verbindliche Leistungsbeschreibung. Die Zahl kann für ein erstes Briefing genügen und für eine Vertragsentscheidung trotzdem zu ungenau sein. Fachliche Regeln werden deshalb an der tatsächlichen späteren Verwendung ausgerichtet. Es hilft, für jedes Ergebnis festzuhalten, wofür es verwendet werden darf und welche zusätzlichen Fragen offenbleiben. Das verhindert, dass ein technisch erfolgreich verarbeitetes Formular einen weitergehenden Geschäftsabschluss zu bestätigen scheint.

Auch der absichtlich stateless aufgebaute Ablauf hat eine klare Grenze. Beim erneuten Absenden wird lediglich dieselbe Zusammenstellung noch einmal erzeugt. Bei einem echten Buchungs- oder Versandvorgang könnte eine Wiederholung dagegen eine zweite Handlung auslösen. Solche Folgen müssen beim Ausbau des Formulars eigens betrachtet werden. Sie lassen sich nicht dadurch lösen, dass eine Schaltfläche nach dem ersten Klick nur optisch deaktiviert wird.

Checkliste für den nächsten Formularschritt

  • Fachliche Bedeutung und erlaubte Darstellung jedes Felds sind beschrieben.
  • Technische Kennungen werden nicht mit Namen oder freien Texten verwechselt.
  • Native Beschriftungen, Hinweise und Pflichtbedingungen sind vorhanden.
  • Zusätzliche Browsermeldungen werden nach einer Korrektur zurückgesetzt.
  • Der Server prüft Typ, Umfang und fachliche Werte unabhängig.
  • Arrays und unbekannte Auswahlwerte erzeugen keinen Erfolg.
  • Angenommene Daten werden gezielt in das Ergebnis übernommen.
  • Die Ausgabe wird passend zu ihrem tatsächlichen Kontext behandelt.
  • Fehler sind zusammenfassend und direkt am Feld auffindbar.
  • JavaScript-Ausfall und umgangene Browserprüfung wurden tatsächlich geprüft.
  • Eine Erfolgsmeldung beschreibt nur eine wirklich ausgeführte Handlung.
  • Für spätere Speicher- oder Versandaktionen werden eigene Schutzgrenzen ergänzt.

Gemeinsam weiterdenken

Was möchten Sie bewegen?

Eine Idee, eine konkrete Aufgabe oder der nächste Schritt mit KI. Wir hören zu.

Sprechen wir darüber