Früher oder später in Ihrer Programmierkarriere stehen Sie vor dem Dilemma der Validierung und Ausnahmebehandlung. Dies war auch bei mir und meinem Team der Fall. Vor ein paar Jahren erreichten wir einen Punkt, an dem wir architektonische Maßnahmen ergreifen mussten, um alle Ausnahmefälle zu berücksichtigen, die unser recht umfangreiches Softwareprojekt abwickeln musste. Nachfolgend finden Sie eine Liste von Vorgehensweisen, die wir in Bezug auf Validierung und Ausnahmebehandlung schätzen und anwenden konnten.
Als wir anfingen, über unser Problem zu sprechen, kam eines sehr schnell zum Vorschein. Was ist Validierung und was ist Ausnahmebehandlung? In einem Benutzerregistrierungsformular haben wir beispielsweise einige Regeln für das Passwort (es muss sowohl Zahlen als auch Buchstaben enthalten). Wenn der Benutzer nur Buchstaben eingibt, handelt es sich um ein Validierungsproblem oder um eine Ausnahme. Sollte die Benutzeroberfläche dies bestätigen oder einfach an das Backend übergeben und Ausnahmen feststellen, die von mir geworfen werden?
Wir sind zu dem Schluss gekommen, dass die Validierung sich auf vom System definierte Regeln bezieht und anhand der vom Benutzer angegebenen Daten überprüft wird. Bei einer Validierung sollte es nicht darauf ankommen, wie die Geschäftslogik oder das System für diese Angelegenheit funktioniert. Zum Beispiel kann unser Betriebssystem ohne Protest ein aus einfachen Buchstaben bestehendes Passwort erwarten. Wir möchten jedoch eine Kombination aus Buchstaben und Zahlen erzwingen. Dies ist ein Fall zur Validierung, eine Regel, die wir auferlegen wollen.
Ausnahmen sind jedoch Fälle, in denen unser System unvorhergesehen, falsch oder überhaupt nicht funktioniert, wenn bestimmte Daten in einem falschen Format bereitgestellt werden. Wenn im obigen Beispiel der Benutzername bereits auf dem System vorhanden ist, handelt es sich um einen Ausnahmefall. Unsere Geschäftslogik sollte in der Lage sein, die entsprechende Ausnahme auszulösen, und die Benutzeroberfläche fängt sie ab und behandelt sie, damit der Benutzer eine nette Nachricht sieht.
Nachdem wir nun klar gemacht haben, was unsere Ziele sind, wollen wir uns einige Beispiele ansehen, die auf derselben Idee für die Benutzerregistrierung basieren.
Für die meisten heutigen Browser ist JavaScript eine Selbstverständlichkeit. Es gibt fast keine Webseite ohne ein gewisses Maß an JavaScript. Eine bewährte Methode ist die Überprüfung einiger grundlegender Dinge in JavaScript.
Nehmen wir an, wir haben ein einfaches Benutzerregistrierungsformular in index.php
, wie unten beschrieben.
Benutzer Registration Neuen Account Registrieren
Dadurch wird etwas ähnlich dem folgenden Bild ausgegeben:
Jedes dieser Formulare muss bestätigen, dass der in den beiden Kennwortfeldern eingegebene Text gleich ist. Natürlich soll dies sicherstellen, dass der Benutzer bei der Eingabe seines Passworts keinen Fehler macht. Mit JavaScript ist die Validierung sehr einfach.
Zuerst müssen wir ein wenig von unserem HTML-Code aktualisieren.
Wir haben den Kennworteingabefeldern Namen hinzugefügt, damit wir sie identifizieren können. Dann haben wir angegeben, dass das Formular beim Senden das Ergebnis einer aufgerufenen Funktion zurückgeben soll validatePasswords ()
. Diese Funktion ist das JavaScript, das wir schreiben. Solche einfachen Skripts können in der HTML-Datei gespeichert werden, andere, komplexere Skripts sollten in ihre eigenen JavaScript-Dateien geschrieben werden.
Das einzige, was wir hier tun, ist der Vergleich der Werte der beiden Eingabefelder mit dem Namen "Passwort
" und "bestätigen
". Wir können auf das Formular durch den Parameter verweisen, den wir beim Aufruf der Funktion senden. Wir verwendeten"diese
"in der Form onsubmit
Attribut, so wird das Formular selbst an die Funktion gesendet.
Wenn die Werte gleich sind, wahr
wird zurückgegeben und das Formular wird gesendet, andernfalls wird eine Warnmeldung angezeigt, die den Benutzer darauf hinweist, dass die Passwörter nicht übereinstimmen.
Wir können zwar JavaScript verwenden, um die meisten unserer Eingaben zu überprüfen, aber es gibt Fälle, in denen wir einen einfacheren Weg gehen wollen. In HTML5 ist ein gewisses Maß an Eingabevalidierung verfügbar, und die meisten Browser wenden sie gerne an. Die Verwendung der HTML5-Validierung ist in manchen Fällen einfacher, bietet jedoch weniger Flexibilität.
Benutzer Registration Neuen Account Registrieren
Um mehrere Validierungsfälle zu demonstrieren, haben wir unser Formular etwas erweitert. Wir haben eine E-Mail-Adresse und eine Website hinzugefügt. HTML-Validierungen wurden auf drei Feldern festgelegt.
Nutzername
ist einfach erforderlich. Es wird mit einer beliebigen Zeichenfolge validiert, die länger als null Zeichen ist. Email
"und wenn wir das angeben"erforderlich
msgstr "Attribut, Browser wenden eine Validierung auf das Feld an. url
". Wir haben auch ein" angegebenMuster
msgstr "Attribut, in das Sie Ihre regulären Ausdrücke schreiben können, um die erforderlichen Felder zu überprüfen. Um den Benutzer auf den Status der Felder aufmerksam zu machen, haben wir auch ein wenig CSS verwendet, um die Grenzen der Eingaben je nach Status der erforderlichen Validierung in Rot oder Grün zu färben.
Das Problem bei HTML-Überprüfungen besteht darin, dass sich verschiedene Browser beim Senden des Formulars anders verhalten. Einige Browser wenden CSS nur an, um die Benutzer zu informieren, andere verhindern, dass das Formular vollständig abgeschickt wird. Ich empfehle Ihnen, Ihre HTML-Überprüfungen gründlich in verschiedenen Browsern zu testen und bei Bedarf auch einen JavaScript-Fallback für Browser bereitzustellen, die nicht intelligent genug sind.
Mittlerweile wissen viele Leute von Robert C. Martins sauberem Architekturvorschlag, bei dem das MVC-Framework nur zur Präsentation dient und nicht für die Geschäftslogik.
Im Wesentlichen sollte sich Ihre Geschäftslogik an einem separaten, gut isolierten Ort befinden, der die Architektur Ihrer Anwendung widerspiegelt. Die Ansichten und Controller des Frameworks sollten die Bereitstellung des Inhalts für den Benutzer steuern, und Modelle können ganz oder bei Bedarf fallen gelassen werden , wird nur für Liefervorgänge verwendet. Eine solche Operation ist die Validierung. Die meisten Frameworks verfügen über hervorragende Validierungsfunktionen. Es wäre eine Schande, wenn Sie Ihre Modelle nicht einsetzen und dort ein wenig validieren.
Wir werden nicht mehrere MVC-Web-Frameworks installieren, um zu demonstrieren, wie Sie unsere vorherigen Formulare validieren können. Es folgen zwei ungefähre Lösungen in Laravel und CakePHP.
Laravel ist so konzipiert, dass Sie mehr Zugriff auf die Validierung im Controller haben, wo Sie auch direkten Zugriff auf die Eingaben des Benutzers haben. Der eingebaute Validator wird dort bevorzugt verwendet. Es gibt jedoch Vorschläge im Internet, dass die Validierung in Modellen nach wie vor eine gute Sache in Laravel ist. Ein vollständiges Beispiel und eine Lösung von Jeffrey Way finden Sie in seinem Github-Repository.
Wenn Sie lieber eine eigene Lösung schreiben, können Sie etwas Ähnliches wie das unten stehende Modell tun.
Die Klasse UserACL erweitert Eloquent private $ rules = array ('userName' => 'erforderlich | alpha | min: 5', 'password' => 'required | min: 6', 'confirm' => 'required | min: 6 ',' email '=>' required | email ',' website '=>' url '); private $ Fehler; öffentliche Funktion validate ($ data) $ validator = Validator :: make ($ data, $ this-> rules); if ($ validator-> fail ()) $ this-> errors = $ validator-> errors; falsch zurückgeben; return true; public function errors () return $ this-> errors;
Sie können dies von Ihrem Controller aus verwenden, indem Sie einfach die UserACL
Objekt und Aufruf validieren. Sie haben wahrscheinlich die "registrieren
"Methode auch für dieses Modell und die registrieren
wird die bereits validierten Daten nur an Ihre Geschäftslogik delegieren.
CakePHP fördert die Validierung auch in Modellen. Es verfügt über umfangreiche Validierungsfunktionen auf Modellebene. Hier wird beschrieben, wie eine Bestätigung für unser Formular in CakePHP aussehen würde.
Die Klasse UserACL erweitert AppModel public $ validate = ['userName' => ['rule' => ['minLength', 5], 'required' => true, 'allowEmpty' => false, 'on' => 'create ',' message '=>' Der Benutzername muss mindestens 5 Zeichen lang sein. ' ], 'password' => ['rule' => ['equalsTo', 'confirm'], 'message' => 'Die beiden Passwörter stimmen nicht überein. Bitte geben Sie sie erneut ein. ' ]]; public function equalsTo ($ checkedField, $ otherField = null) $ value = $ this-> getFieldValue ($ checkedField); return $ value === $ this-> data [$ this-> name] [$ otherField]; private Funktion getFieldValue ($ fieldName) return array_values ($ otherField) [0];
Wir haben die Regeln nur teilweise veranschaulicht. Es reicht aus, die Validierungskraft im Modell hervorzuheben. CakePHP ist das besonders gut. Es hat eine große Anzahl von eingebauten Validierungsfunktionen wie "minimale Länge
"In dem Beispiel und verschiedenen Möglichkeiten, dem Benutzer Feedback zu geben. Noch mehr, Konzepte wie"erforderlich
" oder "allowEmpty
"sind eigentlich keine Validierungsregeln. Cake wird diese beim Generieren Ihrer Ansicht betrachten und HTML-Validierungen auch auf mit diesen Parametern gekennzeichneten Feldern einfügen. Regeln sind jedoch großartig und können einfach erweitert werden, indem einfach Methoden auf der Modellklasse erstellt werden, wie wir es taten Vergleichen Sie die beiden Kennwortfelder. Schließlich können Sie immer angeben, welche Nachricht Sie an die Ansichten senden möchten, falls die Validierung fehlschlägt. Weitere Informationen zur CakePHP-Validierung finden Sie im Kochbuch.
Die generelle Validierung auf Modellebene hat ihre Vorteile. Jedes Framework bietet einen einfachen Zugriff auf die Eingabefelder und erstellt den Mechanismus, um den Benutzer im Falle eines Validierungsfehlers zu benachrichtigen. Try-Catch-Anweisungen oder andere anspruchsvolle Schritte sind nicht erforderlich. Durch die Überprüfung auf der Serverseite wird auch sichergestellt, dass die Daten unabhängig von deren Gültigkeit überprüft werden. Der Benutzer kann unsere Software nicht mehr wie mit HTML oder JavaScript überlisten. Natürlich ist jede serverseitige Validierung mit den Kosten einer Netzwerkrundfahrt und Rechenleistung auf Seiten des Providers und nicht auf der Seite des Clients verbunden.
Der letzte Schritt bei der Überprüfung der Daten vor der Übergabe an das System ist die Ebene unserer Geschäftslogik. Informationen, die diesen Teil des Systems erreichen, sollten so bereinigt werden, dass sie verwendet werden können. Die Geschäftslogik sollte nur auf Fälle prüfen, die für sie kritisch sind. Das Hinzufügen eines bereits vorhandenen Benutzers ist beispielsweise ein Fall, in dem eine Ausnahme ausgelöst wird. Die Überprüfung der Länge des Benutzers auf mindestens fünf Zeichen sollte auf dieser Stufe nicht vorkommen. Wir können sicher davon ausgehen, dass solche Einschränkungen auf höheren Ebenen durchgesetzt wurden.
Auf der anderen Seite ist der Vergleich der beiden Passwörter ein Diskussionspunkt. Wenn wir zum Beispiel das Passwort nur in der Nähe des Benutzers in einer Datenbank verschlüsseln und speichern, können wir die Prüfung fallen lassen und davon ausgehen, dass die vorherigen Layer sicherstellt, dass die Passwörter gleich sind. Wenn Sie jedoch einen echten Benutzer auf dem Betriebssystem erstellen, der eine API oder ein CLI-Tool verwendet, für das tatsächlich ein Benutzername, ein Passwort und eine Passwortbestätigung erforderlich sind, möchten wir möglicherweise auch den zweiten Eintrag verwenden und an ein CLI-Tool senden. Lassen Sie es erneut prüfen, wenn die Kennwörter übereinstimmen, und seien Sie bereit, eine Ausnahme auszulösen, wenn dies nicht der Fall ist. Auf diese Weise haben wir unsere Geschäftslogik so modelliert, dass sie dem Verhalten des realen Betriebssystems entspricht.
Ausnahmen von PHP zu werfen ist sehr einfach. Lassen Sie uns unsere Benutzerzugriffssteuerungsklasse erstellen und zeigen, wie Sie eine Benutzeradditionsfunktion implementieren.
Klasse UserControlTest erweitert PHPUnit_Framework_TestCase function testBehavior () $ this-> assertTrue (true);
Ich beginne immer gerne mit etwas Einfachem, das mich in Bewegung setzt. Das Erstellen eines dummen Tests ist eine großartige Möglichkeit, dies zu tun. Es zwingt mich auch, darüber nachzudenken, was ich umsetzen möchte. Ein Test namens UserControlTest
Ich dachte, ich brauche ein Nutzerkontrolle
Klasse, um meine Methode zu implementieren.
required_once __DIR__. '/… /UserControl.php'; Klasse UserControlTest erweitert PHPUnit_Framework_TestCase / ** * @expectedException Ausnahme * @expectedExceptionMessage Benutzer darf nicht leer sein * / function testEmptyUsernameWillThrowException () $ userControl = new UserControl (); $ userControl-> add (");
Der nächste zu schreibende Test ist ein degenerativer Fall. Wir werden nicht für eine bestimmte Benutzerlänge testen, wir möchten jedoch sicherstellen, dass wir keinen leeren Benutzer hinzufügen möchten. Es ist manchmal leicht, den Inhalt einer Variablen über alle diese Schichten unserer Anwendung hinweg aus Sicht des Geschäfts zu verlieren. Dieser Code wird offensichtlich fehlschlagen, da wir noch keine Klasse haben.
PHP Warnung: required_once ([long-path-here] / Test /… /UserControl.php): Stream konnte nicht geöffnet werden: Keine solche Datei oder Verzeichnis in [long-path-here] /Test/UserControlTest.php in Zeile 2
Lassen Sie uns die Klasse erstellen und unsere Tests ausführen. Jetzt haben wir ein anderes Problem.
PHP-Schwerwiegender Fehler: Aufruf der nicht definierten Methode UserControl :: add ()
Aber auch das können wir in wenigen Sekunden beheben.
Klasse UserControl öffentliche Funktion add ($ username)
Jetzt können wir einen schönen Testfehler haben, der uns die gesamte Geschichte unseres Codes erzählt.
1) UserControlTest :: testEmptyUsernameWillThrowException Fehler beim Aktivieren der Ausnahme vom Typ "Exception".
Zum Schluß können wir noch etwas programmieren.
öffentliche Funktion add ($ username) if (! $ username) throw new Exception ();
Dadurch wird die Erwartung für die Ausnahme bestanden, aber ohne Angabe einer Nachricht schlägt der Test fehl.
1) UserControlTest :: testEmptyUsernameWillThrowException Fehler beim Bestätigen, dass die Ausnahmemeldung "enthält" Der Benutzer darf nicht leer sein ".
Zeit zum Schreiben der Exception-Nachricht
public function add ($ username) if (! $ username) throw new Exception ('Benutzer darf nicht leer sein!');
Das macht unseren Test bestanden. Wie Sie sehen können, überprüft PHPUnit, dass die erwartete Ausnahmemeldung in der tatsächlich ausgelösten Ausnahme enthalten ist. Dies ist nützlich, da wir Nachrichten dynamisch erstellen und nur nach dem stabilen Teil suchen können. Ein allgemeines Beispiel ist, wenn Sie einen Fehler mit einem Basistext ausgeben und am Ende den Grund für diese Ausnahme angeben. Gründe werden normalerweise von Bibliotheken oder Anwendungen von Drittanbietern bereitgestellt.
/ ** * @expectedException Ausnahme * @expectedExceptionMessage Benutzer George * kann nicht hinzugefügt werden * / Funktion testWillNotAddAnAlreadyExistingUser () $ command = \ Mockery :: mock ('SystemCommand'); $ command-> shouldReceive ('execute') -> once () -> with ('adduser George') -> andReturn (false); $ command-> shouldReceive ('getFailureMessage') -> once () -> andReturn ('Benutzer ist bereits auf dem System vorhanden.'); $ userControl = neues Benutzersteuerelement (Befehl $); $ userControl-> add ('George');
Durch das Auftreten von Fehlern bei doppelten Benutzern können wir den Aufbau dieser Nachricht noch weiter untersuchen. Der obige Test erzeugt einen Mock, der einen Systembefehl simuliert, der fehlschlägt und auf Anforderung eine schöne Fehlermeldung zurückgibt. Wir werden diesen Befehl an die Nutzerkontrolle
Klasse für den internen Gebrauch.
Klasse UserControl private $ systemCommand; öffentliche Funktion __construct (SystemCommand $ systemCommand = null) $ this-> systemCommand = $ systemCommand? : neuer SystemCommand (); public function add ($ username) if (! $ username) throw new Exception ('Benutzer darf nicht leer sein!'); Klasse SystemCommand
Einspritzen der a Systembefehl
Instanz war ziemlich einfach. Wir haben auch eine erstellt Systembefehl
Klasse in unserem Test, nur um Syntaxprobleme zu vermeiden. Wir werden es nicht umsetzen. Sein Umfang geht über das Thema dieses Tutorials hinaus. Wir haben jedoch eine weitere Testfehlermeldung.
1) UserControlTest :: testWillNotAddAnAlreadyExistingUser schlug fehl, wenn diese Ausnahme vom Typ "Exception" ausgelöst wurde.
Ja. Wir werfen keine Ausnahmen. Die Logik zum Aufrufen des Systembefehls und zum Versuch, den Benutzer hinzuzufügen, fehlt.
public function add ($ username) if (! $ username) throw new Exception ('Benutzer darf nicht leer sein!'); if (! $ this-> systemCommand-> execute (sprintf ('adduser% s', $ username))) Neue Exception auslösen (sprintf ('Benutzer% s kann nicht hinzugefügt werden. Grund:% s', $ username, $ this-> systemCommand-> getFailureMessage ()));
Nun, diese Änderungen an der hinzufügen()
Methode kann den Trick tun. Wir versuchen, unseren Befehl auf dem System auszuführen, egal was passiert, und wenn das System sagt, dass es den Benutzer aus irgendeinem Grund nicht hinzufügen kann, lösen wir eine Ausnahme aus. Die Nachricht dieser Ausnahme wird teilweise hartcodiert, mit dem Namen des Benutzers und dem Grund des Systembefehls am Ende verkettet. Wie Sie sehen, macht dieser Code unseren Test durch.
In den meisten Fällen reicht es aus, Ausnahmen mit unterschiedlichen Nachrichten auszulösen. Wenn Sie jedoch ein komplexeres System haben, müssen Sie auch diese Ausnahmen abfangen und auf dieser Basis unterschiedliche Aktionen ausführen. Wenn Sie die Nachricht einer Ausnahme analysieren und nur dann etwas unternehmen, kann dies zu lästigen Problemen führen. Erstens sind Strings Teil der Benutzeroberfläche, der Präsentation, und sie haben einen unbeständigen Charakter. Die Logik auf sich ständig ändernden Zeichenfolgen wird zu einem Albtraum des Abhängigkeitsmanagements führen. Zweitens, einen getMessage ()
Die Methode der gefangenen Ausnahme jedes Mal ist auch eine merkwürdige Methode, um zu entscheiden, was als nächstes zu tun ist.
Vor diesem Hintergrund ist das Erstellen eigener Ausnahmen der nächste logische Schritt.
/ ** * @expectedException ExceptionCannotAddUser * @expectedExceptionMessage Benutzer George * kann nicht hinzugefügt werden * / Funktion testWillNotAddAnAlreadyExistingUser () $ command = \ Mockery :: mock ('SystemCommand'); $ command-> shouldReceive ('execute') -> once () -> with ('adduser George') -> andReturn (false); $ command-> shouldReceive ('getFailureMessage') -> once () -> andReturn ('Benutzer ist bereits auf dem System vorhanden.'); $ userControl = neues Benutzersteuerelement (Befehl $); $ userControl-> add ('George');
Wir haben unseren Test modifiziert, um eine eigene benutzerdefinierte Ausnahme zu erwarten, ExceptionCannotAddUser
. Der Rest des Tests ist unverändert.
Klasse ExceptionCannotAddUser erweitert Exception öffentliche Funktion __construct ($ userName, $ reason) $ message = sprintf ('Benutzer% s kann nicht hinzugefügt werden. Grund:% s', $ userName, $ reason); parent :: __ construct ($ message, 13, null);
Die Klasse, die unsere benutzerdefinierte Ausnahme implementiert, ist wie jede andere Klasse, muss aber erweitert werden Ausnahme
. Die Verwendung von benutzerdefinierten Ausnahmen bietet uns auch einen hervorragenden Ort für die gesamte String-Manipulation der Präsentation. Durch die Verschiebung der Verkettung haben wir auch die Präsentation aus der Geschäftslogik entfernt und das Prinzip der Einzelverantwortung respektiert.
public function add ($ username) if (! $ username) throw new Exception ('Benutzer darf nicht leer sein!'); if (! $ this-> systemCommand-> execute (sprintf ('adduser% s', $ username))) werfen neuen ExceptionCannotAddUser ($ username, $ this-> systemCommand-> getFailureMessage ());
Unsere eigene Ausnahme zu werfen, ist nur eine Frage der Veränderung des alten "werfen
"Befehl an den neuen und Senden von zwei Parametern, anstatt die Nachricht hier zu verfassen. Natürlich sind alle Tests bestanden.
PHPUnit 3.7.28 von Sebastian Bergmann… Zeit: 18 ms, Speicher: 3.00 MB OK (2 Tests, 4 Assertions) Fertig.
Ausnahmen müssen zu einem bestimmten Zeitpunkt abgefangen werden, es sei denn, Sie möchten, dass Ihr Benutzer sie so sieht, wie sie sind. Wenn Sie ein MVC-Framework verwenden, möchten Sie wahrscheinlich Ausnahmen im Controller oder Modell feststellen. Nachdem die Ausnahme abgefangen wurde, wird sie in eine Nachricht an den Benutzer umgewandelt und in Ihrer Ansicht dargestellt. Ein üblicher Weg, um dies zu erreichen, ist die Erstellung einestryAction ($ action)
"Methode im Basis-Controller oder -Modell Ihrer Anwendung und rufen Sie sie immer mit der aktuellen Aktion auf. In dieser Methode können Sie die Fanglogik und die Erzeugung schöner Nachrichten durchführen, um sich an Ihr Framework anzupassen.
Wenn Sie kein Webframework oder keine Webschnittstelle für diese Angelegenheit verwenden, sollte Ihre Präsentationsebene darauf achten, diese Ausnahmen abzufangen und umzuwandeln.
Wenn Sie eine Bibliothek entwickeln, liegt es in der Verantwortung Ihrer Kunden, Ihre Ausnahmen zu erfassen.
Das ist es. Wir haben alle Schichten unserer Anwendung durchquert. Wir haben in JavaScript, HTML und in unseren Modellen validiert. Wir haben Ausnahmen aus unserer Geschäftslogik geworfen und sogar eigene Ausnahmen erstellt. Dieser Ansatz zur Validierung und Ausnahmebehandlung kann problemlos von kleinen bis großen Projekten angewendet werden. Wenn Ihre Validierungslogik jedoch sehr komplex wird und in verschiedenen Teilen Ihres Projekts überlappende Teile der Logik verwendet werden, können Sie alle Validierungen, die auf einer bestimmten Ebene ausgeführt werden können, an einen Validierungsdienst oder einen Validierungsanbieter extrahieren. Diese Stufen können JavaScript-Validator, Back-End-PHP-Validator, Kommunikationsprüfer von Drittanbietern usw. sein, sind aber nicht darauf beschränkt.
Danke fürs Lesen. Einen schönen Tag noch.