Wider den Spaghetti-Code
Mit der endlich „richtigen“ Objektorientierung, wie PHP sie in der Version 5 bietet, können Webentwickler Projekte deutlich klarer strukturieren als bisher. Darüber hinaus lässt sich durch die Trennung von HTML- und Programmcode eine Qualitätssteigerung erreichen. Ein Baukasten für kleine und mittlere Anwendungen zeigt, wie es funktioniert.
- Susanne Pfeiffer
Das Design einer PHP-Webanwendung beeinflusst deren Qualität entscheidend. PHP hatte in dieser Hinsicht lange einen schlechten Ruf durch die Verquickung von HTML- und PHP-Code. Gutes Design ermöglicht die Trennung der beiden und fördert dadurch die Aufgabenteilung zwischen Designer und Programmierer. Darüber hinaus sorgt es für eine Strukturierung des PHP-Codes, die zu einer besseren Wartbarkeit und höheren Robustheit der Anwendung beiträgt. Dieser Artikel stellt einen PHP-5-basierten Baukasten für Webanwendungen vor, dessen Bestandteile die Grundlage für ein einfaches, schichtenorientiertes Softwaredesign liefern.
Es basiert auf PHP 5 und benötigt einige mit dieser Version neu eingeführte Eigenschaften zur Unterstützung objektorientierter Konzepte. Ein guter Überblick über sie findet sich in der PHP-5-Changes-Datei. Der Baukasten orientiert sich an einem 4-Schichten-Modell, bestehend aus Darstellungsschicht, Applikationslogik, Fachdomänen- und Infrastrukturschicht.
Für die Bereitstellung der Benutzeroberfläche ist die Darstellungsschicht zuständig, die Applikationsschicht für die Ablauflogik, die Fachdomänenschicht enthält die für die Anwendung relevante Fachlogik, und in der Infrastrukturschicht liegen die Schnittstellen zu systemnahen oder externen Komponenten. Für eine detaillierte Diskussion des Vier-Schichten-Modells siehe beispielsweise den Band von Eric Evans.
Aus den folgenden Bestandteilen setzt sich der Baukasten zusammen (schematisch in Abbildung 1):
- Darstellungsschicht aus statischen HTML-Seiten (mit isolierten PHP-EinschĂĽben) sowie eine Hilfsklasse zur Generierung komplexer HTML-Tags;
- Applikationsschicht aus ausfĂĽhrbaren PHP-Skripten zur Ablaufsteuerung
- Fachklassen, die die Fachlogik beinhalten:
- Klassen zur Kapselung der Datenbankzugriffe und des Session-Managements in Fachdomäne und Infrastrukturschicht;
- Klassen fĂĽr das Exception Handling und Logging (Letzteres in diesem Artikel nicht berĂĽcksichtigt).
Üblicherweise lässt eine solche Architektur nur Zugriffe innerhalb einer Schicht oder von einer in die direkt darunterliegende zu, um die Abhängigkeiten zwischen den Schichten zu minimieren. Jede Klasse ist im Allgemeinen genau einer Schicht zugeordnet. Um den Baukasten und seine Verwendung zu vereinfachen, weicht dieser Artikel von diesen Prinzipien jedoch an einigen Stellen ab. Er verwendet eine offene Architektur (relaxed architecture), die den Zugriff aus einer Schicht auf alle darunterliegenden zulässt. Insbesondere greift die Darstellungsschicht direkt auf die Fachdomäne zu. Dies führt zu einer erheblichen Vereinfachung der Ablauflogik.
Je nach Komplexität der Anwendung können die physischen und fachlichen Anteile der Datenbankzugriffsklassen sowie des Session-Managements zusammenfallen. In diesem Falle erstrecken sich diese Klassen über zwei Schichten: Fachdomäne und Infrastrukturschicht (siehe unten).
Entitäten der realen Welt
Zunächst zu den Fachklassen: Deren Instanzen repräsentieren im Programm die Entitäten der realen Welt. Dies könnten beispielsweise ein Benutzer im Zusammenhang der Benutzerregistrierung sein oder ein Produkt im Rahmen eines Online-Shops. Eine komplexe Datenbankabfrage fungierte ebenfalls als Fachobjekt, dessen Attribute die Anfragekriterien und Ergebnisliste sind.
Bei vielen Transaktionen ist nur eine einzige, jedoch unter Umständen mehrfach instanziierte Fachklasse erforderlich, beispielsweise für die Anzeige von Listen im Rahmen von Such- und Pflegetransaktionen. Darüber hinaus kann jede Fachklasse weitere Fachobjekte als Attribute enthalten. Das Fachobjekt dient als so genannter „Information Expert“, das heißt, es verwaltet alle fachlich relevanten Informationen und stellt geeignete Zugriffsmethoden bereit. Es enthält jedoch keine Informationen oder Methoden zum Lesen der Daten aus der Datenbank oder zum persistenten Speichern. Dies ist Sache der Datenbankzugriffsklassen.
Dass die Klassen keine Methoden zum Datenbankzugriff enthalten, hängt mit dem verwendeten Konzept für den Zugriff zusammen, der über einen so genannten Datenbank-Mapper realisiert ist. Die Alternative wäre die Verwendung eines Gateways oder eines aktiven Records, in diesem Falle enthielten die Fachklassen Methoden zum Schreiben, Lesen oder Löschen der Objekte (siehe dazu die Bücher von Fowler und Schlossnagle).
Zunächst hat eine Fachklasse für jede relevante Eigenschaft ein Attribut. Typischerweise entsprechen diese weit gehend den Eingabefeldern des HTML-Formulars der dazugehörigen Pflegetransaktion. Die Attribute können einzelne Variablen oder komplexe Datenstrukturen wie Felder oder wieder Objekte sein. Alle Attribute sind gemäß dem Prinzip des Information Hiding privat, sodass der Zugriff nur über entsprechende Zugriffsmethoden erlaubt ist. Der Konstruktor (anders als in PHP 4: __construct()) dient dazu, die Attribute auf Grund der als Parameter übergebenen Werte zu initialisieren. Ein Überladen mehrerer Konstruktoren wie in C++ oder Java ist wegen der Typlosigkeit von PHP leider nicht realisierbar. Man kann sich jedoch weit gehend über die Verwendung von Default-Werten oder die Typprüfung innerhalb des Konstruktors behelfen.
Die Fachklasse stellt die erforderlichen get- und set-Methoden für den Zugriff auf die privaten Attribute bereit (siehe die get/setAuthorId-Methoden in Listing 1). In vielen Fällen dürfte es pro Attribut eine get- und eine set-Methode geben. Attribute, die komplexe Strukturen darstellen, benötigen unter Umständen mehrere Methoden, um den Zugriff auf die einzelnen Attribute der jeweiligen Datenstruktur zu ermöglichen.
Listing 1: BookClass
<?php
class BookClass
{
private $authorid = 0;
private $author = null;
private $title = "";
private $subtitle = "";
public function __construct($authorid = 0,
$author = null,
$title = '',
$subtitle = '')
{
$this->authorid = $authorid;
if (isset ($author))
{
$this->setAuthor($author);
}
else
{
$this->author = new AuthorClass();
}
$this->title= $title;
$this->subtitle = $subtitle;
}
public function __destruct () {}
public function setAuthorId ($authorid)
{ $this->authorid = $authorid; }
public function getAuthorId ()
{ return $this->authorid; }
public function checkAuthorId()
{ return $this->authorid > 0; }
public function setAuthor($author)
{
if ($author instanceof AuthorClass)
{$this->author = $author;}
else
{
// Error Handling
}
}
public function getAuthor (){ return $this->author; }
public function checkAuthor()
{return $this->author->checkLastname()
&& $this->author->checkFirstname();}
// get- und set-Methoden fuer title und subtitle
// ...
public function isValid()
{
return $this->checkAuthorId()
&& $this->author->isValid()
&& $this->checkTitle()
&& $this->checkSubtitle();
}
public function isEmpty()
{
return ( ($this->authorid == 0)
&& $this->author->isEmpty()
&& ! $this->title
&& ! $this->subtitle );
}
}
?
Sinnvoll dĂĽrfte in der Regel die Bereitstellung geeigneter Methoden zur GĂĽltigkeitsprĂĽfung der im Konstruktor oder ĂĽber die set-Methoden gesetzten einzelnen Attribute sein. Ebenfalls hilfreich ist eine Methode zur ĂśberprĂĽfung, ob ein Objekt leer ist; ein solches Objekt gibt beispielsweise die Datenbankzugriffsklasse zurĂĽck, wenn sie keinen passenden Eintrag gefunden hat.
Sind die Fachobjekte persistent zu speichern, benötigt man in der Regel eine ID, über die das DBMS den Eintrag identifizieren kann. Diese ID kann die Fachklasse als Attribut aufnehmen oder separat mitführen. Üblicher ist die ID als Attribut. Im Rahmen dieses Baukastens kommt jedoch das separate Mitführen im jeweiligen Zusammenhang zum Einsatz, weil dieses Vorgehen zu einer klaren Trennung der fachlichen, datenbankunabhängigen Informationen von den technischen Datenbankschlüsseln führt.
Um die Beziehung zwischen Datenbankobjekten abzubilden, mĂĽssen bestimmte PHP-Objekte gegebenenfalls FremdschlĂĽssel aus der Datenbank als Attribute speichern. In Listing 1 ist daher die Datenbank-ID des Autors Attribut der Buch-Klasse.
Der durch das separate Mitführen der ID entstehende Mehraufwand macht sich durch größere Klarheit und Redundanzfreiheit bei den noch zu beschreibenden Datenbankzugriffsklassen bezahlt. Listing 1 zeigt die Implementierung einer Fachklasse, die ein Buch repräsentiert (BookClass). Außer einem Autor-Objekt enthält die Buch-Klasse die ID des Autors als Attribut.
Die Datenbankzugriffsklassen sind für das Mapping zwischen den Fachobjekten und den zugrunde liegenden Datenbankeinträgen zuständig. Sie sollen die Datenbankzugriffe kapseln und eine einfache Schnittstelle bereitstellen. Nur hier existieren SQL-Statements: Ziel ist die Kapselung der technischen Aspekte der Datenbankzugriffe, das heißt die nach außen hin bereitgestellten Methoden sollen unabhängig vom verwendeten Datenbanksystem sein. Für die Darstellung der Zugriffsklassen sei angenommen, dass ein relationales DBMS die Daten speichert, denkbar sind jedoch auch andere Formen zur persistenten Speicherung (Textdateien oder objektorientierte DBMS).
Es gibt zwei gegensätzliche Ansätze für den Datenbankzugriff: durch einen Daten-Mapper oder einen aktiven Record beziehungsweise ein Gateway. Bei letzterem Ansatz stellen die Fachobjekte Methoden zum Lesen, Speichern und Löschen der Daten bereit. Bei Verwendung eines Daten-Mappers wird die Übersetzung der Daten aus dem DBMS in die Fachobjekte ausgelagert (separate Klasse). In diesem Fall stellen die Fachklassen keine eigenen Methoden für den Zugriff bereit. Für eine ausführliche Besprechung dieser Konzepte siehe wiederum die beiden Bücher im Literaturverzeichnis der Printausgabe.
Alle Zugriffe in einer Klasse
Der Baukasten realisiert den Datenbankzugriff durch einen Daten-Mapper. Bei der Verwendung eines Daten-Mappers stellt sich die Frage der Schichtenzugehörigkeit. Üblich ist die Zuordnung zur Infrastrukturschicht. Das ist aber schwierig, da Mapper auf die Fachklassen und damit auf Klassen der darüberliegenden Schichten zugreifen, was in einer Schichten-Architektur nicht vorkommen darf. Daher sind in diesem Artikel diejenigen Klassen und Methoden, die auf die Fachobjekte zugreifen, der Fachdomänen-Schicht zugeordnet. Die Klassen und Methoden für den Datenbankzugriff gehören zur Infrastrukturschicht. Falls Fachobjekt- und Datenbankzugriff innerhalb derselben Klasse realisiert sind - was meist der Fall sein dürfte -, erhält man eine schichtenübergreifende Klasse. Eine weiterführende Beschreibung, wie sich schichtenübergreifende Klassen verwenden lassen, findet sich in Coldeweys und Kellers Vortrag.
Den genauen Aufbau des objektorientierten PHP-5-Baukastens sowie eine Beschreibung der weiteren Klassen finden Sie in der Print-Ausgabe von iX 4/2005. (hb)