Ideenschmiede

Ein weiteres tolles WordPress-Blog

30
Jun

Vorbedingungen elegant überprüfen durch php-Annotationen

Eike   |   Technik   |   0 Kommentare   |   Teilen   |  

In Webanwendungen ist die Prüfung von Bedingungen vor dem Ausführen einer Aktion oder dem Anzeigen eines bestimmten Inhalts eine wiederkehrende Standardaufgabe. Einige Beispiele dafür sind:

-  die Validierung von Parametern
-  die Prüfung, ob der Benutzer eingeloggt ist
-  die Autorisierung von Aktionen
-  das Erzwingen von SSL-Verbindungen für bestimmte Inhalte oder Aktionen

Dieser Beitrag beschreibt einen Ansatz zur einfachen Notation solcher Bedingungen mit Hilfe von Annotationen. Er kommt in einem eigenen Framework zum Einsatz, welches Grundlage unserer Webprojekte ist.

Motivation
Auslöser der Entwicklung dieses Ansatzes war die Beobachtung, dass viele Controller-Aktionen mit sehr ähnlichen Code-Abschnitten begonnen haben. Die eigentlichen Prüfalgorithmen wurden zwar in einzelne wieder verwendbare Methoden ausgelagert, dennoch begann fast jede Methode mit einem oder mehreren Aufrufen solcher Prüfalgorithmen. Zudem stehen am Beginn sehr vieler Methoden die gleichen Prüfungen – zum Beispiel „ist der Benutzer eingeloggt“. Da es allerdings immer auch Ausnahmen gibt, konnten diese Bedingungen auch nicht sinnvoll an zentrale Stellen ausgelagert werden. So erfordert zum Beispiel die Aktion „log in“ natürlich nicht, dass ein Nutzer bereits eingeloggt ist. Aus diesen Überlegungen heraus entstand der Ansatz, der in diesem Beitrag beschrieben wird.

Ansatz
Annotationen ermöglichen die Notation von zusätzlichen Informationen im Programmcode. Die hierfür verwendete Syntax ist dabei nicht zwingend Bestandteil der verwendeten Programmiersprache. So werden Annotationen häufig innerhalb von Kommentaren eingebettet und somit vom Compiler oder Interpreter der jeweiligen Sprache ignoriert (Annotationen können allerdings auch von Sprachen direkt unterstützt werden, wie das zum Beispiel bei Java der Fall ist).

Annotationen sollen meist durch Werkzeuge automatisiert verarbeitet werden. Eine sehr verbreitete Verwendung von Annotationen ist die Dokumentation von Klassen und deren Methoden (Javadoc bei Java oder auch PHPdoc für PHP).

Das für unsere Webprojekte eigens entwickelte Framework verwendet Annotationen zur Spezifikation von Vorbedingungen sowie eine Model-View-Controller-Struktur. In den Views und den Controllern kommen Annotationen zum Einsatz, um Bedingungen für die Anzeige einer Seite oder für das Ausführen einer Aktion durch den Benutzer zu notieren.

Im Folgenden liegt der Fokus auf Controller-Aktionen. Die verwendeten Konzepte funktionieren für Views analog.

Syntax
Zur Notation einer Vorbedingung verwenden wir das Tag @requires gefolgt von einer Liste von Vorbedingungen, die einen eindeutigen Namen haben. Es können beliebig viele durch Kommas getrennte Vorbedingungen notiert werden. Damit eine Aktion ausgeführt werden kann, müssen alle Vorbedingungen erfüllt sein.

Vererbung von Bedingungen
Bedingungen können sowohl an Controller-Methoden als auch an Controller-Klassen notiert werden. Die für eine Controller-Klasse definierten Bedingungen gelten für alle Aktionen des Controllers, sie werden also den jeweiligen Aktionen vererbt. Durch die Annotation einer einzelnen Methode können weitere Bedingungen zu einer einzelnen Aktion hinzugefügt werden.

Sollen Bedingungen für fast alle Aktionen eines Controllers gelten, so können diese ebenfalls für eine Klasse notiert werden. Durch die Annotation @notRequires können dann Ausnahmen für einzelne Aktionen definiert werden.

Definition und Prüfung von Vorbedingungen
Die Prüfalgorithmen für Vorbedingungen werden in einer von ConditionChecker erbenden Klasse definiert. Die Klassen erhalten für jede Vorbedingung eine Methode, deren Name sich aus dem Prefix „check“ und dem Namen der Bedingung zusammensetzt. Die zu verwendende ConditionChecker-Klasse wird global oder für einzelne Controller konfiguriert. Somit können flexibel weitere Regeln hinzugefügt oder angepasst werden.

Abhängigkeiten zwischen einzelnen Bedingungen können durch die Annotation @depends definiert werden. Sie erhält als Parameter eine Liste von Bedingungen, welche geprüft werden sollen, bevor die aktuelle Bedingung geprüft wird.

Das Framework ruft die Prüfmethoden automatisch vor Aufruf einer Controller-Aktion aus. Ist eine Bedingung nicht erfüllt, wird eine Exception geworfen. Das Framework fängt diese Exception und ruft den „conditionFailedHandler“ des Controllers auf. Dieser erhält als Parameter die Aktion, die ausgeführt werden sollte, sowie die Exception der fehlgeschlagenen Bedingung. Der „conditionFailedHandler“ kann auf die fehlgeschlagene Bedingung reagieren.  Das kann die Ausgabe eines Fehlers sein, aber es können auch Mechanismen ausgelöst werden, um die fehlgeschlagene Bedingung zu erfüllen.  So kann zum Beispiel ein Fehlschlagen der Bedingung „Benutzer ist eingeloggt“ bewirken, dass der Benutzer auf eine Login-Seite weitergeleitet wird und die jeweilige Aktion nach erfolgreichem Login automatisch erneut durchgeführt wird.

Beispiele für Vorbedingungen
Eine Klasse von Bedingungen dient der Autorisierung von Aktionen. Für jedes Recht unseres Sicherheitsmodells wird eine entsprechende Bedingung implementiert. Aktionen, die ein bestimmtes Recht erfordern, müssen somit nur noch mit der entsprechenden Bedingung annotiert werden.

Die Prüfung, ob ein bestimmtes Recht vorhanden ist, erfordert in den meisten Fällen, dass der Benutzer eingeloggt ist. „Eingeloggt“ kann als eigenständige Bedingung realisiert werden. Die Bedingungen für Rechte sind abhängig. Durch die Abhängigkeit ergibt sich auch die Reihenfolge der Prüfung.

Im folgenden Beispielcode werden die beiden Bedingungen „loggedIn“ und „isAdministrator“ definiert.

class MyConditionChecker extends ConditionChecker{
	public function checkLoggedIn(){
	...
	}

/**

*@depends loggedIn

*/

	public function checkIsAdministrator(){
	...
	}
}

Nun folgt ein einfacher Controller, der diese Bedingungen verwendet:

/**

*@requires loggedIn

*/

Class MyController extends Controller{

/**

*@notRequires loggedIn

*/

	public function actionLogIn(){
	...
	}

	public function actionCreateEntry(){
	...
	}

/**

*@requires isAdministrator

*/

	public function actionDeleteEntry(){
	...
	}
}

Der Controller definiert drei Aktionen. Zunächst wird global die Bedingung „loggedIn“ erzwungen, wodurch alle Aktionen in diesem Controller diese Bedingung grundsätzlich prüfen. Ausnahme bildet im Beispiel die Aktion „actionLogIn“. Durch die Annotation @notRequires wird diese Bedingung für diese Methode unterdrückt.

Die Methode „actionCreateEntry“ enthält keine eigene Annotation, so dass die globale Bedingung „loggedIn“ verwendet wird.

Zum Aufruf der Methode „actionDeleteEntry“ werden Administratorrechte benötigt. Der Benutzer muss hierfür ebenfalls eingeloggt sein – zum einen, weil die Bedingung „loggedIn“ im Controller definiert ist, zum anderen aber auch weil „isAdministrator“ diese als Vorbedingung definiert. Durch die Vorbedingungsrelation wird „loggedIn“ auf jedenfall zuerst geprüft. Damit wird verhindert, dass ein Benutzer die Meldung „unzureichende Rechte“ erhält, obwohl er nur vergessen hat, sich einzuloggen.

Noch keine Kommentare

Schreibe jetzt den ersten Kommentar:

Kommentar verfassen