Data Driven Websites mit PHP

Web-Baukasten

Kristian Köhntopp


Statische Websites locken keinen Hund mehr hinter dem Ofen hervor. Die wirklich sinnvollen Anwendungen im WWW sind darauf angewiesen, dynamisch erzeugten Content aus Datenbanken auszugeben. Immer mehr wird PHP für "Data Driven Websites" zum Mittel der Wahl.

D  
as Standardprodukt für dynamischen Web-Content im Open Source Bereich war lange Zeit Perl, aber im letzten Jahr hat sich PHP als meistinstalliertes Modul für den Apache Webserver als der Marktführer durchgesetzt. Aktuell ist derzeit die Version 3.0.16, aber von Version 4 gibt es bereits erste Release Candidates. Angefangen hat alles wie so oft mit einem Hack: PHP/FI war ein einfacher Hypertext-Präprozessor mit Datenbankanbindung, der von Rasmus Lerdorf entwickelt wurde, um seine Bewerbung ins Web zu stellen. Andi Gutmans und Zeev Suraski haben die Sprache dann im Rahmen eines Universitätsprojektes stark erweitert, beschleunigt und in ihre heutige an C und Java angelehnte Form gebracht. An der Entwicklung von PHP waren jedoch, wie bei Open Source Projekten üblich, sehr viele Leute in aller Welt [2] beteiligt.

Versionswirrwarr?

Für die Version 4 haben Andi und Zeev den Interpreterkern aus PHP herausgelöst und als halbkommerzielles Projekt in ihrer Firma Zend.COM weiterentwickelt. Er wird jetzt unter der Q Public License vertrieben. Zend ist dabei der Sprachkern, oder wie es die Hersteller bezeichnen, die "Scripting Engine", welche die Kontrollstrukturen wie if(), while() und for() sowie das Handling der Variablen und arithmentischen Operationen realisiert. PHP4 liefert auf diesem Kern aufsetzend ein raffiniertes Modul-Ladesystem und eine sehr weitreichende Bibliothek von Modulen. Aber der Zend-Kern ist noch vielseitiger: So wird die populäre MySQL-Datenbank derzeit um einen Zend-Interpreter als prozedurale SQL-Erweiterung bereichert.

Modul, Servlet oder CGI?

Grundsätzlich gibt es für Webanwendungen zwei Strategien für den Einsatz von PHP: Der PHP-Interpreter kann als CGI-Modul generiert werden und ist dann auf jedem Webserver einsetzbar, der CGI-Scripte ausführen kann. Diese Installationsvariante ist nicht nur sehr portabel, sondern sie erlaubt auch, ein Upgrade des Interpreters unabhängig vom Webserver upgraden und mehrere unterschiedliche Versionen von PHP auf einem Webserver einsetzen zu können. Als CGI-Programm bringt der PHP-Interpreter jedoch einen gewissen Overhead mit sich. Viel effizienter ist es, den Interpreter über die Plug-In-API seines Webservers als Bestandteil des Servers selbst ablaufen zu lassen. PHP 4 unterstützt dabei inzwischen eine ganze Reihe von Servern: Apache, AOLserver, fhttpd, Internet Information Server, Netscape Server, phttpd, roxen und thttpd. Außerdem kann der Interpreter als Servlet eingesetzt werden, etwa mit Apaches Jakarta/tomcat Engine.

PHP spricht gern mit Datenbanken

Ein wesentliches Feature von PHP ist die Datenbankintegration. Dabei gelten der Geschwindigkeit und der Flexibilität des Datenbankzugriffes besonderes Augenmerk. Entsprechend werden neben ODBC-Treibern, die praktisch alle Datenbanken inzwischen anbieten, auch die nativen APIs einer ganzen Reihe von Datenbanken unterstützt, weil hier der Kommunikations-Overhead oft geringer ist und weil einige besondere Features dieser Datenbanken über ODBC nicht erreicht werden können. PHP kann unter anderem mit Adabas-D, mSQL, MySQL [3], Oracle, Postgres, Solid, Sybase/Sybase-CT und Velocis nativ kommunizieren. Außerdem kann es dBase und filePro-Dateien sowie Unix- DBM-Dateien sowohl lesen als auch schreiben und es unterstützt die FreeODBC [4] und OpenLink ODBC APIs.ýAber PHP ist noch darüber hinaus sehr kommunikationsfreudig: Es kann mit der GD-Bibliothek von Thomas Boutell [5] PNG und JPG-Bilder dynamisch beim Abruf erzeugen und dabei über FreeType beliebige TTF-Fonts einbinden, es kann über IMAP entfernte Mailboxen über das Web managen und es spricht außerdem fließend LDAP und SNMP, so dass die Entwicklung von Webinterfaces für Netzwerk-Managementapplikationen sehr einfach wird. Außerdem beherrscht PHP natürlich FTP und HTTP, so dass die Einbindung und Kontrolle entfernter Webseiten in eigene Seiten einfach wird und so genannte "Intelligent Agents" erstellt werden können. Diese holen entfernte Webseiten, analysieren diese und erstellen Zusammenfassungen oder erzeugen automatische Aktionen auf den entfernten Seiten.

Installation und Konfiguration

Nach dem Download der Quellen für die aktuelle PHP-Version vom Webserver des PHP-Projektes [1] und dem Auspacken der Quellen lässt sich eine CGI-Version des PHP-Interpreters sehr schnell durch den Aufruf des "configure"-Scriptes erzeugen. Dabei sind eine Reihe von Schaltern und Optionen anzugeben, die dem Script die Lage unter unterschiedlichen einzubindenden Bibliotheken mitteilen. Der Autor übersetzt seine CGI-Version von PHP für SuSE Linux 6.4 mit den Optionen
    ./configure --with-mysql=/usr/local \
    --with-pgsql=/usr \
    --with-pcre \
    --with-gd=/usr \
    --with-ttf=/usr \
    --with-png-dir=/usr \
    --with-dbase=yes \
    --with-config-file-path=. \
    --enable-debug=no \
    --enable-safe-mode=no \
    --enable-url-fopen-wrapper=yes \
    --enable-track-vars=yes \
    --enable-force-cgi-redirect 
und ruft dann einfach make auf. Dabei wird eine Produktionsversion (ohne Debugcode) von PHP erzeugt, die die Datenbanken MySQL und Postgres unterstützt, dBase-Dateien lesen und über die GD-Bibliothek Grafiken erzeugen kann. Die Option - -enable-url-fopen-wrapper aktiviert außerdem die HTTP- und FTP-Downloadmöglichkeiten der Sprache und die Optionen - -enable-track-vars sowie - -enable-force-cgi-redirect sind für den sicheren Betrieb der Sprache als CGI-Modul notwendig. Das resultierende Binary wird einfach in ein CGI-Verzeichnis des Webservers kopiert. Dorthin ist auch die Datei php3.ini-dist aus dem Hauptverzeichnis der PHP3-Distribution zu kopieren und nach php3.ini umzubenennen. Die Datei enthält eine Reihe von Voreinstellungen für den PHP3-Interpreter, die für die jeweilige Anwendung angepasst werden können. Verwendet man, wie die meisten Leute, den Apache-Webserver, sind außerdem noch zwei Zeilen in der srm.conf des Servers einzutragen, die den PHP-Support aktivieren:
 AddHandler php-cgi    .php3
 Action  php-cgi    /cgi-bin/php
Die erste Zeile bewirkt, dass für Dateien mit der Endung ".php3" die Server-Action php-cgi aufgerufen wird. Die zweite Zeile definiert diese Action als das CGI-Programm, das unter der URL /cgi-bin/php erreichbar ist. Apache-intern bewirkt dies ein einfaches Rewriting von URLs der Form /test.php3 nach /cgi-bin/php/test.php3.

Abb.1: test.php - Die Anweisung phpinfo() generiert eine detaillierte Bannerseite, die alle internen Konfigurationsvoreinstellungen und alle übergebenen Parameter anzeigt.
Listing zu Bild1 :
<html>
<head><title>Testseite</title></head>
<body bgcolor="#ffffff">
<?php 
  phpinfo(); // Generierung einer Statusseite
?>
</body>
</html>

Erkenne dich selbst - PHP-Test

Der PHP-Interpreter wird also aufgerufen und bekommt über die Umgebungsvariable PATH_INFO die URL des Scriptes, hier /test.php3, mitgeteilt. Ein direkter Aufruf des Interpreters ohne durch das Rewriting zu laufen ist aus Sicherheitsgründen nicht möglich: Dafür haben wir beim configure-Durchlauf die Option --enable-force-cgi-redirect angegeben. PHP-Programme werden in den regulären HTML-Code eingebettet. Jede normale HTML-Seite ist also schon einmal ein gültiges, wenn auch sehr langweiliges PHP3-Programm. Um Code in solche Seiten einbetten zu können, existieren vier Arten von Erweiterungstags. Die empfohlene Methode und SGML-kompatible Methode besteht darin, PHP-Code in "<?php ?>"-Blöcke einzuschließen. Der Bequemlichkeit halber kann man auch die verkürzte Form <? ?> verwenden. Einige HTML-Editoren erkennen diese beiden Pseudo-Tags jedoch nicht korrekt und "korrigieren" den vermeintlich fehlerhaften HTML-Code oder schieben ihn an andere Stellen innerhalb der Datei. Um auch solche Editoren nutzen zu können, ist es auch erlaubt <script language='php'> </script> zu verwenden, um PHP-Codeblöcke einzuschließen. Auf Wunsch zahlreicher Anwender von HTML-Editoren für Windows wurde ab PHP 3.0.4 außerdem die Möglichkeit geschaffen, PHP-Programme in ASP-Tags einzubetten: "<% %>" ist ebenfalls als Programmmarkierung erlaubt, wenn man in seiner php3.ini-Datei den Schalter asp_tags = On definiert. Außerdem kann "<%= $variable %>" oder in PHP4 auch <?= $variable ?> als Kürzel für <% print variable %> verwendet werden. Die einfachste mögliche PHP-Datei sieht demnach aus, wie in Listing 1 gezeigt. Das in der Datei enthaltene HTML wird dabei so ausgegeben, wie angezeigt. PHP-Codeblöcke werden vom Interpreter ausgeschnitten und ausgeführt. Generiert der Codeblock während der Ausführung irgendwelche Ausgaben, werden diese an Stelle des ausgeschnittenen Codeblocks eingesetzt. Im Beispiel oben wird der Block "<?php phpinfo() ?>"-Block also durch die Ausgabe der Funktion phpinfo() ersetzt. Die Syntax von PHP lehnt sich sehr eng an C, Java oder Javascript an: Alle bekannten Kontrollstrukturen sind vorhanden und funktionieren so, wie man es als Programmierer gewohnt ist. Variablen werden in PHP jedoch wie in einer Unix-Shell mit einem Dollarzeichen eingeleitet. Sie brauchen nicht deklariert werden und der PHP-Interpreter kümmert sich auch um die gesamte Speicherverwaltung.

Listing 2 - Parameterübergabe
PHP3-Programmen stehen in den assoziativen Feldern $HTTP_GET_VARS,
$HTTP_POST_VARS und $HTTP_COOKIE_VARS die Parameter des Programmaufrufes zur Verfügung

<html>
<head><title>Auslesen der Parameter</title></head>

<body bgcolor="#ffffff">
<?php
  printf("<h1>Alle GET-Parameter</h1>\n");
  if ( isset($HTTP_GET_VARS))   // Teste, ob überhaupt Parameter vorhanden sind.
  {
    reset($HTTP_GET_VARS);      // Beginne am Anfang des Feldes
                                // und gehe mit each() alle Feldelemente durch.
    while(list($k, $v) = each($HTTP_GET_VARS)) {
      // $k ist der Key, $v der Value<\#201>
      printf("Name: %s  Wert: %s<br>\n", $k, $v);
    }
  }

  // Gezielter Zugriff auf ein einzelnes Element des Feldes.
  printf("Der Parameter name hat den Wert %s<br>\n",
    $HTTP_GET_VARS["name"]);
 ?>
</body>
</html>


Die Kontrollstrukturen von PHP gleichen weitgehend denen von C, C++ oder Java

if (bedingung) { Bedingungen
  anweisungen

 } else {
  anweisungen
 }


if (bedingung): Alternative Schreibweise
  anweisungen

else:
  anweisungen
 endif;


while (bedingung) { While-Schleife
  anweisungen
 }


while (bedingung): Alternative Schreibweise
  anweisungen
 endwhile;


for (startanweisung; vergleich; zählanweisung) {
  anweisungen
 }


switch ($variable) { Switch-Anweisung 

  case konstante1:
    anweisungen
  break;

  case konstante2:
    anweisungen
  break;

  default:
    anweisungen
  break;

 } 

Syntax wie gehabt

Neben den üblichen skalaren Datentypen Ganzzahl, Fließkommazahl und String, die nicht unterschieden zu werden brauchen, weil die Sprache Typumwandlungen nach Bedarf automatisch vornimmt, kennt PHP noch multidimensionale assoziative Arrays. Auch diese brauchen in der Größe nicht vordefiniert zu werden, sondern können einfach nach Bedarf verwendet werden. PHP-Scripte können Parameter über die HTTP-Methoden GET und POST erhalten und haben auch Zugriff auf Cookies. Beim Start des Scripts werden die Parameter automatisch importiert und stehen in den gleichnamigen Variablen zur Verfügung: Wird ein Script also unter der URL /test2.php3?name=Kris &host=valiant¶aufgerufen, stehen dem Script die Variablen $name und $host mit den Werten "Kris" und "valiant" vorinitialisiert zur Verfügung. Ebenso werden POST-Parameter und Cookies importiert. Wem diese Art der unkontrollierten Variablenvorbelegung sicherheitstechnisch zu aufregend ist, der kann sie über die Konfigurationsdirektive gpc_order in der php3.ini-Datei kontrollieren. gpc_order=gpc, die Default-Einstellung, bewirkt, dass beim Start des Scriptes zunächst alle GET-Parameter importiert werden, danach die POST-Parameter und als letztes alle Cookies. Durch Vertauschen der Buchstaben kann man die Importreihenfolge verändern, durch Weglapsen von Kennbuchstaben kann man den Import aus einzelnen Datenquellen abschalten. Über die assoziativen Felder $HTTP_GET_VARS, $HTTP_POST_VARS und $HTTP_COOKIE_VARS, die in jedem Fall mit den Name-Wert-Paaren der entsprechenden Scriptparameter belegt werden, haben solche Scripts dennoch Zugriff auf alle Parameter und zugleich mehr Kontrolle darüber, welche Werte wann und wie den Programmablauf beeinflussen. Listing 2 zeigt ein Script, das die $HTTP_GET_VARS durchliest. Das Script enthält ein interessantes Konstrukt, das man in PHP sehr häufig findet. Es handelt sich um die Redewendung
  reset ($arrayname); while \

  (list($k, $v) = each($arrayname)) { ... }
Diese Anweisung liest das Array $arrayname elementweise durch. Der Index des aktuellen Elementes steht dabei in der Variablen $k zur Verfügung, sein Wert in $v. In PHP sind die Elemente eines assoziativen Arrays untereinanderdurch Zeiger verbunden, so dass eine Reihenfolge erhalten bleibt, auch wenn es sich eigentlich um einen Hash handelt, der bei der Ablage der Elemente im Speicher keine Reihenfolge definiert. Außerdem hat jedes Array intern einen Zeiger auf ein aktuelles Element, den man durch diese Liste bewegen kann. Die Anweisung reset($arrayname) stellt diesen internen Zeiger auf den Anfang des Arrays zurück. Die Funktion each($arrayname) liefert nun den Index und den Wert des aktuellen Elementes als Feld mit zwei Elementen zurück und schiebt den internen Zeiger aktuelle Elemente vorwärts. list($k, $v) wiederum erzeugt aus den beiden Variablen $k und $v ein weiteres Array mit zwei Elementen, aber in der Form von "lvalues", also von Zeigern auf Speicherplätzen, denen man Werte zuweisen kann. Der erste von each() zurückgegebene Wert wird also in $k abgespeichert, der zweite von each() zurückgegebene Wert in $v.

Zugriff auf Datenbanken

Über eingebaute Abfragefunktionen ist der Zugriff auf Datenbanken in PHP leicht zu realisieren. Das Beispielprogramm in Listing 3 zeigt ein PHP-Script, das auf eine MySQL-Datenbank zugreift. Um eine Datenbankabfrage durchführen zu können, muss man zunächst einmal eine Verbindung zur Datenquelle herstellen. Im Beispiel wird die native API einer Datenbank (hier: MySQL) verwendet, aber der Zugriff über ODBC folgt im wesentlichen denselben Regeln. Die Funktion mysql_connect()stellt die Verbindung zum Datenbankserver her. Dabei muss dem Kommando der Name des Serverhosts und ein Username/Passwort-Paar mitgegeben werden. Das Resultat ist, wenn alles stimmt, die ID eines Datenbank-Links, über das man weitere Kommandos senden kann. In der Regel wird man auf einem Datenbankserver eine ganze Reihe von Datenbanken haben. Das Beispielscript oben wählt zunächst mit dem "use"-Kommando eine Datenbank aus, indem es mit Hilfe von mysql_query() ein entsprechendes Kommando über das Datenbank-Link sendet. Danach wird ein weiteres Kommando, ein SQL-Select-Kommando über das Link an den Datenbankserver abgeschickt. Weitere API-Funktionen erlauben es dann, die Breite und Höhe der Ergebnistabelle zu bestimmen und auszugeben. Schließlich wird die API-Funktion mysql_fetch_array() verwendet, um nacheinander jeweils eine Zeile des Ergebnisses einzulesen und dem PHP-Script als assoziatives Array $d zur Verfügung zu stellen. Die Funktion liefert jeden Spaltenwert der aktuellen Zeile zweimal: Einmal über die Spaltennummer (0,1,…) indiziert und einmal über den Spaltennamen (username, password, …) indiziert. Innerhalb der Schleife kann man auf Werte in der aktuellen Zeile entweder über $d[0] oder $d["username"] zugreifen, oder man zählt die Elemente des Feldes $d wieder auf die in PHP übliche Weise auf. Es ist ein leichtes, die Ausgabe so umzustellen, dass statt einfacher <br>-Tags auch komplizierteres HTML (etwa eine Tabelle) erzeugt wird.

Listing 3 - Abfrage einer MySQL-Datenbank mit PHP

<html>
<?php
  // Verbindung zur Datenbank auf localhost als
  // User kris ohne Passwort.
  $link = mysql_connect("localhost", "kris", "");

  // Auswahl der zu verwendenden Datenbank auf dem Server
  $query = "use poe_sessions";
  if (!mysql_query($query, $link))
    die("Datenbank poe_sessions existiert nicht.<br>\n");

  // Auslesen der Tabelle auth_users in dieser Datenbank
  $query = "select username, password, perms from auth_users";
  $res = mysql_query($query, $link);
  if (!$res)
    die("Anfrage $query scheitert.<br>\n");

  // Bestimme Größe des Ergebnisses
  $rows = mysql_num_rows($res);
  $cols = mysql_num_fields($res);
  printf("Anfrage ergibt %d Zeilen zu %d Spalten.<br>\n", $rows, $cols);

  // Durchlesen des Ergebnisses
  while($d = mysql_fetch_array($res)) {

    // $d ist ein Array aus Spaltenname, aktuellem Wert
    reset($d);
    while(list($k, $v) = each($d)) {
      printf("%s = %s, ", $k, $v);
    }
    printf("<br>\n");
  }
?>

Listing 4 - Kein Cache bitte!


Erzeugen einer Seite, die nicht gecacht werden
darf: In einer Datenbanktabelle werden Seitenabrufzähler
hinterlegt.


<?php
  header("Expires: 0");        // Netscape Browser Cache
  header("Pragma: no-cache");  // HTTP/1.0 Cache
  // HTTP/1.1 Cache
  header("Cache-Control: no-cache, no-store, must-revalidate");

  // Link aufbauen
  $link = mysql_connect("localhost", "kris", "");
  mysql_query("use poe_sessions", $link);
  
  // Zähler hochzählen
  $query = sprintf("update 
    page_counter 
   set 
     counter = counter +1 
   where 
     pagename = '%s'",
    $PHP_SELF);
  $res = mysql_query($query, $link);
  $success = mysql_affected_rows($link);
  
  // Wenn der Zähler noch nicht existierte, anlegen.
  if (!$success) {
    $query = sprintf("insert into page_counter 
            ( pagename, counter) 
      values('%s',      '1')",
      $PHP_SELF);
    mysql_query($query, $link);
  }
  
  // Zählerstand abfragen
  $query = sprintf("select 
    counter 
  from 
    page_counter 
  where 
    pagename = '%s'",
    $PHP_SELF);
  $res = mysql_query($query, $link);
  $d = mysql_fetch_array($res);
  $counter = $d["counter"];
 ?>
<html>
<body bgcolor="#ffffff">
<h1>Seitenzähler</h1>

Diese Seite wurde <?php print $counter ?> mal aufgerufen.

</body>
</html>

Listing 4a - Anlegen einer Datenbankstelle



kris<\@>valiant:/home/kris > mysql poe_sessions
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 29 to server version: 3.21.33

Type 'help' for help.

mysql> create table page_counter (
    ->   pagename varchar(127) not null,
    ->   counter integer,
    ->   primary key (pagename)
    -> );
Query OK, 0 rows affected (0.00 sec)

mysql> describe page_counter;
+----------+--------------+------+-----+---------+-------+
| Field    | Type         | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| pagename | varchar(127) |      | PRI |         |       |
| counter  | int(11)      | YES  |     | NULL    |       |
+----------+--------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

mysql> quit

Kekse und andere Headerzeilen

Über die header()-Funktion kann PHP HTTP-Headerzeilen generieren. Dies ist sehr nützlich, weil man auf diese Weise CGI-Redirects oder Cache-Control-Zeilen erzeugen kann. Gerade letzteres ist bei Seiten mit dynamisch generiertem Inhalt immens wichtig. Dabei muss man wissen, dass man die "header()"-Funktion in PHP nur dann erfolgreich einsetzen kann, wenn man den entsprechenden PHP-Code unmittelbar an den Anfang der Seite schreibt. PHP hält die Erzeugung der Ausgabe solange zurück, wie noch kein HTML auf der Seite enthalten ist. In dieser Zeit können mit header() beliebig viele Headerzeilen generiert werden. Sobald jedoch das erste HTML auf der Seite angetroffen wird, gibt PHP die angesammelten Headerzeilen sowie bei Bedarf auch seinen Standardheader "Content-Type: text/html" aus. Danach sind keine weiteren header()-Aufrufe mehr möglich. (siehe Listing 4) In Listing 4 ist ein Script zu sehen, das seine Ausgabe als variabel und nicht in Caches zu speichern markiert. Es generiert zu diesem Zweck drei Headerzeilen, "Expires", "Pragma" und "Cache-Control". Die erste Headerzeile bewirkt, dass Netscape die Seite nicht in seinem internen Browsercache hinterlegt, sondern bei jedem Abruf neu holt. Die zweite Headerzeile teilt HTTP/1.0-Caches auf dem Weg zwischen den Server und dem Browser mit, dass die Seite nicht gecached werden darf, während die dritte Zeile dies den unterschiedlichen Sorten von HTTP/1.1-Caches klarmacht. Der eigentliche Code der Seite ist ebenfalls interessant: In einer Datenbanktabelle page_counter mit den beiden Spalten pagename (varchar(127)) und counter (integer) werden die Abrufzahlen von Webseiten hinterlegt (siehe Listing 4b). Der Name der abgerufenen Seite wird dabei über die PHP-Einbauvariable $PHP_SELF bestimmt. Das Script versucht zunächst, mit Hilfe einer SQL-Update-Anweisung den Zähler für die betreffende Seite zu erhöhen. Dies gelingt in der Regel, nur beim ersten Abruf der Seite scheitert dies, weil noch kein Zähler für diese Seite existiert. Über die Funktion mysql_affected_rows() kann man feststellen, wie viele Zeilen von der Update-Anweisung bearbeitet wurden. Nach Konstruktion ist dies entweder eine Zeile, wenn der Zähler für die betreffende Seite schon existiert oder keine Zeile, wenn der Zähler für die Seite noch nicht existiert. In diesem Fall muss eine SQL-Insert Anweisung nachgeschoben werden, die den Zähler anlegt. Schließlich lässt sich der aktuelle Zählerstand abgefragen und in der Variablen $counter zur Verfügung stellen.

Zähler inklusive

Im wirklichen Leben wird man diese Codezeilen natürlich nicht auf jede seiner Seiten setzen wollen. sondern eine Include-Datei definieren. Diese enthält eine Funktion count_page() die den Seitenzähler weiterstellt. Außerdem wird die Include-Datei auch eine Funktion page_counter() enthalten, die den aktuellen Zählerstand einer beliebigen Seite ausliest und automatisch counter_page() aufruft. Listing 5 zeigt den modifizierten Code. Die Include-Datei sollte in einem gesonderten Verzeichnis abgelegt werden, das nicht unterhalb der DocumentRoot des Webservers liegt und das auch kein CGI-Verzeichnis ist. Es bietet sich also eine Struktur wie die folgende für den Webserver an:

           / - home - www - servers ->

           -> www.koehntop.de ------ pages
                              ------ cgi
                              ------ php

Dabei ist /home/www/servers/www. koehntopp.de/ das Basisverzeichnis des Webservers. In ihm befinden sich das Unterverzeichnis pages, das die DocumentRoot des Webservers darstellt, das Unterverzeichnis cgi, in dem sich der PHP-Interpreter und die php3.ini befinden und das Unterverzeichnis php, in dem die PHP-Includes abgelegt werden sollen. In php3.ini trägt man nun in der Zeile include_path= den Pfadnamen des PHP-Includeverzeichnisses ein. Es können auch mehrere Verzeichnisse angegeben werden, deren Namen mit einem Doppelpunkt (in Unix) oder einem Semikolon (in Windows) getrennt werden müssen. Nun wird dieser Code mit den Anweisungen require("count.inc") oder include("count.inc") eingebunden. Er wird an der Stelle in die aktuelle Seite eingefügt, an der die require() bzw. include()-Anweisung steht. require() und include() unterscheiden sich nicht in der Wirkung, wohl aber in der Effizienz der Ausfühung: require() ist schneller als include(), wird aber unbedingt ausgeführt, auch dann, wenn die Anweisung zum Beispiel in einem if-Block steht und der eingebundene Code also niemals ausgeführt werden würde. Entsprechend sollte man require() für statische Einbindungen verwenden, etwa für Vorspänne oder immer benötigte Bibliotheksfunktionen und include() für bedingte (dynamische) Einbindungen: zum Beispiel mit variablen Dateinamen oder durch ein if() geschützt. Anders als in C werden in PHP Funktionsdefinitionen mit dem Schlüsselwort function eingeleitet. Danach folgt der Name der Funktion und eine Parameterliste. Für alle Parameter kann optional ein Defaultwert angegeben werden (siehe die Definition von page_counter() in Listing 5). Wie in C gibt return den Wert der Funktion zurück. Listing 6 zeigt den Seitenzähler in einem Beispiel. An Stelle der require()-Anweisung muss man sich den in count.inc enthaltenen Code hindenken, das heißt dort sieht der PHP-Interpreter die Definitionen der Funktionen count_page() und page_counter() sowieso einen Aufruf von count_page(). Auf der Seite wird dann page_counter() einmal ohne Parameter und einmal mit dem Namen einer anderen Seite als Parameter aufgerufen. Dies ist möglich, weil bei der Definition der Funktion page_counter() ein Default-Wert für den Parameter $pagename angegeben wurde: Läßt man den Parameter weg, wird $pagecounter als leer angenommen und weiter unten in der Funktion durch den Namen der aktuellen Seite, $PHP_SELF, ersetzt. In diesem Fall gibt die Funktion den Zählerstand für die aktuelle Seite zurück. Gibt man dagegeben den Namen einer anderen Seite an, wird der Zählerstand dieser Seite zurückgegeben. Dabei ist lediglich zu beachten, dass man den Seitennamen genauso wie $PHP_SELF angibt, also mit führendem Slash und passender Groß- und Kleinschreibung.

Listing 5 - Erweiterung des Seitenzähler-Codes aus Listing 4:
Definition von zwei Funktionen zum Zählen von Seitenzugriffen. Der Code sollte als Datei count.inc im PHP-Includeverzeichnis (siehe Text) abgelegt werden.
Definition von zwei Funktionen zum Zählen von Seitenzugriffen. Der
Code sollte als Datei count.inc im PHP-Includeverzeichnis (siehe Text)
abgelegt werden.  <?php
  function count_page() {
    global $PHP_SELF;

    header("Expires: 0"); header("Pragma: no-cache");
    header("Cache-Control: no-cache, no-store, must-revalidate");

    $link = mysql_connect("localhost", "kris", ""); mysql_query("use
    poe_sessions", $link);

    $query = sprintf("update page_counter set counter = counter +1
      where pagename = '%s'", $PHP_SELF);
    $res = mysql_query($query, $link); $success =
    mysql_affected_rows($link);

    if (!$success) {
      $query = sprintf("insert into page_counter ( pagename, counter)
        values('%s', '1')", $PHP_SELF);
      mysql_query($query, $link);
    }
  }

  function page_counter($pagename = "") {
    global $PHP_SELF;

    if ($pagename == "")
      $pagename = $PHP_SELF;

    $link = mysql_connect("localhost", "kris", ""); mysql_query("use
    poe_sessions", $link);

    $query = sprintf("select counter from page_counter
      where pagename = '%s'", $pagename);
    $res = mysql_query($query, $link); $d = mysql_fetch_array($res);

    return $d["counter"];
  }

  // count_page einmal aufrufen count_page();


Listing 6: Anwendung des Seitenzählers aus Listing 5
<?php require("count.inc") ?> <html> <body
bgcolor="#ffffff"> <h1>Seite mit Zähler</h1>

Diese Seite wurde bisher <?php page_counter() ?> mal aufgerufen.Die
Seite <tt>/blafasel.html</tt> wurde bisher <?php
page_counter("/blafasel.html") ?> mal aufgerufen.  </body>
</html>


Ausblick

Wenn man länger mit PHP arbeitet, wird man sich bald eine Bibliothek mit immer wieder gebrauchtem Code anschaffen. PHP unterstützt dies durch einfache objektorientierte Features, welche die Verwaltung des globalen Namensraumes vereinfachen. Dies wird am Beispiel einer kleinen Datenbank-Zugriffsklasse DB_Sql erläutert Außerdem kann man mit PHP dynamisch Bilder erzeugen. Mit der DB_Sql-Klasse werden wir die Zugriffstatistik einer Website abrufen und ein Abrufdiagramm berechnen, das bei jedem Abruf die aktuellen Werte enthält. (uwo)

Infos

[1] Die PHP Website enthält den Source und Binärdistributionen zum Download sowie Dokumentation und Support. (weltweit) http://www.php.net (Deutschland) http://www.php3.de
[2] Die Entwickler von PHP3. http://www.php.net/credits.php3
[3] Die Website von TCX, den Entwicklern von MySQL, hat die URL http://www.tcx.se.
[4] Die FreeODBC Homepage, http://users.ids.net/~bjepson/freeODBC/
[5] The Boutell Website http://www.boutell.com/
[6] PHPLIB ist eine Bibliothek mit nützlichen Funktionen zur Erstellung von Webanwendungen http://phplib.shonline.de/
[7] Häufig gestellte Fragen in de.comp.lang.php http://www.koehntopp.de/php/
[8] Weitere Informationen zu PHP http://www.koehntopp.de/kris/artikel/

Copyright © 2000 Linux New Media AG