
Embperl dient dazu, in HTML-Seiten eingebettete Perl-Fragmente auszuwerten, wenn der Server die Seite ausliefert. Zur Erstellung kann man normale HTML-Editoren nutzen. Den Perl-Code führt der Server entweder nur aus, oder er schreibt das Ergebnis der Ausführung an die entsprechende Stelle der HTML-Seite. Ersteres erreicht man, indem man die Perl-Befehle zwischen die Begrenzer [- und -] schreibt, Letzteres, indem man [+ und +] verwendet. Außerdem stehen verschiedene Metakommandos wie if und foreach zur Verfügung, die eine Ablaufsteuerung ermöglichen. Sie müssen zwischen den Begrenzern [$ und $] stehen.
Alle Datenbankzugriffe erledigt dabei DBIx::Recordset. Dieses Modul soll dem Programmierer Standardaufgaben abnehmen, die beim DBI-Einsatz anfallen. Das kleine Beispiel in Listing 1 illustriert einige der grundlegenden Funktionen beider Module. Wenn man es mit dem Namen einer Datenbanktabelle aufruft, stellt es deren Inhalt als HTML-Tabelle da.
Um den Inhalt der Tabelle adressen anzuzeigen, könnte man die Seite so aufrufen: http://www.domain.de/pfad/zu/beispiel1.epl?!Table=adressen. Alle Query-Parameter platziert Embperl im Hash %fdat, das heißt fdat{!Table} enthält den Text adressen. Außerdem ersetzt das Modul den Code zwischen [+ und +] durch das Ergebnis des Ausdrucks. Die Überschrift der HTML-Seite lautet im Beispiel deshalb: Inhalt der Tabelle adressen. Embperl führt den folgenden [- -] Block lediglich aus, im zum Browser geschickten Ergebnis erscheint jedoch nichts davon. Die erste Zeile des Blocks legt die zu benutzende Datenbank fest und verwendet dazu die für DBIs connect() gebräuchliche Syntax. Ließe man diese Zeile weg, müsste man die Datenbankinformationen per !DataSource-Parameter übergeben. Aus Sicherheitsgründen sollte man darauf verzichten und die Daten fest in der HTML-Seite kodieren.
Als Nächstes ruft das Skript die Methode Search aus DBIx::Recordset auf. Verwendet man wie hier die Klassenmethode, konstruiert sie ein neues Objekt und führt mit den übergebenen Parametern direkt eine Suche in der Datenbank aus. Man kann diese beiden Schritte auch trennen. Dazu ruft man zuerst die Klassenmethode Setup() auf, um das Objekt zu konstruieren. Mit dessen Hilfe ist die Instanzmethode Search() zum Suchen benutzbar. Im Beispiel werden keine weiteren Parameter übergeben, sodass es als Ergebnis die komplette Tabelle liefert. DBIx::Recordset setzt den Aufruf intern in den SQL-Befehl SELECT * FROM adressen um.
Die vorletzte Zeile des [- -] - Blocks dient dazu, die Feldnamen der Tabelle zu erfahren. Während der Konstruktor ein Typeglob (*set) als Ergebnis liefert, bedient sich der Methodenaufruf von Names eines Skalars ($set). Über den Umweg des Typeglob kann DBIx::Recordset gleichzeitig einen Skalar, ein Array und einen Hash zurückliefern. Es ist jedoch auch möglich alle drei getrennt zu konstruieren.
Auf den ersten Blick folgt nun eine banale HTML-Tabelle. Embperl expandiert die nächsten Zeilen jedoch so, dass am Ende der vollständige Inhalt der Datenbanktabelle erscheint. $names enthält eine Referenz auf ein Array der Feldnamen. Die von Embperl bereitgestellte magische Variable $col wird automatisch so lange hochgezählt, bis das Ergebnis (der Rückgabewert) des Blocks, in dem sie vorkommt, undefiniert ist. Dabei wiederholt das Programm gleichzeitig die den Ausdruck umschließenden <th>- beziehungsweise <td>-Tags. Im Falle einer Tabelle mit den drei Spalten name, vorname und ort sähe das Ergebnis so aus:
<th>name</th><th>vorname</th><th>ort</th>
Damit wäre die Kopfzeile fertig, und weiter gehts mit den eigentlichen Feldinhalten. An dieser Stelle kommt der Array-Teil des von Search gelieferten Typeglob ins Spiel. Der Zugriff auf das Ergebnis der SQL-Abfrage erfolgt über @set, wobei jedes Element dieses Arrays eine Zeile der Datenbanktabelle enthält. Allerdings lädt DBIx::Recordset nicht das gesamte Abfrageergebnis in den Speicher, sondern fordert nur die angesprochenen Datensätze von der Datenbank an. Dieser Vorgang bleibt jedoch für den Benutzer transparent. Die Zeilen sind nun wiederum als Hash gespeichert, dessen Schlüsselfelder den Feldnamen entsprechen. Derselbe Mechanismus, der in der Kopfzeile beim Expandieren der Spalten half, funktioniert hier zweidimensional mit $row als Zeilen- und $col als Spaltenzähler.
Eine einfache Tabellenausgabe lastet das Skript jedoch nicht aus: Mit weiteren Query-Parameter beim Aufruf der Seite kann man Teile der Tabelle auswählen und anzeigen. So würde http://www.domain.de/pfad/zu/beispiel1.epl?!Table=adressen&ort=Berlin dazu führen, dass Embperl nicht nur !Table, sondern auch ort mit Hilfe des Hash %fdat übergibt. Da ort dem Namen eines Tabellenfeldes entspricht, interpretiert DBIx::Recordset ihn als Parameter für den WHERE-Teil der SELECT-Anweisung und erzeugt den SQL-Befehl
SELECT * FROM adressen WHERE ort=Berlin;
Programmierer müssen sich dabei nicht um Datentypen und das Setzen von Anführungszeichen bei Zeichenketten kümmern, diese Arbeit nimmt ihnen DBIx::Recordset ab.
Kompliziertere Anfragen sind genauso einfach zu erstellen. Mit dem folgenden Formular beispielsweise kann der Anwender entweder einen Namen oder einen Ort eingeben:
<form action=/pfad/zu/beispiel1.epl method=GET><input type=text name=+name|ort><input type=hidden name=!Table value=adressen><input type=submit></form>
Wenn der Anwender nun in das Eingabefeld Richter eingibt, erzeugt DBIx::Recordset folgende SQL-Anfrage:
SELECT * FROM adressen WHERE name=Richter OR ort=Richter
Nach diesem Prinzip kann man durch Variieren der Parameter beliebig komplexe Bedingungen erzeugen. Man kann also dieselbe Seite von verschiedenen Stellen aus mit unterschiedlichen Suchanfragen aufrufen.
In der Praxis dürften die bisher betrachteten einzelnen Tabellen wohl selten vorkommen. Deshalb bietet DBIx::Recordset einige Methoden für den leichteren Umgang mit mehreren Tabellen. Die Parameter !TabJoin oder !TabRelation veranlassen das Modul, zwei oder mehr Tabellen per JOIN zu verbinden, sofern vom Datenbanksystem unterstützt. Das Ergebnis erscheint wie eine einzelne Tabelle.
Noch interessanter ist es, Verbindungen in der Tabellenstruktur selbst zu definieren. Als Beispiel sei eine Tabelle mit Adressen in zwei neue zerlegt: Eine mit Vornamen, Namen et cetera und eine mit Ortsnamen. Für die Verbindung sorgt ein spezielles Feld. DBIx::Recordset findet so einen Zusammenhang automatisch, falls die Tabellenfelder wie im folgenden Fall nach bestimmten Regeln benannt sind. Andernfalls muss man dem Modul zunächst mitteilen, wie die Tabellen innerhalb einer Datenbank logisch verknüpft sind.
Tabelle personen: vorname, name, staedte_id Tabelle staedte: id, ort
In personen informiert staedte_id das Modul darüber, dass es das Feld id in der Tabelle staedte zum Verbinden benutzen soll. Damit ist es jetzt möglich, jeder Person eindeutig einen Ort zuzuordnen und jedem Ort eine Liste von Personen. Mit einer kleinen Modifikation des ersten Beispiels liefert nun http://www.domain.de/beispiel2.epl?!Table=personen dasselbe Ergebnis wie vorher.
[- use DBIx::Recordset ; $db = DBIx::Database ->new (dbi:mysql:test) ; $db ->TableAttr (staedte, !NameField, ort) ; $fdat{!DataSource} = $db ; $fdat{!LinkName} = 3 ; *set = DBIx::Recordset ->Search(\%fdat) ; $names = $set ->Names ; $maxcol = 30 ; -]
Neu ist hier das Objekt DBIx::Database. Es ermittelt und speichert Metainformationen über eine Datenbank. Anhand der Namen stellt es fest, dass das Feld staedte_id in der Tabelle personen auf das Feld id in der Tabelle staedte verweist. Die Methode TableAttr() stellt DBIx::Database weitere Informationen über die Datenbank zur Verfügung, die nicht automatisch ermittelt werden können. Man kann damit auch Einstellungen vornehmen, die global für alle weiteren Zugriffe gelten. TableAttr() erwartet als Argumente den Tabellennamen, den Namen des Attributs und dessen Wert. Das obige Beispiel nutzt es, um den Klartextnamen für einen Datensatz der Tabelle staedte festzulegen. Diese Initialisierungen sind nur einmal und nicht bei jedem Request erforderlich. Beim Einsatz von mod_perl könnte man diese zwei Zeilen und gegebenenfalls weitere Definitionen in einer Datei zusammenfassen, die der Server nur beim Start ausführen muss.
Neu ist außerdem der Parameter !LinkName. Er weist DBIx::Recordset an, statt des Werts, der der Verknüpfung dient (staedte_id) den Klartextnamen aus der verbundenen Tabelle (ort) als Ergebnis zu liefern. DBIx::Recordset generiert aus diesen Anforderungen einen SQL-Join, sodass nur ein SELECT-Befehl für die Abfrage nötig ist.
Was aber im umgekehrten Fall, wenn die id eines Ortes vorliegt und alle dort wohnenden Menschen anzuzeigen sind? Diese Aufgabe erledigt das in Listing 2 gezeigte Skript, das man zum Beispiel so aufrufen kann: http://www.domain.de/pfad/zu/beispiel3.epl?id=5
Da die Seite auf die Tabellen staedte und personen zugeschnitten ist, sind sie hier direkt in ihr festgelegt und werden nicht als Parameter übergeben. Search() sucht den zu den Query-Werten passenden Ort heraus. Der Befehl [+ $set{ort} +] gibt den Inhalt des Feldes ort im aktuellen Datensatz aus. Direkt nach dem Aufruf von Search ist das immer der erste gefundene. Die Personen zu diesem Ort liefert DBIx::Recordset fast von alleine: Das spezielle Feld -personen enthält nämlich ein Unterobjekt für die Tabelle personen, für die die Suchkriterien automatisch so ausgewählt sind, dass sie den Link-Bedingungen entsprechen.
DBIx::Recordset kann nicht nur Daten anzeigen, sondern auch schreibend auf die Datenbank zugreifen. Am einfachsten geht das, wenn man dem Ergebnis eines Search-Aufrufs neue Werte zuweist. So ändert $set{ort} = Köln ; den Ortsnamen des aktuellen Datensatzes. Um die Übersetzung in korrekte SQL-Befehle kümmert sich das Modul selbstständig.
Während dies für normale Programme hilfreich ist, dürften für CGI-Skripts die Methoden Insert, Update und Delete praktischer sein, die wie Search an die Seite übergebene Parameter akzeptieren. Die Methode Execute fasst alle vier Methoden zusammen und ermöglicht so, die auszuführende Aktion via CGI-Parameter zu steuern. Ein Beispiel dafür zeigt Listing 3.
Beim Aufruf zeigt diese Seite den Inhalt der voreingestellten Tabelle an. Alternativ kann man einen Tabellennamen mit dem Parameter !Table angeben. Mit dem Link am unteren Ende der Seite gelangt man zu einem Eingabeformular. Dort kann man beliebig viele Felder ausfüllen und auf Suchen klicken. Die Seite ruft sich dadurch selber auf, und Execute bekommt durch den Parameter =search die Anweisung, die passenden Datensätze zu suchen. Gibt es mehrere, erscheint eine Ergebnistabelle mit $max Datensätzen, und PrevNextForm() erzeugt unter ihr HTML-Links zum Vor- und Zurückblättern.
Voraussetzung für das Funktionieren des Skripts ist ein primärer Schlüssel in jeder Tabelle, den $fdat{!PrimKey} = id ; festlegt. Die entsprechende Tabellenspalte repräsentiert ein HTML-Link, wodurch man den Datensatz zum Bearbeiten öffnen kann. Auf andere Tabellen verweisende Felder erscheinen ebenfalls als Link. Ein Klick darauf öffnet die verbundene Tabelle beziehungsweise den verbundenen Datensatz.
Passt hingegen nur ein Datensatz auf die Suchkriterien, erscheint wieder das Eingabeformular, diesmal mit gefüllten Feldern. Man kann den Datensatz nun ändern. Die neuen Werte übernimmt die Datenbank nach Anklicken von Ändern, was den Parameter =update an Execute() übergibt. Neu (Parameter =empty) erzeugt ein leeres Formular. Nach Eingabe der Daten schreibt Hinzufügen (=insert) sie in die Datenbank. Auch das Löschen ist mit dem entsprechenden Knopf (=delete) möglich. Weitere Erläuterungen finden sich als Kommentare ([# #]-Blöcke) im Quelltext.
Embperl bietet noch mehr, zum Beispiel Sessionhandling und Modularisierung. Weitere Informationen enthält die vollständige Dokumentation nebst FAQs und einer Einführung auf der Embperl-Homepage, auch in [1]. Wer es lieber auf Deutsch mag, kann Teile davon bei http://www.ecos.de/Embperl beziehungsweise in der Kurzreferenz [2] nachlesen.
GERALD RICHTER
ist Programmierer und Netzwerkadministrator bei der Firma ecos eletronic communication services GmbH.
Literatur
[1] Lincoln Stein, Doug MacEachern; Writing Apache Modules with Perl and C; OReilly & Associates; Sebastopol/CA 1999
[2] Linda Mui, Gerald Richter; CGI - kurz & gut; OReilly-Verlag; Köln ca. September 1999
| iX-TRACT |
|
Dieser Text ist der Zeitschriften-Ausgabe 09/1999 von iX entnommen.
Parallelprogrammierung - die Kunst der Multi-Core-Nutzung
Agile ALM - agile Praktiken im Application Lifecycle Management
Webentwicklung - Applikationen für mobile Clients