Data Driven Websites mit PHP, Teil 2

Secundo Piatto

Kristian Köhntopp

Im ersten Teil des PHP-Workshops ging es um die Grundbegriffe der Sprache, die Fortsetzung beschreibt in nachvollziehbarer Weise, wie man wiederverwendbaren Code mittels Klassen erstellt. In der nächsten Ausgabe geht es dann in einem dritten Teil kurz um dynamischen Erzeugung von Web-Grafiken mit einer PHP-Bibliothek.

PHP verfügt über eine ganze Reihe unterschiedlicher Datenbankinterfaces, die sich leider aufgrund der Entstehungsgeschichte der Sprache in einigen Details unterscheiden und außerdem bequemer zu bedienen sein könnten. Mit Hilfe einer include-Datei und der objektorientierten Eigenschaften von PHP wollen wir uns ein Datenbankinterface schaffen, das mit unterschiedlichen Datenbanken auf die gleiche Weise kommunizieren kann. Anschließend geht es um das Erzeugen von Grafiken mit der GD-Bibliothek von Thomas Boutell.

Bibliotheken bauen mit Objekten

Wenn man anfängt, sich eine Bibliothek von PHP-Funktionen in include-Dateien zuzulegen, wird man schnell auf Probleme stoßen, sobald man auch Includes anderer PHP-Anwender hinzuzieht. Früher oder später wird es unweigerlich Kollisionen zwischen Funktionen oder globalen Variablen gleichen Namens geben.

Aber selbst wer nur selbstgeschriebene Funktionen einsetzt kann Probleme bekommen. Man stelle sich zum Beispiel eine Sammlung von Funktionen vor, die mit einem Datenbank-Link arbeiten. Listing 1 zeigt eine Sammlung solcher Funktionen, die eine globale Variable $Link-ID verwenden.

Wenn man sich eine Include-Datei mit diesen Funktionen definiert, kann man die query()-Funktion leicht so schreiben, dass sie prüft, ob die Link_ID definiert ist und gegebenenfalls connect() aufruft, um das Link zu initialisieren. query() könnte das Handle zum aktuellen Anfrageresultat in $Query_ID hinterlegen und next_record() könnte damit arbeiten. Damit das funktioniert, müssen diese Funktionen auf eine gemeinsame Variablen zugreifen können und da PHP keine Zeiger oder Referenzen kennt, ist es notwendig, dass es sich um eine gemeinsame globale Variable handelt.

Leider macht dies Probleme, sobald man auf einer Seite zwei verschiedene Datenbankabfragen verwenden möchte, denn diese würden sich die globalen Variablen gegenseitig überschreiben. Hätte PHP Zeigervariablen, könnte man jedem Aufruf von connect(), query() und next_record() die entsprechenden Zeiger auf die zu verwendenden Variablen mitgeben, aber dann wäre kaum etwas gewonnen: Man müsste wieder Link_IDs und Query_IDs verwalten.

PHP bietet eine andere Methode: Man kann die zu einer Datenbankabfrage gehörenden Variablen und Funktionen zu einem Paket zusammenschnüren und dem Paket einen Namen geben. Dieser Prozess selbst belegt noch keine Namen im Namensraum. Danach kann man quasi Kopien dieses Paketes unter einem wählbaren Variablennamen in den Namensraum der Sprache einhängen, ähnlich wie man eine Platte mountet. Das Schnüren eines Paketes von Variablen und Funktionen nennt man in PHP das Definieren einer Klasse und das "Mounten" dieses Paketes im Namensraum der Sprache ist das Erschaffen eines Objektes dieser Klasse mit "new". In PHP sieht das so aus wie in Listing 2 gezeigt.

Anders als in Listing 1 verbraucht die Deklaration einer Klasse keine Namen im globalen Namensraum - hinter der schließenden Klammer der class-Anweisung ist also gar nichts passiert, außer dass wir PHP erklärt haben, was alles zu einem DB_MiniSQL gehört. PHP hat jetzt also einen Bauplan für DB_MiniSQL-Objekte.

Mit Hilfe der "new"-Anweisung sagen wir PHP, dass es ein DB_MiniSQL erschaffen und unter dem Namen $db1 in den Namensraum der Sprache legen soll. Ein zweites Objekt der gleichen Klasse soll unter dem Namen $db2 angelegt werden. Anders als bei der Variante mit den globalen Variablen kommt es hier nicht zur Kollision zwischen z.B. den beiden Link_IDs, da sich die beiden existierenden Link_IDs quasi in ihren "Pfadnamen" unterscheiden: Sie werden als $db1->Link_ID und $db2->Link_ID angesprochen.

Auch bei den Funktionen müssen wir angeben, welche der beiden Datenbankverbindungen wir verwenden wollen: $db1->query() sendet eine Anfrage über das eine Link, $db2 ->query() über das andere Link.

Dadurch, dass wir als Entwickler einer Bibliothek Funktionssammlungen in Include-Dateien in einer Klasse kapseln, überlassen wir es also dem Anwender unserer Bibliothek, wie viele Anwendungen unserer Bibliothek er gleichzeitig in Betrieb hat und unter welchem Namenspräfix er unsere Funktionen in seinen Scripten einblendet. Als Anwender einer solchen Objektbibliothek müssen wir uns nur daran gewöhnen, mit "new" ein Namenspräfix für die verwendeten Funktionen zu wählen (etwa: $db1 = new DB_MiniSQL) und dann statt eines Funktionsnamens query() den Namen mit diesem Präfix zusammen zu verwenden: $db1->query() beziehungsweise $db2->query().

Um innerhalb der Funktion query() auf Variablen wie $Link_ID zuzugreifen, müsste man nun aber den eigenen Namen kennen. Schließlich müsste query() entscheiden, ob es auf $db1->Link_ID oder $db2->Link_ID oder mit einem ganz anderen Namen arbeiten soll. Das wäre allerdings übermäßig umständlich und man hat dies nicht so gelöst. Stattdessen kann innerhalb eines Objektes über das feste Präfix $this auf die eigenen Variablen und Funktionen zugegriffen werden, unabhängig davon, unter welchem Namen sie außerhalb des Objektes sichtbar sind. Eine Funktion query() in einem Datenbank-Objekt kann also ihre eigene Link_ID als $this->Link_ID ansprechen und ihre eigene Funktion connect() als $this->connect() aufrufen.

Listing 1: Eine hypothetische Sammlung von Funktionen zum Zugriff auf eine Datenbank

?php
  var $Link_ID;  // ID der aktuellen DB-Verbindung
  var $Query_ID; // ID des aktuellen Abfrageresultates
  var $Error;    // Letzte Datenbank-Fehlermeldung

  function connect() { ... }

  function query()   { ... }

  function next_record() { ... }

  function num_rows() { ... }

?>

Listing 2: Definition einer Klasse DB_MiniSQL

?php
class DB_MiniSQL {
  var $Link_ID;  // ID der aktuellen DB-Verbindung
  var $Query_ID; // ID des aktuellen Abfrageresultates
  var $Error;    // Letzte Datenbank-Fehlermeldung

  function connect() { ... }

  function query()   { ... }

  function next_record() { ... }

  function num_rows() { ... }
}

$db1 = new DB_MiniSQL;
$db2 = new DB_MiniSQL;
?>

Listing 3: Definition und Erläuterung der in der Klasse DB_Sql verwendeten Variablen.

class DB_Sql {
  var $Host     = ""; // Host, auf dem MySQL läuft
  var $Database = ""; // zu verwendetende Database
  var $User     = ""; // User und Passwort für Login
  var $Password = "";

  var $Link_ID  = 0;  // Resultat des mysql_connect()
  var $Query_ID = 0;  // Resultat des mysql_query()
  var $Record   = array();  // aktuelles mysql_fetch_array()-Ergebnis
  var $Row;           // Aktuelle Ergebniszeile

  var $Errno    = 0;  // Fehlerstatus der Anfrage...
  var $Error    = "";

  // Hier Funktionen einfügen
}

Eine Datenbankklasse als Beispiel

Wir wollen dies am Beispiel einer Klasse DB_Sql zum Zugriff auf eine MySQL-Datenbank demonstrieren [1]. Unsere Klasse soll die Variablen $Host, $Database, $User und $Pass-word enthalten, die definieren, mit welcher Datenbank auf welchem Server kommuniziert werden soll und welcher Benutzer mit welchem Passwort sich dort anmelden soll. Das Resultat dieser Anmeldung wird eine $Link_ID sein, die ebenfalls in der Datenbank gespeichert werden muss.

Anfragen an die Datenbank werden entweder ein Resultat $Query_ID erzeugen, oder Fehlermeldungen, deren Text $Error und Fehlernummer $Errno ebenfalls verfügbar gemacht werden müssen. Wenn das Resultat der Datebankabfrage durchgelesen wird, wird man eine aktuelle Zeile in einem Array $Record zwischenspeichern wollen und man wird eine Zeilennummer $Row mitführen wollen. Unsere Klasse muss also intern schon einmal die in Listing 3 gezeigten Variablen anlegen - die Funktionen, die diese Variablen mit sinnvollen Werten belegen, stehen noch aus.

Damit man mit dieser Klasse etwas Sinnvolles tun kann, wird man zunächst einmal eine Datenbankverbindung herstellen müssen. Dies kann schief gehen: Der Datenbankserver könnte nicht erreichbar sein, die gewünschte Datenbank könnte nicht vorhanden sein oder Username und Passwort sind falsch. Die Klasse muss dann einen Fehler signalisieren und das Programm anhalten. Wir definieren eine Funktion halt(), der eine Fehlermeldung übergeben wird und die diese Meldung dann ausgibt und das Programm anhält, sowie eine Funktion connect(), die versucht, die Link_ID zu initialisieren. Das Resultat ist in Listing 4 zu sehen.

Die Funktion halt() gibt zunächst einmal eine Reihe von schließenden Tags aus, die helfen sollen, falls der Datenbankfehler in einer HTML-Tabelle auftritt. In diesem Fall würden viele Browser (Netscape zum Beispiel!) den Text nicht darstellen, weil die offene Tabelle beziehungsweise die Tabellenzelle nicht geschlossen würde. Nur im HTML-Quelltext der Seite wäre die Fehlermeldung sichtbar. Danach wird die Fehlermeldung ausgegeben und die Werte der Variablen $Errno und Error des Objekts noch zusätzlich angezeigt. Die die()-Anweisung lässt den Interpreter dann anhalten, damit kein weiterer Schaden entsteht.

Die Funktion connect() testet, ob bereits eine Link_ID existiert. Wenn dies nicht der Fall ist, wird unter Verwendung der Werte aus $Host, $User und $Password versucht, eine Datenbankverbindung aufzubauen. Falls dies nicht gelingt, wird mit einem Fehler gestoppt, andernfalls wird versucht, die in $Database angegebene Datenbank mit einem use-Kommando zu aktivieren.

Listing 4: Die Funktionen halt() und connect() der Klasse DB_Sql.

function halt($msg) {
    printf("</td></tr></table>Database error:</b> %s<br>\n", $msg);
    printf("MySQL Error</b>: %s (%s)<br>\n",
      $this->Errno,
      $this->Error);
    die("Session halted.");
  }

  function connect() {
    if ( 0 == $this->Link_ID ) {
      $this->Link_ID=mysql_connect($this->Host, $this->User, $this->Password);
      if (!$this->Link_ID) {
        $this->halt("Link-ID == false, connect failed");
      }
      if (!mysql_query(sprintf("use %s",$this->Database),$this->Link_ID)) {
        $this->halt("cannot use database ".$this->Database);
      }
    }
  }

Listing 5: Eine (nicht empfohlene) Möglichkeit, DB_Sql anzuwenden.

?php
  // Definiert die DB_Sql-Klasse
  require("db_mysql.inc");

  // $db ist unser Datenbankobjekt
  $db = new DB_Sql;

  // Belegen der Connectparameter
  $db->Host     = "localhost";
  $db->User     = "kris";
  $db->Password = ""
  $db->Database = "wahl";

  // Verbinden mit der Datenbank.
  $db->connect();

 ?>

Klassen erweitern

Unsere Klasse ist nun schon benutzbar, wenn auch nicht in sinnvoller Weise. Man kann eine Datenbankverbindung aufbauen, aber ohne query()- und next_record()-Funktionen leider keine Werte abfragen. Wir wollen die Klasse dennoch schon einmal einsetzen, um uns mit ihrem Gebrauch vertraut zu machen. Listing 5 zeigt eine Methode, mit der man die Klasse verwenden könnte.

Diese Methode ist jedoch nicht sehr empfehlenswert: Nach der Erzeugung des Objektes $db müssen seine Connectparameter einzeln und für jede Seite neu belegt werden, auf der DB_Sql verwendet wird. Es wäre schöner, wenn wir eine neue Datenbank-Klasse definieren könnten, die genauso ist wie DB_Sql, nur mit anderen Connect-Parametern. Tatsächlich können wir das: Klassen können erweitert, und vorhandene Klassendefinitionen dabei verwendet werden. Listing 6 zeigt die Definition einer Klasse DB_Wahl, die denselben Connect durchführt wie in Listing 5 gezeigt. Listing 6b zeigt das zu Listing 5 äquivalente Programm unter Verwendung von DB_Wahl.

DB_Wahl ist nicht leer, sondern verfügt über genau dieselben Variablen und Funktionen wie DB_Sql, auch wenn diese in der Definition von DB_Wahl nicht mit aufgeführt sind. Erkennbar ist dies an der Deklaration der Klasse: DB_Wahl extends DB_Sql, das heißt zunächst einmal ist DB_Wahl ganz genauso wie DB_Sql definiert. In der weiteren DB_Wahl-Definition werden nun aber die Startwerte einiger Variablen überschrieben, nämlich mit den Connectparametern für die "Wahl"-Datenbank.

Wenn jetzt DB_Wahl verwendet wird, wie in Listing 6b gezeigt, wird eine Datenbankverbindung mit diesen Parametern aufgebaut. Anders als in Listing 5 müssen diese Parameter aber nicht mehr in jeder Datei neu aufgeführt werden, sondern können zentral an einer Stelle (der Definition von DB_Wahl in local.inc) gepflegt werden - besonders bei großen Projekten sehr empfehlenswert.

Abfragen und Abfrageresultate

Listing 7 zeigt drei Funktionen, mit denen man DB_Sql dann endlich einer sinnvollen Verwendung zufügen kann: Nun ist es nämlich möglich, tatsächlich Anfragen an eine Datenbank zu senden und Resultate abzuholen. Die Funktion query() ruft zu diesem Zweck erst einmal connect() auf. connect() wird eine Datenbankverbindung herstellen und die passende Datenbank auswählen, wenn dies noch nicht geschehen ist. Auf diese Weise sparen wir uns den manuellen Aufruf von connect() vor der ersten Anfrage auf einer Seite und können stattdessen einfach query() verwenden, ohne weiter nachdenken zu müssen.

Die auskommentierte Anweisung enthält Code, der die aktuelle Query ausdruckt. Wenn wir eine Anwendung debuggen müssen, kann es nützlich sein, alle SQL-Anfragen zu Gesicht zu bekommen. Durch Entfernen des Kommentarzeichens ist das leicht möglich.

Beim Absetzen einer neuen Query wird eine neue Query_ID erzeugt und die aktuelle Ergebniszeile auf Null gesetzt. Danach muss abgefragt werden, ob die Query legal war, das heißt, ob sie gültiges SQL enthielt: Errno und Error werden aktualisiert. Wenn dabei ein Fehler auftrat, wird das Programm mit einer Fehlermeldung angehalten. Andernfalls wird die Query_ID an den Abfrager zurückgegeben.

Die next_record()-Funktion kann dann eingesetzt werden, um die Ergebnisse der Anfrage abzuholen. Sie liest jeweils eine Zeile des Ergebnisses, führt den Zeilenzähler mit und prüft auf das Auftreten von Fehlern. Wird das Ende des Anfrageergebnisses erreicht, das heißt ist $this->Record kein Array mehr, sondern leer, wird das Anfrageergebnis freigegeben, um Speicher zu sparen. next_record() liefert "true", solange noch Ergebnisse vorliegen und kann daher in einer while()-Schleife eingesetzt werden.

Mit Hilfe der seek()-Funktion ist es möglich, innerhalb einer Ergebnistabelle zu springen und so ein Anfrageergebnis mehrfach zu lesen oder Zeilen am Anfang der Ergebnistabelle zu überspringen. Listing 8 zeigt eine mögliche Anwendung von query() und next_record(), um Daten in einer Tabelle abzufragen.

Ein Webserver (zum Beispiel das für diesen Kurs erstellte www.wahl.de [5]) enthalte Bannerwerbung mit Verweisen auf die Webserver von Inserenten. Die Werbebanner liegen als Gif-Bilder vor, deren Pfadnamen bekannt sind. In der Datenbank ist in einer Tabelle "inserenten" hinterlegt, welche Kunden Bannerwerbung auf diesem Server geschaltet haben und bei jedem Aufruf der Webseite sollen umlaufend Werbebanner ausgesucht und angezeigt werden. Die Inserententabelle enthält zu jedem Werbebanner eine Kennung, den Pfadnamen der Gif-Datei und den Link zum Server des Kunden sowie den zu verwendenden Alt-Text.

Listing 8 zeigt, wie mit Hilfe der DB_Wahl-Klasse diese Tabelle durchgelesen werden kann. Ausgegeben wird eine HTML-Tabelle aller Kundenkennungen mit den zugehörigen Bannerinformationen. Listing 8b zeigt die Definition der Tabelle "inserenten" sowie der Tabelle "bannerwechsel". Die "bannerwechsel"-Tabelle enthält nur eine Zeile mit nur einer Spalte (also nur eine Zahl), nämlich die aktuelle Bannernummer. Das Banneranzeigeprogramm verwendet diese Information, um die Bannerrotation zu steuern.

Der Bannerrotator (Listing 9) besteht aus einer Funktion banner_rotate(), die nichts weiter macht, als den Zähler in der "bannerwechsel"-Tabelle weiterzuzählen und auszulesen, um dann einen Image-Tag für das passende Banner zu generieren. Das dabei auftretende Locking ist MySQL-spezifisch (MySQL kennt keine richtigen Transaktionen).

Die Funktion ist ziemlich linear: Sie sperrt die "bannerwechsel"-Tabelle und bewegt den Zähler um eine Position weiter. Danach liest sie den aktuellen Zählerstand aus und entsperrt die Tabelle wieder. Mit dem Zählerstand, der modulo der Anzahl der Inserenten begradigt wird, wird aus der Tabelle "inserenten" der passende Inserent herausgesucht und ein Image-Tag erzeugt, der in ein Link verpackt ist. Dabei darf nicht direkt in die Präsentation des Inserenten gesprungen werden, sondern es muss ein Verweis auf ein weiteres lokales Programm erzeugt werden, das Klicks auf das Banner in der Datenbank registriert und dann erst mittels eines Redirects zur Präsentation des Inserenten verzweigt. Nur auf diese Weise kann dem Inserenten auch die Wirksamkeit seiner Werbung bewiesen werden.

Das hier nicht gezeigte jump.php3-Script bekommt als Parameter die Kennung des geklickten Banners als GET-Parameter übergeben. Im Programm steht dieser als $kennung zur Verfügung. Mittels dieser Information kann das Programm die Information link aus der Tabelle "inserenten" extrahieren und einen Location-Header() dorthin generieren. Danach kann es in einer weiteren Tabelle "banner" Informationen über den Browser des Anklickers verewigen (etwa den Hersteller des Browsers, das Betriebssystem, auf dem er läuft, die Sprache und Version des Browsers sowie die $REMOTE_ADDR desjenigen, der geklickt hat). Diese Informationen können dann mit PHP abgefragt und aufbereitet werden.

Um die Klasse DB_Sql abzurunden [2], werden in Listing 10 noch einige Funktionen definiert, die den Zugriff auf die Resultate einer Query vereinfachen: Die Funktionen num_rows() und num_fields() geben die Höhe und Breite der Ergebnistabelle aus. Die Funktionen f() und p() können verwendet werden, um auf einzelne Felder der aktuellen Ergebniszeile zuzugreifen. Im nächsten Heft werden wir zeigen, wie man aus diesen Daten mit Hilfe von Thomas Boutells Grafikbibliothek [4] Diagramme erstellt. (uwo)

Listing 6: Erstellung einer neuen Klasse DB_Wahl aus DB_Sql.

// DB_Wahl ist genau wie DB_Sql, nur anders... :-)

class DB_Wahl extends DB_Sql {
  var $Host     = "localhost";
  var $User     = "kris";
  var $Password = "";
  var $Database = "wahl";
}

Listing 6b: Anwendung von DB_Wahl.

// Enthält DB_Sql.
require("db_mysql.inc");
// Enthält DB_Wahl
require("local.inc");

// Erstelle ein Datenbankobjekt
$db = new DB_Wahl;

// Verbinde mit der Datenbank
$db->connect();

Listing 7: Die Funktionen query(), next_record() und seek() von DB_Sql.

function query($Query_String) {
    $this->connect();

#   printf("Debug: query = %s<br>\n", $Query_String);

    $this->Query_ID = mysql_query($Query_String, $this->Link_ID);
    $this->Row   = 0;
    $this->Errno = mysql_errno();
    $this->Error = mysql_error();
    if (!$this->Query_ID) {
      $this->halt("Invalid SQL: ".$Query_String);
    }

    return $this->Query_ID;
  }

  function next_record() {
    $this->Record = mysql_fetch_array($this->Query_ID);
    $this->Row   += 1;
    $this->Errno = mysql_errno();
    $this->Error = mysql_error();

    $stat = is_array($this->Record);
    if (!$stat) {
      mysql_free_result($this->Query_ID);
      $this->Query_ID = 0;
    }
    return $stat;
  }

  function seek($pos) {
    $status = mysql_data_seek($this->Query_ID, $pos);
    if ($status)
      $this->Row = $pos;
    return;
  }

Listing 8: Abfrage der Tabelle "inserenten" in der Datenbank "wahl".

<?php
  require("db_mysql.inc"); // DB_Sql
  require("local.inc");    // DB_Wahl

  $db = new DB_Wahl;
  $query = "select kennung, grafik, link, beschreibung from inserenten";
  $db->query($query);
 ?>
html>
body bgcolor="#ffffff">

table border=1 bgcolor="#eeeeee">
 <tr>
  <th>Kennung</th>
  <th>Grafik</th>
  <th>Link</th>
  <th>Beschreibung</th>
 </tr>

?php while($db->next_record()): ?>
 <tr>
  <td><?php print $db->Record["kennung"] ?></td>
  <td><?php print $db->Record["grafik"] ?></td>
  <td><?php print $db->Record["link"] ?></td>
  <td><?php print $db->Record["beschreibung"] ?></td>
 </tr>
?php endwhile ?>
/table>
/body>
/html>

Listing 8b: Definition der Tabelle "inserenten".

CREATE TABLE inserenten (
  id int(11) DEFAULT '0' NOT NULL auto_increment,
  kennung varchar(127) DEFAULT '' NOT NULL,
  grafik varchar(127) DEFAULT '' NOT NULL,
  link varchar(127) DEFAULT '' NOT NULL,
  beschreibung varchar(127) DEFAULT '' NOT NULL,
  PRIMARY KEY (id),
  KEY kennung (kennung),
);

CREATE TABLE bannerwechsel (
  pos int(11) DEFAULT '0' NOT NULL,
);

Listing 9: Funktion banner_rotate() zum Wechseln von Werbebannern.

<?php
function banner_rotate() {
  global $db; // Setzt voraus, dass ein globales Datenbank-Objekt existiert.

  $max_inserent = 4; // Konfigurier mich!

  $db->query("lock tables bannerwechsel");              // Lock setzen
  $db->query("update bannerwechsel set pos = pos + 1"); // Banner wechseln
  $db->query("select pos from bannerwechsel");          // Zähler auslesen
  $db->next_record();
  $pos = $db->Record["pos"];
  $db->query("unlock tables");                          // Lock entfernen

  // Inserent zum aktuellen Zählerstand (mod $max_inserent) suchen
  $query = sprintf("select * from inserenten where id = '%s'",
    $pos % $max_inserent);
  $db->query($query);
  $db->next_record();

  // Link und Image ausgeben
  printf("<a href=\"jump.php3?kennung=%s\"><img src=\"%s\" alt=\"%s\" width=468 height=60 border=0></a>",
    $this->Record["kennung"],
    $this->Record["grafik"],
    $this->Record["beschreibung"]);
}

?>

Listing 10: Weitere Funktionen der Klasse DB_Sql.

<?php
  function num_rows() {
    return mysql_num_rows($this->Query_ID);
  }

  function num_fields() {
    return mysql_num_fields($this->Query_ID);
  }

  function f($Name) {
    return $this->Record[$Name];
  }

  function p($Name) {
    print $this->Record[$Name];
  }
?>

Infos

[1] Der vollständige Code zur Klasse DB_Sql ist Bestandteil der Bibliothek PHPLIB (http://www.phplib.shonline.de) und kann von der PHPLIB-Site geladen werden. PHPLIB enthält außerdem Versionen von DB_Sql für Postgres, ODBC und Oracle.
[2] Der vollständige Code zur Klasse DB_Sql aus [1] enthält noch einige Funktionen mehr, die für das Beispiel hier aber nicht von Bedeutung sind.
[3] PHPLIB Version-6 enthält eine Klasse, die außerdem Liniendiagramme erzeugen kann.
[4] libgd, http://www.boutell.com/
[5] http://www.wahl.de, ein Praktikumsprojekt im Rahmen eines Industriepraktikums bei SH Online.

Copyright © 2000 Linux New Media AG