German (Deutsch) translation by Władysław Łucyszyn (you can also view the original English article)
Früher oder später in Ihrer Programmierkarriere werden Sie mit dem Dilemma der Validierung und Ausnahmebehandlung konfrontiert sein. Dies war auch bei mir und meinem Team der Fall. Vor ein paar Jahren hatten wir einen Punkt erreicht, an dem wir architektonische Maßnahmen ergreifen mussten, um alle Ausnahmefälle zu berücksichtigen, die unser recht großes Softwareprojekt bewältigen musste. Nachfolgend finden Sie eine Liste von Methoden, die wir bei der Validierung und Ausnahmebehandlung bewertet und angewendet haben.
Validierung vs. Ausnahmebehandlung
Als wir anfingen, unser Problem zu diskutieren, tauchte eine Sache sehr schnell auf. Was ist Validierung und was ist Ausnahmebehandlung? Zum Beispiel haben wir in einem Benutzerregistrierungsformular 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 eine Ausnahme. Sollte die Benutzeroberfläche dies validieren oder es einfach an das Backend übergeben und alle Ausnahmen abfangen, die von mir ausgelöst werden?
Wir sind zu dem allgemeinen Schluss gekommen, dass sich die Validierung auf Regeln bezieht, die vom System definiert und anhand der vom Benutzer bereitgestellten Daten überprüft wurden. Eine Validierung sollte sich nicht darum kümmern, wie die Geschäftslogik funktioniert oder wie das System für diese Angelegenheit funktioniert. Zum Beispiel kann unser Betriebssystem ohne Proteste ein Passwort erwarten, das aus einfachen Buchstaben besteht. Wir wollen jedoch eine Kombination aus Buchstaben und Zahlen erzwingen. Dies ist ein Fall für die Validierung, eine Regel, die wir auferlegen möchten.
Ausnahmen sind jedoch Fälle, in denen unser System möglicherweise unvorhergesehen, falsch oder gar nicht funktioniert, wenn bestimmte Daten in einem falschen Format bereitgestellt werden. Wenn im obigen Beispiel der Benutzername beispielsweise bereits auf dem System vorhanden ist, handelt es sich um eine Ausnahme. Unsere Geschäftslogik sollte in der Lage sein, die entsprechende Ausnahme und die Benutzeroberfläche auszulösen und zu verarbeiten, damit der Benutzer eine nette Nachricht sieht.
Validierung in der Benutzeroberfläche
Nachdem wir unsere Ziele klargestellt haben, sehen wir uns einige Beispiele an, die auf der gleichen Idee des Benutzerregistrierungsformulars basieren.
Validierung in JavaScript
Für die meisten heutigen Browser ist JavaScript eine Selbstverständlichkeit. Es gibt fast keine Webseite ohne ein gewisses Maß an JavaScript. Eine gute Vorgehensweise besteht darin, einige grundlegende Dinge in JavaScript zu überprüfen.
Angenommen, wir haben ein einfaches Benutzerregistrierungsformular in index.php, wie unten beschrieben.
1 |
<!DOCTYPE html>
|
2 |
<html>
|
3 |
<head>
|
4 |
<title>User Registration</title> |
5 |
<meta charset="UTF-8"> |
6 |
</head>
|
7 |
<body>
|
8 |
<h3>Register new account</h3> |
9 |
<form>
|
10 |
Username: |
11 |
<br/>
|
12 |
<input type="text" /> |
13 |
<br/>
|
14 |
Password: |
15 |
<br/>
|
16 |
<input type="password" /> |
17 |
<br/>
|
18 |
Confirm: |
19 |
<br/>
|
20 |
<input type="password" /> |
21 |
<br/>
|
22 |
<input type="submit" name="register" value="Register"> |
23 |
</form>
|
24 |
</body>
|
25 |
</html>
|
Dies gibt etwas Ähnliches wie das folgende Bild aus:

Jedes dieser Formulare sollte bestätigen, dass der in die beiden Kennwortfelder eingegebene Text gleich ist. Dies soll natürlich sicherstellen, dass der Benutzer beim Eingeben seines Passworts keinen Fehler macht. Mit JavaScript ist die Validierung recht einfach.
Zuerst müssen wir ein wenig von unserem HTML-Code aktualisieren.
1 |
<form onsubmit="return validatePasswords(this);"> |
2 |
Username: |
3 |
<br/>
|
4 |
<input type="text" /> |
5 |
<br/>
|
6 |
Password: |
7 |
<br/>
|
8 |
<input type="password" name="password"/> |
9 |
<br/>
|
10 |
Confirm: |
11 |
<br/>
|
12 |
<input type="password" name="confirm"/> |
13 |
<br/>
|
14 |
<input type="submit" name="register" value="Register"> |
15 |
</form>
|
Wir haben den Passworteingabefeldern Namen hinzugefügt, damit wir sie identifizieren können. Dann haben wir angegeben, dass das Formular beim Senden das Ergebnis einer Funktion namens validatePasswords() zurückgeben soll. Diese Funktion ist das JavaScript, das wir schreiben werden. Einfache Skripte wie dieses können in der HTML-Datei gespeichert werden, andere, komplexere Skripte sollten in ihren eigenen JavaScript-Dateien gespeichert werden.
1 |
<script>
|
2 |
function validatePasswords(form) { |
3 |
if (form.password.value !== form.confirm.value) { |
4 |
alert("Passwords do not match"); |
5 |
return false; |
6 |
}
|
7 |
return true; |
8 |
}
|
9 |
|
10 |
</script>
|
Das einzige, was wir hier tun, ist, die Werte der beiden Eingabefelder "password" und "confirm" zu vergleichen. Wir können das Formular anhand des Parameters referenzieren, den wir beim Aufrufen der Funktion senden. Wir haben "this" im onsubmit-Attribut des Formulars verwendet, sodass das Formular selbst an die Funktion gesendet wird.
Wenn die Werte gleich sind, wird true zurückgegeben und das Formular gesendet. Andernfalls wird eine Warnmeldung angezeigt, die den Benutzer darüber informiert, dass die Kennwörter nicht übereinstimmen.



HTML5-Validierungen
Während wir JavaScript verwenden können, um die meisten unserer Eingaben zu validieren, gibt es Fälle, in denen wir einen einfacheren Weg einschlagen möchten. In HTML5 ist ein gewisser Grad an Eingabevalidierung verfügbar, und die meisten Browser wenden diese gerne an. Die Verwendung der HTML5-Validierung ist in einigen Fällen einfacher, bietet jedoch weniger Flexibilität.
1 |
<head>
|
2 |
<title>User Registration</title> |
3 |
<meta charset="UTF-8"> |
4 |
<style>
|
5 |
input { |
6 |
width: 200px; |
7 |
}
|
8 |
input:required:valid { |
9 |
border-color: mediumspringgreen; |
10 |
}
|
11 |
input:required:invalid { |
12 |
border-color: lightcoral; |
13 |
}
|
14 |
</style>
|
15 |
</head>
|
16 |
<body>
|
17 |
<h3>Register new account</h3> |
18 |
<form onsubmit="return validatePasswords(this);"> |
19 |
Username: |
20 |
<br/>
|
21 |
<input type="text" name="userName" required/> |
22 |
<br/>
|
23 |
Password: |
24 |
<br/>
|
25 |
<input type="password" name="password"/> |
26 |
<br/>
|
27 |
Confirm: |
28 |
<br/>
|
29 |
<input type="password" name="confirm"/> |
30 |
<br/>
|
31 |
Email Address: |
32 |
<br/>
|
33 |
<input type="email" name="email" required placeholder="A Valid Email Address"/> |
34 |
<br/>
|
35 |
Website: |
36 |
<br/>
|
37 |
<input type="url" name="website" required pattern="https?://.+"/> |
38 |
<br/>
|
39 |
<input type="submit" name="register" value="Register"> |
40 |
</form>
|
41 |
</body>
|
Um mehrere Validierungsfälle zu demonstrieren, haben wir unser Formular ein wenig erweitert. Wir haben auch eine E-Mail-Adresse und eine Website hinzugefügt. HTML-Validierungen wurden in drei Feldern festgelegt.
- Der
usernamefür die Texteingabe ist einfach erforderlich. Es wird mit jeder Zeichenfolge überprüft, die länger als null Zeichen ist. - Das E-Mail-Adressfeld ist vom Typ "
email". Wenn wir das Attribut "required" angeben, wenden die Browser eine Validierung auf das Feld an. - Schließlich ist das Website-Feld vom Typ "
url". Wir haben auch ein "pattern"-Attribut angegeben, in das Sie Ihre regulären Ausdrücke schreiben können, die die erforderlichen Felder validieren.
Um den Benutzer auf den Status der Felder aufmerksam zu machen, haben wir auch ein wenig CSS verwendet, um die Ränder 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 Versuch, das Formular zu senden, unterschiedlich verhalten. Einige Browser wenden nur das CSS an, um die Benutzer zu informieren, andere verhindern das Senden des Formulars insgesamt. Ich empfehle Ihnen, Ihre HTML-Validierungen gründlich in verschiedenen Browsern zu testen und bei Bedarf auch einen JavaScript-Fallback für Browser bereitzustellen, die nicht intelligent genug sind.
Validierung in Modellen
Mittlerweile kennen viele Leute den Vorschlag für eine saubere Architektur von Robert C. Martin, in dem das MVC-Framework nur zur Präsentation und nicht zur Geschäftslogik dient.



Im Wesentlichen sollte sich Ihre Geschäftslogik an einem separaten, gut isolierten Ort befinden, der so organisiert ist, dass er die Architektur Ihrer Anwendung widerspiegelt, während die Ansichten und Controller des Frameworks die Bereitstellung des Inhalts für den Benutzer steuern sollten und Modelle ganz oder bei Bedarf gelöscht werden können , wird nur zur Ausführung lieferungsbezogener Vorgänge verwendet. Eine solche Operation ist die Validierung. Die meisten Frameworks verfügen über hervorragende Validierungsfunktionen. Es wäre eine Schande, Ihre Modelle nicht in Betrieb zu nehmen und dort eine kleine Validierung durchzuführen.
Wir werden nicht mehrere MVC-Webframeworks installieren, um zu demonstrieren, wie unsere vorherigen Formulare validiert werden. Hier sind jedoch zwei ungefähre Lösungen in Laravel und CakePHP.
Validierung in einem Laravel-Modell
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 in Laravel immer noch eine gute Sache ist. Ein vollständiges Beispiel und eine Lösung von Jeffrey Way finden Sie in seinem Github-Repository.
Wenn Sie es vorziehen, Ihre eigene Lösung zu schreiben, können Sie etwas Ähnliches wie das folgende Modell tun.
1 |
class UserACL extends Eloquent { |
2 |
private $rules = array( |
3 |
'userName' => 'required|alpha|min:5', |
4 |
'password' => 'required|min:6', |
5 |
'confirm' => 'required|min:6', |
6 |
'email' => 'required|email', |
7 |
'website' => 'url' |
8 |
);
|
9 |
|
10 |
private $errors; |
11 |
|
12 |
public function validate($data) { |
13 |
$validator = Validator::make($data, $this->rules); |
14 |
|
15 |
if ($validator->fails()) { |
16 |
$this->errors = $validator->errors; |
17 |
return false; |
18 |
}
|
19 |
return true; |
20 |
}
|
21 |
|
22 |
public function errors() { |
23 |
return $this->errors; |
24 |
}
|
25 |
}
|
Sie können dies von Ihrem Controller aus verwenden, indem Sie einfach das UserACL-Objekt erstellen und die Validierung aufrufen. Sie werden wahrscheinlich auch bei diesem Modell die Methode "register" haben, und das register delegiert nur die bereits validierten Daten an Ihre Geschäftslogik.
Validierung in einem CakePHP-Modell
CakePHP fördert die Validierung auch in Modellen. Es verfügt über umfangreiche Validierungsfunktionen auf Modellebene. Hier erfahren Sie, wie eine Validierung unseres Formulars in CakePHP aussehen würde.
1 |
class UserACL extends AppModel { |
2 |
|
3 |
public $validate = [ |
4 |
'userName' => [ |
5 |
'rule' => ['minLength', 5], |
6 |
'required' => true, |
7 |
'allowEmpty' => false, |
8 |
'on' => 'create', |
9 |
'message' => 'User name must be at least 5 characters long.' |
10 |
],
|
11 |
'password' => [ |
12 |
'rule' => ['equalsTo', 'confirm'], |
13 |
'message' => 'The two passwords do not match. Please re-enter them.' |
14 |
]
|
15 |
];
|
16 |
|
17 |
public function equalsTo($checkedField, $otherField = null) { |
18 |
$value = $this->getFieldValue($checkedField); |
19 |
return $value === $this->data[$this->name][$otherField]; |
20 |
}
|
21 |
|
22 |
private function getFieldValue($fieldName) { |
23 |
return array_values($otherField)[0]; |
24 |
}
|
25 |
}
|
Wir haben die Regeln nur teilweise veranschaulicht. Es reicht aus, die Validierungskraft des Modells hervorzuheben. CakePHP ist besonders gut darin. Es verfügt über eine große Anzahl integrierter Validierungsfunktionen wie "minLength" im Beispiel und verschiedene Möglichkeiten, dem Benutzer Feedback zu geben. Darüber hinaus sind Konzepte wie "required" oder "allowEmpty" keine Validierungsregeln. Cake wird diese beim Generieren Ihrer Ansicht berücksichtigen und HTML-Validierungen auch in Felder einfügen, die mit diesen Parametern markiert sind. Regeln sind jedoch großartig und können einfach erweitert werden, indem einfach Methoden für die Modellklasse erstellt werden, wie wir es getan haben, um die beiden Kennwortfelder zu vergleichen. Schließlich können Sie immer die Nachricht angeben, die Sie im Falle eines Validierungsfehlers an die Ansichten senden möchten. Mehr zur CakePHP-Validierung im Kochbuch.
Die Validierung im Allgemeinen auf Modellebene hat ihre Vorteile. Jedes Framework bietet einfachen Zugriff auf die Eingabefelder und erstellt den Mechanismus, um den Benutzer bei einem Validierungsfehler zu benachrichtigen. Keine Notwendigkeit für Try-Catch-Anweisungen oder andere ausgefeilte Schritte. Die Validierung auf der Serverseite stellt außerdem sicher, dass die Daten unabhängig von der jeweiligen Situation validiert werden. Der Benutzer kann unsere Software nicht mehr wie mit HTML oder JavaScript austricksen. Natürlich ist jede serverseitige Validierung mit den Kosten eines Netzwerk-Roundtrips und der Rechenleistung auf der Anbieterseite anstatt auf der Clientseite verbunden.
Ausnahmen von der Geschäftslogik auslösen
Der letzte Schritt beim Überprüfen von Daten vor dem Festschreiben an das System liegt auf der 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 nach Fällen suchen, die für sie kritisch sind. Das Hinzufügen eines bereits vorhandenen Benutzers ist beispielsweise ein Fall, wenn eine Ausnahme ausgelöst wird. Das Überprüfen der Länge des Benutzers auf mindestens fünf Zeichen sollte auf dieser Ebene nicht erfolgen. Wir können davon ausgehen, dass solche Einschränkungen auf höheren Ebenen durchgesetzt wurden.
Andererseits ist der Vergleich der beiden Passwörter ein Diskussionsgegenstand. Wenn wir beispielsweise nur das Kennwort in der Nähe des Benutzers in einer Datenbank verschlüsseln und speichern, können wir die Prüfung löschen und davon ausgehen, dass die vorherigen Ebenen sichergestellt haben, dass die Kennwörter gleich sind. Wenn wir jedoch einen echten Benutzer auf dem Betriebssystem mithilfe einer API oder eines CLI-Tools erstellen, für die tatsächlich ein Benutzername, ein Kennwort und eine Kennwortbestätigung erforderlich sind, möchten wir möglicherweise auch den zweiten Eintrag verwenden und ihn an ein CLI-Tool senden. Lassen Sie es erneut überprüfen, ob 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 auslösen
Ausnahmen von PHP zu werfen ist sehr einfach. Lassen Sie uns unsere Benutzerzugriffskontrollklasse erstellen und zeigen, wie eine Benutzerzusatzfunktion implementiert wird.
1 |
class UserControlTest extends PHPUnit_Framework_TestCase { |
2 |
function testBehavior() { |
3 |
$this->assertTrue(true); |
4 |
}
|
5 |
}
|
Ich beginne immer gerne mit etwas Einfachem, das mich zum Laufen bringt. Das Erstellen eines dummen Tests ist eine großartige Möglichkeit, dies zu tun. Es zwingt mich auch darüber nachzudenken, was ich implementieren möchte. Ein Test namens UserControlTest bedeutet, dass ich dachte, ich brauche eine UserControl-Klasse, um meine Methode zu implementieren.
1 |
require_once __DIR__ . '/../UserControl.php'; |
2 |
class UserControlTest extends PHPUnit_Framework_TestCase { |
3 |
|
4 |
/**
|
5 |
* @expectedException Exception
|
6 |
* @expectedExceptionMessage User can not be empty
|
7 |
*/
|
8 |
function testEmptyUsernameWillThrowException() { |
9 |
$userControl = new UserControl(); |
10 |
$userControl->add(''); |
11 |
}
|
12 |
|
13 |
}
|
Der nächste zu schreibende Test ist ein degenerativer Fall. Wir werden nicht auf eine bestimmte Benutzerlänge testen, aber wir möchten sicherstellen, dass wir keinen leeren Benutzer hinzufügen möchten. Es ist manchmal leicht, den Inhalt einer Variablen über alle Ebenen unserer Anwendung von der Ansicht zum Unternehmen zu verlieren. Dieser Code wird offensichtlich fehlschlagen, da wir noch keine Klasse haben.
1 |
PHP Warning: require_once([long-path-here]/Test/../UserControl.php): |
2 |
failed to open stream: No such file or directory in
|
3 |
[long-path-here]/Test/UserControlTest.php on line 2
|
Lassen Sie uns die Klasse erstellen und unsere Tests ausführen. Jetzt haben wir ein anderes Problem.
1 |
PHP Fatal error: Call to undefined method UserControl::add()
|
Aber auch das können wir in wenigen Sekunden beheben.
1 |
class UserControl { |
2 |
|
3 |
public function add($username) { |
4 |
|
5 |
}
|
6 |
|
7 |
}
|
Jetzt können wir einen schönen Testfehler haben, der uns die ganze Geschichte unseres Codes erzählt.
1 |
1) UserControlTest::testEmptyUsernameWillThrowException
|
2 |
Failed asserting that exception of type "Exception" is thrown. |
Schließlich können wir eine tatsächliche Codierung durchführen.
1 |
public function add($username) { |
2 |
if(!$username) { |
3 |
throw new Exception(); |
4 |
}
|
5 |
}
|
Dadurch wird die Erwartung für die Ausnahme bestanden, aber ohne Angabe einer Nachricht schlägt der Test dennoch fehl.
1 |
1) UserControlTest::testEmptyUsernameWillThrowException
|
2 |
Failed asserting that exception message '' contains 'User can not be empty'. |
Zeit, die Nachricht der Ausnahme zu schreiben
1 |
public function add($username) { |
2 |
if(!$username) { |
3 |
throw new Exception('User can not be empty!'); |
4 |
}
|
5 |
}
|
Damit ist unser Test bestanden. Wie Sie sehen können, überprüft PHPUnit, ob die erwartete Ausnahmemeldung in der tatsächlich ausgelösten Ausnahme enthalten ist. Dies ist nützlich, da wir damit dynamisch Nachrichten erstellen und nur nach dem stabilen Teil suchen können. Ein häufiges Beispiel ist, wenn Sie einen Fehler mit einem Basistext auslösen und am Ende den Grund für diese Ausnahme angeben. Gründe werden normalerweise von Bibliotheken oder Anwendungen von Drittanbietern angegeben.
1 |
/**
|
2 |
* @expectedException Exception
|
3 |
* @expectedExceptionMessage Cannot add user George
|
4 |
*/
|
5 |
function testWillNotAddAnAlreadyExistingUser() { |
6 |
$command = \Mockery::mock('SystemCommand'); |
7 |
$command->shouldReceive('execute')->once()->with('adduser George')->andReturn(false); |
8 |
$command->shouldReceive('getFailureMessage')->once()->andReturn('User already exists on the system.'); |
9 |
$userControl = new UserControl($command); |
10 |
$userControl->add('George'); |
11 |
}
|
Wenn wir doppelte Benutzer mit Fehlern versehen, können wir diese Nachrichtenkonstruktion noch einen Schritt weiter untersuchen. Der obige Test erstellt einen Mock, der einen Systembefehl simuliert, fehlschlägt und auf Anfrage eine nette Fehlermeldung zurückgibt. Wir werden diesen Befehl zur internen Verwendung in die UserControl-Klasse einfügen.
1 |
class UserControl { |
2 |
|
3 |
private $systemCommand; |
4 |
|
5 |
public function __construct(SystemCommand $systemCommand = null) { |
6 |
$this->systemCommand = $systemCommand ? : new SystemCommand(); |
7 |
}
|
8 |
|
9 |
public function add($username) { |
10 |
if (!$username) { |
11 |
throw new Exception('User can not be empty!'); |
12 |
}
|
13 |
}
|
14 |
|
15 |
}
|
16 |
|
17 |
class SystemCommand { |
18 |
|
19 |
}
|
Das Injizieren einer SystemCommand-Instanz war recht einfach. Wir haben in unserem Test auch eine SystemCommand-Klasse erstellt, um Syntaxprobleme zu vermeiden. Wir werden es nicht implementieren. Sein Umfang geht über das Thema dieses Tutorials hinaus. Wir haben jedoch eine andere Testfehlermeldung.
1 |
1) UserControlTest::testWillNotAddAnAlreadyExistingUser |
2 |
Failed asserting that exception of type "Exception" is thrown. |
Ja. Wir werfen keine Ausnahmen. Die Logik zum Aufrufen des Systembefehls und zum Versuch, den Benutzer hinzuzufügen, fehlt.
1 |
public function add($username) { |
2 |
if (!$username) { |
3 |
throw new Exception('User can not be empty!'); |
4 |
}
|
5 |
|
6 |
if(!$this->systemCommand->execute(sprintf('adduser %s', $username))) { |
7 |
throw new Exception( |
8 |
sprintf('Cannot add user %s. Reason: %s', |
9 |
$username, |
10 |
$this->systemCommand->getFailureMessage() |
11 |
)
|
12 |
);
|
13 |
}
|
14 |
}
|
Diese Änderungen an der add()-Methode können nun den Trick ausführen. 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 fest codiert, wobei der Benutzername angehängt und der Grund aus dem Systembefehl am Ende verkettet wird. Wie Sie sehen können, besteht dieser Code unseren Test.
Benutzerdefinierte Ausnahmen
In den meisten Fällen reicht es aus, Ausnahmen mit unterschiedlichen Nachrichten auszulösen. Wenn Sie jedoch ein komplexeres System haben, müssen Sie diese Ausnahmen auch abfangen und basierend darauf unterschiedliche Maßnahmen ergreifen. Das Analysieren der Meldung einer Ausnahme und das Ausführen von Maßnahmen ausschließlich dazu kann zu lästigen Problemen führen. Erstens sind Zeichenfolgen Teil der Benutzeroberfläche und der Präsentation und haben einen volatilen Charakter. Wenn die Logik auf sich ständig ändernden Zeichenfolgen basiert, führt dies zu einem Albtraum beim Abhängigkeitsmanagement. Zweitens ist das Aufrufen einer getMessage()-Methode für die abgefangene Ausnahme jedes Mal eine seltsame Methode, um zu entscheiden, was als nächstes zu tun ist.
Vor diesem Hintergrund ist das Erstellen eigener Ausnahmen der nächste logische Schritt.
1 |
/**
|
2 |
* @expectedException ExceptionCannotAddUser
|
3 |
* @expectedExceptionMessage Cannot add user George
|
4 |
*/
|
5 |
function testWillNotAddAnAlreadyExistingUser() { |
6 |
$command = \Mockery::mock('SystemCommand'); |
7 |
$command->shouldReceive('execute')->once()->with('adduser George')->andReturn(false); |
8 |
$command->shouldReceive('getFailureMessage')->once()->andReturn('User already exists on the system.'); |
9 |
$userControl = new UserControl($command); |
10 |
$userControl->add('George'); |
11 |
}
|
Wir haben unseren Test geändert, um unsere eigene benutzerdefinierte Ausnahme, ExceptionCannotAddUser, zu erwarten. Der Rest des Tests bleibt unverändert.
1 |
class ExceptionCannotAddUser extends Exception { |
2 |
|
3 |
public function __construct($userName, $reason) { |
4 |
$message = sprintf( |
5 |
'Cannot add user %s. Reason: %s', |
6 |
$userName, $reason |
7 |
);
|
8 |
parent::__construct($message, 13, null); |
9 |
}
|
10 |
}
|
Die Klasse, die unsere benutzerdefinierte Ausnahme implementiert, ist wie jede andere Klasse, muss jedoch die Exception erweitern. Die Verwendung von benutzerdefinierten Ausnahmen bietet uns auch einen großartigen Ort, um alle präsentationsbezogenen Zeichenfolgenmanipulationen durchzuführen. Wenn wir die Verkettung hierher verschieben, haben wir auch die Darstellung aus der Geschäftslogik entfernt und das Prinzip der Einzelverantwortung respektiert.
1 |
public function add($username) { |
2 |
if (!$username) { |
3 |
throw new Exception('User can not be empty!'); |
4 |
}
|
5 |
|
6 |
if(!$this->systemCommand->execute(sprintf('adduser %s', $username))) { |
7 |
throw new ExceptionCannotAddUser($username, $this->systemCommand->getFailureMessage()); |
8 |
}
|
9 |
}
|
Das Auslösen unserer eigenen Ausnahme ist nur eine Frage des Änderns des alten "throw"-Befehls in den neuen und des Sendens von zwei Parametern, anstatt die Nachricht hier zu verfassen. Natürlich bestehen alle Tests.
1 |
PHPUnit 3.7.28 by Sebastian Bergmann. |
2 |
|
3 |
.. |
4 |
|
5 |
Time: 18 ms, Memory: 3.00Mb |
6 |
|
7 |
OK (2 tests, 4 assertions) |
8 |
|
9 |
Done. |
Ausnahmen in Ihrer MVC abfangen
Ausnahmen müssen irgendwann 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 abfangen. Nachdem die Ausnahme abgefangen wurde, wird sie in eine Nachricht an den Benutzer umgewandelt und in Ihrer Ansicht gerendert. Ein üblicher Weg, dies zu erreichen, besteht darin, eine "tryAction($action)"-Methode im Basis-Controller oder -Modell Ihrer Anwendung zu erstellen und diese immer mit der aktuellen Aktion aufzurufen. Auf diese Weise können Sie die Fanglogik und die nette Nachrichtengenerierung entsprechend Ihrem Framework ausführen.
Wenn Sie kein Webframework oder keine Weboberfläche verwenden, sollte Ihre Präsentationsebene dafür sorgen, dass diese Ausnahmen abgefangen und transformiert werden.
Wenn Sie eine Bibliothek entwickeln, liegt es in der Verantwortung Ihrer Kunden, Ihre Ausnahmen zu erfassen.
Abschließende Gedanken
Das ist es. Wir haben alle Ebenen unserer Anwendung durchlaufen. Wir haben in JavaScript, HTML und in unseren Modellen validiert. Wir haben Ausnahmen von unserer Geschäftslogik geworfen und abgefangen und sogar unsere eigenen benutzerdefinierten Ausnahmen erstellt. Dieser Ansatz zur Validierung und Ausnahmebehandlung kann ohne große Probleme von kleinen bis zu großen Projekten angewendet werden. Wenn Ihre Validierungslogik jedoch sehr komplex wird und verschiedene Teile Ihres Projekts überlappende Teile der Logik verwenden, können Sie in Betracht ziehen, alle Validierungen, die auf einer bestimmten Ebene durchgeführt werden können, an einen Validierungsdienst oder Validierungsanbieter zu extrahieren. Diese Ebenen können JavaScript-Validator, Backend-PHP-Validator, Kommunikationsvalidator von Drittanbietern usw. umfassen, müssen aber nicht darauf beschränkt sein.
Danke fürs Lesen. Einen schönen Tag noch.



