PHP 5 - Ein praktischer Einstieg
2. Auflage

PHP 5 - Ein praktischer Einstieg, 2. Auflage

Von Ulrich Günther
2. Auflage, August 2004
O'Reilly Verlag, ISBN: 3-89721-278-1
www.oreilly.de/catalog/einphp2ger/



TOC PREV NEXT INDEX

Kapitel 8

Kapitel 8

Weitere nützliche PHP-Techniken

In den bisherigen Kapiteln sind wir hauptsächlich PHP-Grundlagen begegnet: Sie haben HTML kennen gelernt - das Brot-und-Butter-Ausgabeformat von PHP. Sie haben gesehen, wie Benutzereingaben mit PHP vom Browser in eine Datenbank gelangen und dort mit PHP über das Web verwaltet werden können. Sie haben auch gelernt, wie Sie mit Objekten effektiven und leistungsfähigen Code schreiben können.

Damit können Sie jetzt auch größere Projekte in Angriff nehmen, ohne dass Ihnen die Übersicht verloren geht. Was noch fehlt, sind die kleinen praktischen Techniken, die im Alltag oft nützlich sind. In diesem Kapitel werden Sie einige dieser Techniken kennen lernen. Unter anderem erwarten uns die folgenden Themen:

E-Mail und PHP

Viele große E-Commerce-Websites kommunizieren mit ihren Benutzern nicht nur über Webseiten, sondern auch per E-Mail. Sobald Sie sich als Benutzer registriert haben, werden Ihnen beispielsweise Ihr Login und Passwort per E-Mail zugeschickt. Wenn Sie etwas bestellt haben, wird Ihnen die Bestellung per E-Mail bestätigt.

Unser Vogelschutzverein könnte beispielsweise die Benutzer nach ihren E-Mail-Adressen fragen und ihnen die Spendenquittung auf diesem Weg zustellen. Ebenso könnte eine E-Mail-Benachrichtigung an den Schatzmeister erfolgen, dass eine neue Spende eingegangen ist. Der Spender könnte wiederum automatisch benachrichtigt werden, wenn die Spende abgebucht worden ist.

PHP für das Versenden von E-Mail konfigurieren

Um Ihr PHP-Skript E-Mail versenden zu lassen, muss PHP auf Ihrem Server entsprechend konfiguriert sein. Dazu müssen Sie in php.ini die entsprechenden Einträge vornehmen. Unter Windows werden zwei Parameter gesetzt:

  1. Den Namen des ausgehenden Mailservers (SMTP). Beispiel:
    SMTP = smtp.auckland.ac.nz
    Sie finden den Namen des Servers in der Konfiguration Ihres E-Mail-Programms. Wenn Sie sich nicht sicher sind, fragen Sie Ihren Systemadministrator nach der Adresse des zuständigen Servers.
  2. Die Absenderadresse, die die E-Mails Ihres Skripts tragen sollen. Beispiel:
    sendmail_from = webmaster@meinefirma.com

Unter Unix (Linux) müssen Sie nur eine Eintragung vornehmen: den Pfad des sendmail-Programms:

sendmail_path =  "/usr/sbin/sendmail -t -i"
 

Speichern Sie php.ini ab und starten Sie den Webserver neu. Damit sind Sie bereit.

E-Mail versenden

Wenn Sie Ihren E-Mail-Text $text, den Betreff $betreff und die E-Mail-Adresse $adresse des Adressaten haben, geht das Mailen sehr einfach mit der mail()-Funktion:

mail($adresse, $betreff, $text);
 

Wenn Sie mehrere Adressaten für das »To:«-Feld haben, können Sie sie in Ihrem Adressen-String durch Kommata getrennt aufführen:

$adresse = "lolly@popcorn.com, kau@gummi.org, lutsch@bonbon.de"
 

Verwenden Sie noch andere Mail-Header, z.B. einen »Cc:«-Header (Kopie), einen »Bcc:«-Header (Kopie, deren Empfänger vom Adressaten nicht gesehen werden kann - wird unter Windows nicht unterstützt) oder einen »Reply-To:«-Header (Antwort an andere Adresse als Absender), können Sie diese als vierten Parameter übergeben. Dazu müssen Sie die einzelnen Header durch einen Carriage Return (Wagenrücklauf) \r und einen Zeilenumbruch \n trennen:

$zusatzHeader = "Cc: schatzmeister@vogelschutzclub.de\r\n"
 
    ."Reply-to: webmaster@vogelschutzclub.de";
 
mail($adresse,$betreff,$text,$zusatzHeader);
 

E-Mails zusammenstellen

Wollen Sie automatische E-Mails von Ihrer Site versenden, handelt es sich dabei im Regelfall um Formbriefe. Mit anderen Worten, der größte Teil des Texts bleibt immer gleich - im jeweiligen Einzelfall werden nur einzelne Teile eingefügt. Im Fall einer Spendenbescheinigung könnten die eingefügten Teile beispielsweise der Name des Spenders und der Spendenbetrag sowie das Datum sein.

Das geht bequem mit einer kleinen Klasse, die Sie in der Datei formBriefKlasse.php in den Beispieldateien für dieses Kapitel finden können. Bevor wir uns den Code der Klasse zu Gemüte führen, sehen wir uns an, wie Sie sie in der Praxis verwenden können (z.B. in spendenCombo.php):

Beispiel 8-1
Eine Formbrief-E-Mail mit der Klasse formBrief versenden
include("./formBriefKlasse.php");
 
$formbrief = new formBrief("formBrief.txt");
 
$formbrief->variablen["name"] = $spender->name;
 
$formbrief->variablen["betrag"] = $spende->betrag;
 
mail($spender->email,
 
    "Spendenbescheinigung",$formbrief->ersetzen());
 

In diesem Fall übergeben wir dem Konstruktor der neuen Klasse formBrief den Namen einer Datei formBrief.txt, die die Vorlage für unsere Spendenbescheinigung enthält. formBrief.txt könnte beispielsweise so aussehen:

Liebe(r) <#name>!
 

 
Haben Sie vielen Dank für Ihre Spende über <#betrag> Euro!
 
Wir werden sie demnächst über Ihre Kreditkarte abbuchen.
 

 
Ihr Schatzmeister des Vogelschutzclubs
 

In dieser Datei sind die zu ersetzenden Teile der Datei durch spezielle Tags markiert. Im Normalfall bestehen diese Tags aus einem Präfix <#, einem Tag-Namen (z.B. hier name und betrag) und einem Suffix >, wobei die Raute dazu dient, die Tags ggf. von HTML-Tags zu unterscheiden. Wie wir gleich sehen werden, können Sie sich aber Präfix und Suffix auch beliebig selbst definieren, wenn nötig.

Der Konstruktor öffnet diese Datei und speichert sie im neuen Objekt. Indem wir die Array-Eigenschaft variablen mit den Namen der jeweiligen Tags »füttern«, können wir dem Objekt mitteilen, durch welche Werte wir die jeweiligen Tags ersetzt haben wollen. Der eigentliche Ersetzungsvorgang wird dann durch die Objektmethode ersetzen() durchgeführt, deren Rückgabewert der ausgefüllte Formbrief ist. Beachten Sie auch, dass Sie Ihren Spender nach einer E-Mail-Adresse fragen und der spender-Klasse eine entsprechende Eigenschaft hinzufügen müssen.

Die Datei formBriefKlasse.php sieht so aus:

Beispiel 8-2
Der Code der Datei formBriefKlasse.php
<?php
 

 
    class formBrief {
 
        // Eigenschaften
 
        var $vorlagenText;
 
        var $komplettText;
 
        var $praefix;
 
        var $suffix;
 
        var $variablen = array();
 

 
        function formBrief($dateiName,
 
                           $praefix = "<#",
 
                           $suffix = ">") {
 
            $this->prefix = $praefix;
 
            $this->suffix = $suffix;
 
            if (!$this->vorlagenText = file_get_contents($dateiName)) {
 
                die("Vorlagendatei kann nicht geoeffnet werden");
 
            }
 
        }
 

 
        function replace() {
 
            $this->komplettText = 
 
                preg_replace_callback(
 
                    "/$this->prefix([A-Za-z]+)$this->suffix/",
 
                    array($this,"ersatz"),$this->vorlagenText);
 
            return $this->komplettText;
 
        }
 

 
        function ersatz($tags) {
 
            return $this->variablen[$tags[1]];
 
        }
 
    }
 

 
?>
 

In ihrem Konstruktor öffnet diese Klasse mit der file_get_contents()-Funktion eine als Parameter angegebene Datei. Die file_get_contents()-Funktion gibt einen String zurück, der den gesamten Dateiinhalt enthält. Diesen Rohtext speichern wir in der Eigenschaft vorlagenText ab.

Die beiden verbleibenden optionalen Parameter des Konstruktors setzen die Anfangs- und Endzeichen der Ersetzungs-Tags, die wir in den Eigenschaften prefix und suffix unterbringen. Damit hat der Konstruktor auch schon seine Schuldigkeit getan.

Jetzt müssen wir unserem neuen Formbrief-Objekt sagen, mit welchen Werten es die Tags mit bestimmten Namen ersetzen soll. Wie wir bereits gesehen haben, geht das ziemlich einfach über die Array-Eigenschaft variablen. Nun müssen wir nur noch die Ersetzung ausführen. Die dazu verwendete Methode ersetzen() besteht nur aus zwei Befehlen:

function ersetzen() {
 
    $this->komplettText = 
 
        preg_replace_callback(
 
            "/$this->prefix([A-Za-z]+)$this->suffix/",
 
            array($this,"ersatz"),$this->vorlagenText);
 
    return $this->komplettText;
 
}
 

Der erste Befehl verwendet die Funktion preg_replace_callback(). Im Prinzip funktioniert diese Funktion genau so wie die Funktion preg_replace(), der wir bereits im Zusammenhang mit der Verzeichnispfad-Vorbereitung für das Abspeichern des Spenderfotos begegnet sind. In preg_replace_callback() wird aber der Wert, mit dem das gefundene Muster ersetzt werden soll, nicht direkt angegeben. Stattdessen müssen wir den Namen einer Callback-Funktion angeben. In diesem Fall ist das die Funktion ersatz(). Da sie eine Methode unserer Klasse ist, müssen wir auch noch das zugehörige Objekt mit angeben. Diese kombinierte Angabe erfolgt über das Array array($this, "ersatz") im zweiten Parameter von preg_replace_callback().

Die Callback-Funktion wird jedes Mal aufgerufen, wenn das Muster in dem angegebenen String $this->vorlagenText gefunden wird. Als Parameter wird ihr ein Array übergeben. Dieses Array entspricht dem Array, das Sie als dritten optionalen Parameter in preg_match() angeben können: Als Eintrag mit dem Index 0 enthält es den gesamten Muster-String, als Eintrag mit dem Index 1 den String, der auf das erste Untermuster in runden Klammern passt, und so weiter.

Wenn Sie sich den regulären Ausdruck ansehen, können Sie erkennen, dass das erste Untermuster in den runden Klammern der aus Buchstaben (wenn Sie wollen, können Sie hier auch Zahlen usw. hinzufügen) bestehende Tag-Name ist. Also wird in $tag[1] jeweils der Name des Tags an unsere Callback-Methode ersatz() übergeben. Damit brauchen wir nur noch in der Eigenschaft variablen nachzuschlagen, welcher Ersatzwert anzugeben ist.

Der Rückgabewert von preg_replace_callback() ist natürlich der String, in dem alle Tags mit den passenden Werten ersetzt worden sind. Er wird in der Eigenschaft komplettText gespeichert und der Einfachheit halber auch gleich als Rückgabewert von ersetzen() ausgegeben.

So, damit können Sie Formbriefe verschicken, ohne dass Ihr Schriftführer PHP lernen muss!

Probieren Sie's selbst!

Wenn Sie der Formbrief-Klasse schon das Ersetzen der Tags überlassen, warum dann nicht auch gleich den ganzen Versendevorgang? Fügen Sie der Klasse zusätzliche Eigenschaften hinzu, die den Betreff des Formbriefs, den/die Adressaten und den/die Empfänger von Kopien speichern. Mit einer Methode versenden() können Sie dann die Formbrief-Klasse bequem zum Postversand einsetzen, ohne sich jedes Mal an die genaue Syntax von mail() erinnern zu müssen. Eine Lösung finden Sie in der Datei formBriefKlasseMitVersand.php in den Beispielen zu diesem Kapitel.

Microsoft Office und PHP

Wenn Sie ein eingefleischter Linuxer sind oder Ihr Webserver nicht unter Windows läuft, können Sie gleich zum nächsten Abschnitt weiterspringen. In diesem Abschnitt sehen wir uns an, wie Sie mit PHP auf Microsoft Office - genauer gesagt, auf MS-Word - zugreifen können. Wieso sollten Sie das wollen?

Nun, nehmen wir einmal an, Ihr Schatzmeister hätte die im letzten Abschnitt besprochenen Formbriefe auch gern als Word-Dokumente vorliegen. Null Problemo, vorausgesetzt, Ihr Webserver läuft unter Windows. Das geht so:

Beispiel 8-3
Einen Formbrief mit COM in Microsoft Word schreiben  
// Formbrief vorbereiten.
 
include("./formBriefKlasse.php");
 
$formBrief = new formBrief("formBrief.txt");
 
$formBrief->variablen["name"] = $spender->name;
 
$formBrief->variablen["betrag"] = $spende->betrag;
 
$formBrief->ersetzen();
 

 
// Word starten.
 
$word = new COM("word.application") or die("Kann Word nicht öffnen!");
 
echo "\n\nWord geladen (Version {$word->Version})\n";
 

 
// Leeres Dokument erzeugen.
 
$word->Documents->Add();
 

 
// In das Dokument schreiben.
 
$word->Selection->Font->Size = 12;
 
$word->Selection->Font->Name = "Arial";
 
$word->Selection->Font->Bold = true;
 
$word->Selection->Font->Italic = true;
 
$word->Selection->TypeText($formBrief->komplettText);
 

 
// Dokument speichern.
 
$pfadName = preg_replace("/\/[^\/]+$/","/wordDaten/",$_SERVER["SCRIPT_FILENAME"]);
 
$word->Documents[1]->SaveAs($pfadName.uniqid("").".doc");
 

 
// Word schließen.
 
$word->Quit();
 

 
// Objekt freigeben.
 
$word->Release();
 
$word = null;
 

Wir machen hier von einer Microsoft-Technologie Gebrauch, der so genannten COM-Automation. Durch diese COM-Technologie können wir von außen auf Objekte, Eigenschaften und Methoden einer anderen Anwendung (in diesem Fall Microsoft Word) zugreifen. Sie finden das obige Beispiel in der Datei formbriefInWord.php unter den Beispieldateien für dieses Kapitel.

Um in eine Word-Datei zu schreiben, verwenden wir also die Funktionalität von Word selbst. Dazu erzeugen wir ein neues COM-Objekt, das dazu in der Lage ist, eine Word-Anwendung zu starten. Wir fügen ihr ein neues Dokument hinzu, setzen (wenn wir wollen) seine Schrifteigenschaften und »tippen« den Text unseres Formbriefs ein.

Danach speichern wir die Datei in einem Unterverzeichnis wordDaten ab. Die Pfadvorbereitung hier ist dieselbe wie für die Abspeicherung des Spenderfotos in Kapi-tel 4. Auch hier gelten übrigens die Sicherheitshinweise in Sachen Dateinamen - die Auswahl der Namen sollten Sie keinesfalls den Benutzern im Internet überlassen!

Dann schließen wir Word und vernichten unser COM-Objekt, um seine Ressourcen an das System zurückzugeben. In Word sieht die Datei dann aus, wie in Abbildung 8-1 gezeigt.
Abbildung 8-1 Der von PHP über COM erstellte Formbrief in Word

Eine ähnliche Automatisierung ist auch mit anderen Office-Programmen wie Excel oder Access möglich. Von einem Datenbankzugriff auf Access über COM ist aber abzuraten - das geht zur Not einfacher über die Datenbank-Schnittstelle ODBC!

Zahlen und Mathematik in PHP

Bisher haben wir uns ein bisschen um diese Frage gedrückt und uns damit zufrieden gegeben, dass wir eine Variable mit entsprechendem Inhalt mal als Zahl und mal als String auffassen können, wie es uns eben passt. Außer den vier Grundrechenarten haben wir auch noch nicht viel gebraucht. Das kann in der Praxis aber ganz anders sein - insbesondere wenn es um technische oder wissenschaftliche Berechnungen geht oder auch nur um Zinseszinsberechnungen in einer Finanzanwendung.

Zahlenformate in PHP

In unseren bisherigen Skripten haben wir PHP im Umgang mit Zahlen immer vertraut. PHP konvertiert nämlich im Regelfall automatisch zwischen seinen eingebauten Zahlenformaten Fließkomma (float), Ganzzahl (integer, int) und dem binären Booleschen Format (Wahrheitswert true oder false) sowie zwischen diesen Formaten und Strings. Apropos Fließkomma: Das Dezimalzeichen in PHP ist kein Komma, sondern wie bei praktisch allen Programmiersprachen der im Angelsächsischen gebräuchliche Punkt.

Das Vertrauen in die Intelligenz von PHP ist aber nicht immer angebracht. Zahlen auf Computern sind generell etwas heimtückisch: Probieren Sie mal das folgende kleine Skript im Kopf aus:

Beispiel 8-4
Nicht alles ist so, wie es scheint: Fließkommazahlen in PHP  
<?php
 
   $x = 0.3;
 
   $x = $x - 0.1; // 0.2?
 
   echo $x."<br>"; 
 
   $x = $x - 0.1; // 0.1?
 
   echo $x."<br>"; 
 
//   $x = 0.1;
 
   $x = $x - 0.1; // 0?
 
   echo $x."<br>"; 
 
?>
 

Okay, vielleicht waren Sie noch nie besonders gut in Mathematik, aber in diesem Skript können Sie mir bestimmt folgen: Wir geben einer Variablen $x den Anfangswert 0.3. Nicht besonders kompliziert, oder? Dann ziehen wir dreimal nacheinander den Wert 0.1 vom gegenwärtigen Wert der Variablen ab und geben jeweils das Ergebnis aus. Der erste echo-Befehl ergibt also 0.2, der zweite 0.1.

Nach Adam Riese sollte beim dritten echo-Befehl also null herauskommen. Null. Zero. Nix. Nun, dann probieren Sie das Skript doch einmal auf Ihrem Server aus. Da bekommen Sie aber vermutlich nicht 0, sondern -2.7755575615629E-017, sprich »minus 2.77555... mal zehn hoch minus 16«. Das ist zwar ziemlich klein und nicht gerade positiv, aber eben nicht null. Oops!

Nun richten Sie Ihr Augenmerk mal auf den zweitletzten echo-Befehl. Der sagt uns ganz klar, dass $x hier noch den Wert 0.1 hat. Also muss das Problem ja wohl in der letzten Subtraktion liegen. Entfernen Sie doch mal die Kommentarzeichen in der nächsten Zeile. Wenn $x bereits den Wert 0.1 hat, können Sie diesen Wert ja gefahrlos auch direkt in die Variable schreiben, oder? Probieren Sie's. Diesmal ist das Ergebnis glatt 0 - das Problem ist verschwunden. Nanu?

Bringen Sie jetzt nicht gleich Ihren Computer in den Laden zurück. Auch PHP ist hier nicht schuldig. Das Problem ist nicht mal besonders neu. Was passiert hier? Nun, ein Computer kann Zahlen nur mit begrenzter Präzision darstellen, weil er pro Zahl lediglich eine begenzte Anzahl Bits zur Verfügung hat. Da ein Bit nur die Werte 1 oder 0 haben kann, ist die Anzahl der möglichen Bit-Kombinationen ebenfalls begrenzt. Das wiederum begrenzt die Präzision, mit der ein Computer Fließkommazahlen darstellen kann.

Wenn Sie Ihrem Computer also befehlen, die Zahl 0.3 zu speichern, kann er das nur als Näherung in binärem Format. Wenn Sie den abgespeicherten Wert wieder in eine Dezimalzahl zurückkonvertieren, bekommen Sie einen Wert knapp unter 0.3, und zwar 0.299999999999999999.

Weil PHP in der Default-Konfiguration nur 14 gültige Stellen darstellt und daher rundet, wird aber nur 0.3 dargestellt. Ziehen wir jetzt 0.1 ab, ziehen wir in Wirklichkeit auch nur eine Näherung ab: 0.10000000000000001. Wenn Sie das dreimal machen, ist das Resultat eben leicht negativ.

Aus diesem Grund müssen wir manchmal auf zusätzliche Hilfsmittel zurückgreifen, wenn wir unsere Zahlen als Strings korrekt formatieren oder Zahlen vergleichen wollen. Tabelle 8-1 zeigt einige Probleme auf und bietet Ihnen Lösungen zur Umgehung der Probleme an.

Tabelle 8-1
Probleme im Umgang mit Zahlen 
Problem-Code Erklärung Lösung
$x = 0.7; $y = 0.8; if ($x + 0.1 == $y) { echo "OK"; } else { echo "Nicht OK"; } ergibt »Nicht OK«. Das gleiche Problem wie im Eingangsbeispiel: Numerische Ungenauigkeit sorgt für Ungleichheit im if-Statement. Nicht ganz trivial - versuchen Sie, keine Fließkommazahlen miteinander zu vergleichen! Wenn Sie z.B. wissen, dass $x und $y sich nur in Schritten von »ungefähr« 0.1 ändern, können Sie es aber auch wie folgt probieren: if ($x - $y + 0.1 < 0.01) {  ...
floor((0.7 + 0.5) * 10) sollte 8 ergeben (floor() ermittelt die größte ganze Zahl, die kleiner oder gleich dem angegebenen Argument ist), ergibt aber 7. Dieses Problem stammt aus der PHP-Dokumentation. Ebenfalls eine Folge der numerischen Ungenauigkeit. Geht es Ihnen nur darum, eine ganze Zahl zu erhalten, können Sie auch round() verwenden. Wenn Sie wissen, in welchen groben Schritten sich Ihre Werte bewegen, hilft auch oft das Addieren einer kleinen Zahl, z.B. bei einer Schrittweite von 0.1: floor((0.7 + 0.5) * 10 + 0.01)
$preis = 3.00; echo $preis."&euro;"; Nullen nach dem Komma werden verschluckt. Eigentlich eine gewollte Eigenschaft, die hier aber ein Problem verursacht. Ein gängiges Problem bei Geldbeträgen. Versuchen Sie es mit sprintf(): $preis = 3.00; echo sprintf("%.2f", $preis)."&euro;"; Die Funktion sprintf() kann einen angegebenen Wert oder eine ganze Reihe von Werten formatieren und in einen String einbetten. In diesem Fall ist das ein Fließkommawert (f), der mit mindestens zwei Nachkommastellen dargestellt wird. Siehe auch den entsprechenden Eintrag im PHP-Manual.
echo (int) 10000000000; Konvertierung zu Ganzzahl gibt 1.410.065.408 aus. Ganze Zahlen werden in PHP wie in anderen Sprachen nur bis zu einer bestimmten Größe unterstützt. Auf den meisten Computern liegt dieser Wert zwischen -2.147.483.647 und +2.147.483.648. Größere Werte stellt PHP automatisch als Fließkommawert dar, wobei allerdings Präzision verloren gehen kann. Vermeiden Sie die explizite Konvertierung zu Ganzzahlen (Integern).

Im Großen und Ganzen sind das aber Ausnahmefälle, die Ihnen in der Regel nicht viele Probleme bereiten werden. Trotzdem ist es gut, davon zu wissen, wenn man einmal mit einer etwas unerwarteten numerischen Ausgabe konfrontiert ist. Wenn Sie höhere Präzision brauchen, können Sie auch die BCMath-Funktionen verwenden, die mit beliebiger Präzision arbeiten. Sie finden eine nähere Beschreibung dieser Funktionen in der Funktionsreferenz der PHP-Dokumentation unter »Mathematische Funktionen mit beliebiger Genauigkeit«.

Mathematische Funktionen

Es ist gar nicht so ungewöhnlich, bei der dynamischen Erstellung von Webseiten Berechnungen anstellen zu müssen. Einige Beispiele dafür:

Steuerberechnung
Eine Webseite bietet die Online-Berechnung eines komplizierteren Steuersparmodells an. Sie müssen mit Preisen, Steuersätzen, Zinsen, Zinsenszinsen, Inflation, Abschreibungen, Freibeträgen und Aufteilungen rechnen. Hier kommen Sie mit den vier Grundrechenarten allein nicht sehr weit. Sie brauchen Potenzen, Logarithmen und Rundungsfunktionen.
Ingenieurtechnische Berechnungen
Potenzielle Kunden eines Richtfunk-Internetanbieters können auf dessen Website ihren Standort eingeben. Der Server berechnet dann die benötigte Antennengröße und sagt den Kunden, in welche Richtung die Antenne ausgerichtet werden muss. Hier brauchen Sie Logarithmen und Exponentialfunktionen sowie trigonometrische Funktionen und Wurzeln.
Astronomie
Eine Astronomie-Website stellt Benutzern für einen eingegebenen Standort sowie eine eingegebene Uhrzeit und ein Datum eine individuelle Sternkarte mit Mond, Planeten und sonstigen Gestirnen zusammen und gibt sie als Grafik aus. Dazu ist ebenfalls jede Menge Trigonometrie erforderlich.
Online-Spiele
Auf einer Online-Spieleseite muss viel »gewürfelt« werden. Dazu brauchen Sie eine Zufallszahlen-Funktion und Funktionen, die daraus Zufallszahlen beliebiger Art (z.B. binär wie beim Münzwurf oder 1-aus-6 wie beim Würfeln) erzeugen können.

PHP bietet für solche Zwecke einen guten Fundus an Standardfunktionen an.

Potenzen und Logarithmen

PHP bietet hier eine ganze Reihe von Funktionen an. Die wichtigsten sind in Tabelle 8-2 zusammengefasst:

Tabelle 8-2
Wurzeln, Potenzfunktionen und Logarithmen  
Funktion Verwendung
sqrt($x)
Berechnet die Quadratwurzel von $x.
exp($x)
Berechnet die Exponentialfunktion (Eulersche Zahl e hoch $x).
log($x)
Berechnet den natürlichen Logarithmus (zur Basis e) der Zahl $x.
log10($x)
Berechnet den Logarithmus zur Basis 10 der Zahl $x.
pow($basis,$exponent)
Berechnet die Potenz $basis hoch $exponent.

Trigonometrische Funktionen

Auch in der Abteilung Trigonometrie braucht PHP sich nicht lumpen zu lassen. Tabelle 8-3 listet die wichtigsten Funktionen in dieser Kategorie auf:

Tabelle 8-3
Trigonometrische Funktionen, Hilfsfunktionen und Konstanten 
Funktion(en) Verwendung
sin($x), cos($x), tan($x)
Sinus, Kosinus und Tangens der Variablen $x, die einen Winkel im Bogenmaß (radians) angibt.
sinh($x), cosh($x), tanh($x)
Sinus, Kosinus und Tangens Hyperbolicus.
asin($x), acos($x), atan($x)
Arkussinus, Arkuskosinus und Arkustangens der Variablen $x. Rückgabewert der Funktionen ist jeweils ein Winkel im Bogenmaß.
asinh($x), acosh($x), atanh($x)
Arkussinus, Arkuskosinus und Arkustangens Hyperbolicus.
deg2rad($winkel_in_grad)
Konvertiert den Winkel $winkel_in_grad ins Bogenmaß.
rad2deg($winkel_im_bogenmass)
Konvertiert den Winkel $winkel_im_bogenmass in Grad.
pi
Kreiszahl Pi (3.14...).

Zufallsfunktionen

PHP verfügt über drei Funktionen, die bei der Erzeugung von Zufallszahlen eine Rolle spielen:

Die Funktion srand($seed) initialisiert den Zufallsgenerator mit einem Startwert $seed. Dieser wird typischerweise aus der Systemzeit gewonnen, z.B. über die microtime()-Funktion, die die Zeit in Mikrosekunden angibt:

srand ((double)microtime()*1000000);
 

In diesem Fall wird der Rückgabewert von microtime() mit einer hohen Ganzzahl multipliziert und durch (double) explizit in eine Fließkommazahl umgewandelt, die dann als Parameter für srand() verwendet wird.

Wenn Sie einen Zufallsgenerator zweimal hintereinander eine Folge von Zahlen erzeugen lassen und dabei jedes Mal denselben $seed-Wert verwenden, sind die beiden Folgen identisch.

Der Zufallsgenerator selbst wird durch die Funktion rand() implementiert. Sie hat zwei Ganzzahl-Parameter: Der erste Parameter gibt die niedrigste mögliche Zufallszahl an, der zweite die höchste. Der Rückgabewert ist ebenfalls eine ganze Zahl. Einen Würfel können Sie also so simulieren:

Beispiel 8-5
Simulation eines Würfels
srand ((double)microtime()*1000000);
 
$wuerfelAugen = rand(1,6);
 

In vielen Anwendungen brauchen Sie aber z.B. eine Zufallszahl, die zwischen einschließlich 0 und ausschließlich 1 liegt (d.h., 0.999999... ist erlaubt, 1 nicht). Das können Sie recht einfach mit der Funktion getrandmax() erledigen, die den systemabhängigen Maximalwert für die von rand() lieferbaren Zufallszahlen festlegt:

Beispiel 8-6
Zufallszahlen zwischen 0 und 1
srand ((double)microtime()*1000000);
 
$max = getrandmax();
 
$zwischenNullUndEins = rand(0,$max)/($max + 1);
 

Hier erzeugen wir einfach eine ganze Zufallszahl und teilen sie dann durch ihren maximal möglichen Wert plus 1. Damit liegt das Ergebnis irgendwo zwischen 0 und 1, aber garantiert noch unter 1.

Zahlenfunktionen

Unter diesen Begriff fallen Funktionen, die Zahlen runden oder Minimal- und Maximalwerte finden. Tabelle 8-4 fasst sie zusammen:

Tabelle 8-4
Zahlenfunktionen
Funktion Verwendung
ceil($x)
Rundet auf die nächste Ganzzahl auf, die größer als oder gleich $x ist. Siehe auch den vorhergehenden Abschnitt über numerische Probleme!
floor($x)
Rundet auf die nächste Ganzzahl ab, die kleiner als oder gleich $x ist. Siehe auch den vorhergehenden Abschnitt über numerische Probleme!
round($x)
Rundet auf die nächste Ganzzahl auf oder ab, die $x am nächsten liegt.
min($a), min($x,$y,$z,...)
Gibt das kleinste Element des Arrays $a zurück oder den kleinsten Wert aus der Liste $x, $y, $z, ...
max($a), max($x,$y,$z,...)
Gibt das größte Element des Arrays $a zurück oder den größten Wert aus der Liste $x, $y, $z, ...
abs($x)
Gibt den Absolutwert von $x zurück, d.h. den Betrag von $x ohne Vorzeichen.

Datenbank-Konsistenz wahren

Dass wir auf unsere Datenbank etwas aufpassen müssen, das haben wir schon im letzten Kapitel gesehen. Es muss aber nicht immer gleich ein Hacker sein, der unsere Datenbank in Unordnung bringt. Es kann auch durch ganz »normale« Vorgänge passieren. In diesem Abschnitt sehen wir uns ein paar Techniken an, mit denen Sie Probleme vermeiden können - oder zumindest angerichteten Schaden reparieren können.

Verbindungsabbruch durch den Benutzer

Sehen Sie sich noch einmal den Code aus spendenCombo.php an, mit dem wir die Objekte in der Datenbank abspeichern:

$spender->speichern();
 
$karte->speichern();
 
$spende->speichern();
 

Jedes dieser Statements führt irgendwo in seinen Eingeweiden zu einem insert-Befehl in der Datenbank. Je nachdem, wie groß und ausgelastet unsere Datenbank ist, kann es ein bisschen dauern, bis alle drei Statements nacheinander abgearbeitet sind. Hier handelt es sich dabei vielleicht nur um Sekundenbruchteile, aber in großen Anwendungen kann es sich auch schon einmal um Sekunden handeln.

Während dieser Zeit sitzt unser Benutzer an seinem Browser und wird vielleicht etwas ungeduldig. Oder das Telefon klingelt. Was auch immer der Grund ist, der Benutzer betätigt aus heiterem Himmel die Stopptaste, gerade während wir unsere Kartendaten abspeichern. Was passiert jetzt?

Da der Browser nun keine Skriptausgabe mehr erwartet, geht PHP davon aus, dass das Skript seine Schuldigkeit getan hat und abgebrochen werden kann. Damit haben wir ein kleines Problem: einen Spender und eine Kreditkarte in der Datenbank, aber keine dazugehörige Spende. Auch wenn es in diesem Fall kein großes Problem ist, sollte das eigentlich nicht vorkommen. In einer größeren Anwendung kann Ihnen eine derart unvollständige Datenbank-Transaktion erhebliche Probleme bereiten. In der Regel wollen Sie ja die komplette Transaktion durchführen.

Zum Glück gibt es eine Möglichkeit in PHP, derartige Aufhänger zu vermeiden. Bevor Sie Code ausführen, der nicht zwischendrin abgebrochen werden darf, fügen Sie einfach den Befehl

ignore_user_abort(true);
 

ein. Damit ignoriert PHP den Verbindungsabbruch von Seiten des Benutzers und fährt sklavisch mit der Skriptausführung fort. Wenn Sie mit dem kritischen Code-Abschnitt fertig sind, können Sie PHP wieder die Genehmigung zum Abbruch geben:

ignore_user_abort(false);
 

Rollbacks

In vielen Datenbanken (nicht aber in MySQL) können Sie (SQL-)Befehle, die Sie bereits in einer Datenbankverbindung gegeben haben, mit einem Rollback wieder rückgängig machen.

Das setzt allerdings voraus, dass Sie noch in dieser Transaktion bemerken, dass Ihre Datenbank bzw. Ihr Skript ein Problem hat. Um bei einem Benutzerabbruch oder einem Timeout reagieren zu können, müssen Sie in PHP den Namen einer Funktion angeben, die bei einem Skriptabbruch ausgeführt werden soll. Das geht so:

register_shutdown_function("sauberAbbrechen");
 

Damit wird die Funktion sauberAbbrechen() in Ihrem Skript aufgerufen, wenn das Skript normal oder durch Benutzerabbruch beendet wird. In diese Funktion können Sie die entsprechenden Rollback-Befehle setzen, die Sie immer dann ausführen, wenn Sie z.B. mit connection_aborted() einen clientseitigen Abbruch feststellen:

if (connection_aborted()) {
 
    // Code zum Rückgängigmachen angefangener Datenbank-Transaktionen
 
    // wird hier eingefügt.
 
}
 

Im Moment funktioniert register_shutdown_function() allerdings noch nicht unter Apache für Windows.

Die Shutdown-Funktion wird übrigens auch dann aufgerufen, wenn ein Skript wegen Überschreitung der maximalen Ausführungszeit abgebrochen wird. Das kann z.B. bei einem überlasteten Datenbank-Server passieren. Auch in diesem Fall kann es ggf. sinnvoll sein, ein Rollback zu veranlassen (oder zumindest entsprechende Anweisungen in eine Logdatei zu schreiben, wenn der Datenbank-Server nicht mehr zu erreichen ist). Die maximale Ausführungszeit können Sie mit set_time_limit() ändern.

Backups und Logs

Manchmal geht trotz aller Vorsichtsmaßnahmen etwas schief, und Ihre Datenbanktabellen werden korrumpiert. Dann ist es wichtig, Sicherheitskopien der Datenbank zu haben. Manche Datenbanken bieten diesen Service automatisch an, bei anderen wiederum müssen Sie die entsprechenden Dateien in das Backup Ihres Servers mit einbeziehen oder sie manuell kopieren.

Bei MySQL können Sie von der Kommandozeile aus ein komplettes Backup Ihrer Datenbank herstellen. Unter Unix geht das z.B. so (für unsere Beispiel-Datenbank):

/usr/local/mysql/bin/mysqldump -u root -p SPENDEN > spendenDump.sql
 

Unter Windows sieht der entsprechende Befehl so aus:

c:\mysql\bin\mysqldump -u root -p SPENDEN > spendenDump.sql
 

Damit wird eine aus SQL-Befehlen bestehende Textdatei spendenDump.sql erstellt. Diese stellt (bis auf die fehlenden drop- und create-Befehle am Anfang der Datei) praktisch eine Kombination aus spendenDb.sql und spendenDaten.sql dar. Damit können Sie diese Datei auch wieder in eine leere Datenbank SPENDEN laden (siehe Kapitel 6). Die Datei selbst können Sie natürlich umbenennen, z.B. um sie mit dem Datum des Backups zu versehen.

Als Faustregel gilt: Je geschäftiger und wichtiger Ihre Datenbank ist, desto häufiger sollten Sie Sicherungskopien anfertigen. Zwischen den Kopien können Sie alle ausgegebenen SQL-Statements (oder auch nur solche Statements, die Datenbank ändern, also z.B. insert-, update- und delete-Statements) in eine SQL-Logdatei schreiben.

Wird Ihre Datenbank beschädigt, hat eine solche Logdatei zwei Vorteile: Erstens können Sie das Log durchsehen, um herauszufinden, wie der Schaden entstanden ist. Dadurch können Sie ziemlich zielgerichtet Verbesserungen an Ihrem Code vornehmen.

Zweitens können Sie - wenn Sie die Logdatei jeweils bei jedem Backup neu anfangen - aus den unproblematischen Logdaten und dem letzten Backup die Datenbank wieder reparieren. Das geht im Prinzip genau so, wie wir unsere Datenbank in Kapitel 6 mit Dummy-Daten geladen haben - durch Import der SQL-Datei, nachdem Sie die Datenbank bis zum Backup wiederhergestellt haben.

Das Anlegen einer Logdatei geht am einfachsten über eine Funktion:

Beispiel 8-7
Eine Logdatei anlegen
function dbLogSchreiben($query) {
 
    $datei = fopen("sqllog.sql","a");
 
    $logString = "# ".date("r")." ".$_SERVER["SCRIPT_FILENAME"]
 
        ." (".$_SERVER["REMOTE_ADDR"]."):";
 
    fwrite($datei, $logString."\n");
 
    fwrite($datei, $query."\n\n");
 
    fclose($datei);
 
}
 

Diese Funktion öffnet mit fopen() eine Textdatei sqllog.sql zum Anhängen von Daten. Dann schreiben Sie mit fwrite() eine Kommentarzeile in die Datei. Das passiert mit Hilfe der date()-Funktion und zweier vom Webserver zur Verfügung gestellter Variablen, die den Namen des zugreifenden Skripts und die Internetadresse des anfordernden Browsers angeben. In die darauf folgende Zeile schreiben wir den SQL-Abfrage-String

# Wed,  1 May 2002 21:57:52 +1200 /spendenListe.php (127.0.0.1):
 
select * from spender, karte, spende where ((spendeSpender = spenderId) and ...
 

Anschließend wird die Logdatei wieder geschlossen.

Eine kleine Warnung: Da für jeden Log-Eintrag eine Datei geöffnet werden muss, kann das bei stark frequentierten oder datenbankintensiven Skripten zu einer erhöhten Rechnerbelastung (d.h. Verlangsamung) führen.

Externe Funktionen verwenden

Manchmal kommt es vor, dass Sie von PHP aus externe Programme verwenden möchten oder müssen. Dafür gibt es zwei gute Gründe:

  1. Eine entsprechende Funktionalität ist in PHP nicht vorhanden, oder ein externes Programm ist für den Anwendungszweck besser geeignet.
  2. Sie möchten ein externes Programm starten, das nach der Ausführung des PHP-Skripts eigenständig weiterlaufen soll.

PHP stellt für diesen Zweck einen Operator sowie eine ganze Reihe verwandter Funktionen zur Verfügung. Welche Lösung für Ihren Anwendungsfall die richtige ist, hängt von den Umständen ab. Die Fragen, die Sie sich stellen sollten, sind:

Wenn Sie diese Fragen beantwortet haben, fällt es Ihnen bestimmt nicht schwer, die richtige Lösung zu finden. Bevor Sie sie implementieren, sollten Sie sich auch mit der Verwendung von escapeshellarg() vertraut machen (siehe unten). Hier sind die verschiedenen Möglichkeiten:

shell_exec($kommando)
Führt das Kommando $kommando auf der Shell (unter Unix) bzw. (unter Windows) in der DOS-Shell aus. Die Ausgabe des Kommandos wird als Rückgabewert an PHP zurückgegeben. Unter Windows können Sie so ein DOS-artiges Verzeichnis-Listing in einer Variablen abspeichern:
$listing = shell_exec("dir c:\xyz");
Unter Unix geht das mit dem entsprechenden Unix-Kommando:
$listing = shell_exec("ls /xyz");
Eine gleichwertige Alternative ist die Verwendung von rückwärtigen Anführungszeichen, die das Kommando umgeben (Backtick-Operator):
$listing = `dir c:\xyz`;
exec($kommando)
Wie shell_exec(), es wird aber nur die letzte Zeile der Ausgabe zurückgegeben. Als zweiten optionalen Parameter können Sie ein Array übergeben. Nach Ausführung von exec() enthält jedes Element des Arrays eine Zeile der Ausgabe. Ein optionaler dritter Parameter bekommt den Statuswert zugewiesen (z.B. 0 bei erfolgreicher Ausführung des externen Kommandos).
system($kommando)
Diese Funktion gibt die Ausgabedaten von $kommando sowohl direkt an den Browser als auch als String-Rückgabewert aus. Ein optionaler zweiter Parameter bekommt den Statuswert zugewiesen.
passthru($kommando)
Wie system(), gibt die Ausgabedaten aber nur an den Browser aus. Dieses Kommando ist insbesondere bei binären Ausgabedaten zu empfehlen.

Wenn Sie ein externes Programm nur starten und nach Skriptende weiterlaufen lassen wollen, sollten Sie außerdem dafür sorgen, dass die Ausgabe in eine Datei o.Ä. umgeleitet wird.

exec("/usr/local/bin/meinProgamm > /dev/null"); 
 

Diese Funktionen machen es uns natürlich leicht, externe Programme zu starten. Manchmal machen sie es uns aber auch zu einfach. Sehen Sie sich einmal den folgenden Code an, der ein Listing eines angegebenen Unterverzeichnisses ausgibt:

Beispiel 8-8
Unsicherer Code
$verzeichnis = $_POST["verzeichnis"];
 
$listing = shell_exec("dir c:/katalog/".$verzeichnis);
 
echo $listing;
 

Das sieht mal wieder harmlos aus und macht bestimmt, was wir wollen. Leider ist dieser Code auch ziemlich gefährlich. Überlegen Sie sich mal, was passiert, wenn Sie dem Skript den String "; delete *.*" als Verzeichnisnamen übergeben. Dann ist das ausgeführte Kommando:

dir c:/katalog/; delete *.*
 

Was hier passiert, ist ziemlich klar, oder? Erst geben wir das Hauptverzeichnis aus, anschließend löschen wir alle Dateien in unserem Skriptverzeichnis!

Für solche Fälle gibt es bei PHP eine Funktion, die vom Benutzer angegebene Kommandozeilen-Parameter in ein sicheres Format überführt: escapeshellarg(). Die erste Funktion können Sie relativ beruhigt verwenden - sie verpackt den übergebenen Parameter in Anführungszeichen und versieht etwaige Anführungszeichen im String mit Backslashs. Damit wird der übergebene String auch extern als eine Einheit behandelt:

Beispiel 8-9
Sicherer Code
// Verzeichnisnamen verpacken:
 
$verzeichnis = 
 
    escapeshellarg("c:/katalog/".$_POST["verzeichnis"]);
 
// Verzeichniswechsel nach oben verhindern (".." rauswerfen):
 
$verzeichnis = preg_replace("/\.\./","",$verzeichnis); 
 
$listing = shell_exec("dir ".$verzeichnis);
 
echo $listing;
 

ergibt bei der obigen böswilligen Eingabe:

dir 'c:\katalog\; delete *.*'
 

Das ist zwar ein nicht-existentes Verzeichnis, richtet aber sonst keinen direkten Schaden mehr an. Die oft angeführte Funktion escapeshellcmd() ist nicht gerade sicher - Sie sollten sie nicht verwenden, wenn Sie sie nicht genau kennen.

Auf keinen Fall sollten Sie den Kommandonamen durch Benutzer festlegen lassen - es sei denn, Sie prüfen vorher genau, dass es sich um ein erlaubtes Kommando handelt, z.B. durch ein if-Statement, ein switch-case-Statement oder einen regulären Ausdruck.

Noch mal zur Sicherheit

Sie haben es sicher schon gemerkt: In diesem Buch hat der Schutz vor böswilligen Eindringlingen mehrfach eine ziemlich wichtige Rolle gespielt. Bisher haben wir das jeweils dann diskutiert, wenn wir potenziellen Schwachstellen in unserem Code begegnet sind.

Inzwischen kennen Sie eine breite Palette von PHP-Techniken. Da ist es nur natürlich, wenn die Experimentierlust in Ihnen aufkommt und Sie mit eigenen Skripten neues Territorium erschließen wollen. Damit Sie auch in Ihrem eigenen Code Sicherheitsprobleme erkennen und vermeiden können, betrachten wir das Thema noch einmal aus einer generellen Perspektive. Was sind die Gefahren? Wo treten sie auf? Wie erkennen Sie eine Sicherheitslücke? Welche Vermeidungsstrategien gibt es?

Wo liegen die Gefahren?

PHP ist im Grunde genommen eine sichere Programmiersprache. Die meisten Funktionen kommen mit beliebigen Parameterwerten klar, Variablen können Sie beliebige Werte zuweisen, ohne dass Ihnen dabei Ihr Server abstürzt. Bei einigen anderen Programmiersprachen ist das nicht so - übrigens einer der Gründe für die zunehmende Verbreitung von PHP.

Praktisch alle Probleme im Zusammenhang mit PHP treten dann auf, wenn Sie den Funktionalitätsbereich von PHP verlassen und mit externen Dateien oder Programmen arbeiten. Einige Beispiele:

Wie Sie sehen, treten alle diese Lücken dort auf, wo Ihr PHP-Skript mit der Außenwelt in Kontakt kommt.

Eine indirekte Ausnahme gibt es: die eval()-Funktion. Diese Funktion ist nicht dazu da, direkt mit anderen Programmen oder Dateien zu arbeiten. Ihr Parameter ist ein String mit PHP-Code, der bei Aufruf der Funktion ausgeführt wird.

Den Rest können Sie sich inzwischen vermutlich denken: Wenn ein Hacker den auszuführenden Code bestimmen kann, lassen sich auch ein paar kleine system()-Statements oder Ähnliches einschleusen - und dann haben Sie den Salat.

Was sind die Gründe für Sicherheitslücken?

Eine andere - neben dem Kontakt zur Außenwelt - Gemeinsamkeit der oben aufgeführten Beispiele besteht darin, dass vom Benutzer vorgegebene Daten zum Einbruch in das System verwendet werden. Eine Sicherheitslücke liegt vor, wenn diese Daten bis zu einer Stelle in einem Skript vordringen, an der sie Schaden anrichten können.

In den meisten Fällen passiert das, weil der Skriptprogrammierer nicht mit diesen Daten gerechnet hat. Meist hat man als Programmierer eine ziemlich genaue Vorstellung von der Art Daten, die man als Eingabe erwartet. Diese Vorstellung führt leicht dazu, dass man andere mögliche Eingaben und ihre Effekte nicht berücksichtigt. HTTP macht aber überhaupt keine Vorschriften darüber, welche Daten von wem an Ihr Skript geschickt werden dürfen. Alles ist möglich.

Der erste grundlegende Fehler, der immer wieder gemacht wird, spiegelt sich daher in der folgenden Aussage wider: »Aber die Daten kommen doch aus dem <select>-Tag in meinem Formular!« Statt <select> können Sie hier auch jedes andere HTML-Tag einsetzen - es bleibt ein Trugschluss. Klar, die Daten können und sollen aus Ihrem Formular kommen, aber garantieren kann Ihnen das niemand!

Dieser grundlegende Fehler wird aber erst dann zum echten Problem, wenn eine bereits verwendete Funktion zweckentfremdet werden kann und der Programmierer dies übersieht. Bei allen serverseitigen Sicherheitslücken, die wir in diesem Buch bisher besprochen haben, wurden Funktionen zweckentfremdet.

Damit wird es relativ einfach, grobe Strategien zum »Abdichten« unserer Skripte zu entwerfen.

Sicherheitslücken vermeiden

Um Ihre PHP-Skripte vor unerwünschtem Missbrauch zu schützen, empfehlen sich die folgenden Strategien:

Strategie 1: Halten Sie sich auf dem Laufenden
Websites wie www.php.net, www.cert.org, www.w3c.org und andere enthalten viele Informationen und Updates zu Sicherheitsproblemen rund um PHP und andere Internet-Applikationen. Selbst wenn manche der in www.cert.org und www.w3c.org beschriebenen Sicherheitsthemen nicht direkt mit PHP zu tun haben, lassen sich viele Problemstellungen auf PHP-Skripte übertragen.
Strategie 2: Eingabedaten auf kontrollierbare Quellen beschränken
Wenn die Konfigurationsdirektive register_globals in php.ini auf on gesetzt ist und der POST- oder GET-Request des Browsers beispielsweise einen Variablennamen namens xyz enthält, gibt es in Ihrem Skript eine Variable $xyz mit demselben Wert. Das kann ggf. bedeuten, dass Variablen vor ihrer ersten Verwendung bereits einen Wert enthalten, der nicht den üblichen Erwartungen entspricht.
Daher sollten Sie routinemäßig alle Variablen vor der ersten Verwendung mit einem von Ihnen bestimmten Anfangswert versehen. Wenn es möglich ist, sollten Sie auch register_globals in php.ini auf off setzen. Damit müssen Sie Ihre Eingabedaten explizit aus den entsprechenden globalen Arrays (z.B. $_POST) auslesen - man kann sie Ihnen nicht einfach unterjubeln.
Strategie 3: Alle Eingabedaten rigoros filtern
Eingabedaten sind all jene Daten, die Sie aus $HTTP_POST_VARS, $HTTP_GET_VARS, $HTTP_POST_FILES, $_POST, $_GET, $_FILES, $_COOKIE usw. entnehmen. Das schließt auch einige der Variablen ein, die vom Server selbst gesetzt werden, wie z.B. $_SERVER["REMOTE_ADDR"], die die - leicht zu fälschende - Internetadresse des Browsers enthält.
Je besser Sie kontrollieren, ob Ihre Eingabedaten auch tatsächlich Ihren Vorstellungen entsprechen, desto schwieriger wird es für Hacker, gültige Strings zum Einbrechen zu finden.
Dabei gibt es zwei Ansätze: Auf der einen Seite können Sie explizit nach gültigen Daten zum Durchlassen suchen. Wenn Ihr Eingabewert nur aus Kleinbuchstaben und Zahlen bestehen soll, können Sie dazu eine entsprechende Zeichenklasse in einem regulären Ausdruck verwenden. Dieser Ansatz ist relativ einfach, solange die erlaubten Zeichenkombinationen einfach zu definieren sind und keine Problem-Strings »durchschlüpfen« können.
Alternativ können Sie speziell nach Zeichen und Zeichenkombinationen suchen, die ein Hacker verwenden würde. Das ist ebenfalls in Ordnung, solange Sie dabei nach allen potenziell gefährlichen Strings suchen. In vielen Fällen ist eine Kombinationsmethode (enthält der Eingabe-String Daten von der erwarteten Sorte und keine gefährlich aussehenden Kombinationen?) die effektivste Methode.
Strategie 4: Überlegen Sie sich genau, welche Ihrer Variablen Anführungszeichen, Backslashs und NULL-Zeichen nur mit vorangehendem Backslash enthalten dürfen
Generell sollten alle theoretisch vom Benutzer beeinflussbaren Variablen durch Backslashs entschärft sein - das macht es einem Hacker schon erheblich schwerer, Ihnen zusammen mit Daten auch noch etwas unerwartetes SQL unterzujubeln. Dasselbe gilt für Daten, die an die Kommandozeile übergeben werden sollen.
Bei mir gilt eine Grundregel: Alle meine benutzerbestimmten Variablen müssen derart entschärft sein - wenn nicht, hole ich das nach, sobald ich sie im Skript in die Finger kriege. Die Konfigurationsdirektive magic_quotes_gpc macht schon den halben Job für Sie, auf dem Rückweg von der Datenbank bzw. Kommandozeile sorgt magic_quotes_runtime dafür. Zur Not können Sie ja mit addslashes() nachhelfen. Wie das geht, können Sie in Kapitel 6 nachlesen.
Strategie 5: Identifizieren Sie Funktionen, mit denen sich Schaden anrichten lässt, und stellen Sie sicher, dass sie nicht missbraucht werden können
Bevor Sie eine Funktion verwenden, empfiehlt es sich, die dazugehörige PHP-Dokumentation durchzulesen. Greift die Funktion auf eine Datei, das Internet, eine Datenbank oder externe Programme zu?
Wenn ja, sollten Sie sicherstellen, dass genau feststeht, wie und mit welchen Daten diese Funktion aufgerufen wird. Lassen Sie beim Aufruf solcher Funktionen so wenig Benutzereingaben einfließen wie möglich. Verpacken Sie Benutzereingaben sicher durch Backslash-Escapes, Anführungszeichen usw.
Strategie 6: Denken Sie wie ein Hacker
Angriff ist die beste Verteidigung - zumindest, was Websites betrifft. Überlegen Sie sich, welche »Einstiegslöcher« ein Hacker in Ihrer Site ausnutzen könnte. Welche Befehle in Ihren Skripten geben Zugang zu Dateien, Datenbanken, externen Programmen oder ins Internet? Wie könnte man diese zu einem Angriff ausnutzen?
Strategie 7: Halten Sie Ihren Code unter Verschluss
Einige der prominentesten Hackerangriffe wurden durch bereits bekannte Sicherheitslücken in bekannt problematischen Skripten ausgeführt, die zum freien Download im Internet zur Verfügung standen. Generell machen Sie potenziellen Einbrechern das Leben schwer, wenn diese Ihren Skript-Code nicht kennen und der Code zumindest gegen die 08/15-Tricks immun ist. Wenn Sie stolz auf ein Skript sind, brauchen Sie es deswegen ja noch nicht gleich zum Download anzubieten.
Strategie 8: Halten Sie Ihre Installationen aktuell
Es kommt immer wieder vor, dass Sicherheitslücken in Betriebssystemen und Applikationen wie Webservern, PHP oder Datenbank-Servern entdeckt werden. Allein in der Zeit, in der dieses Buch geschrieben wurde, wurden mehrere Probleme in Apache, MS IIS, PHP und MySQL bekannt. In solchen Fällen veröffentlichen die Hersteller bzw. Entwickler meist sehr schnell Software-Updates, mit denen Sie die Löcher stopfen können. Wenn ein Loch bekannt wird, sollten Sie so schnell wie möglich aufrüsten. Hacker suchen gezielt nach alter Software mit bekannten Sicherheitsproblemen.
Strategie 9: Betreiben Sie PHP in safe_mode
Dazu müssen Sie Zugang zu der Konfiguration Ihres PHP-Servers haben. Hier können Sie gegebenenfalls safe_mode zu on setzen und konfigurieren. Damit blockieren bzw. beschränken Sie die Ausführung einiger potenziell gefährlicher Funktionen. So können Sie z.B. verlangen, dass auf Systemen mit Benutzerkonten PHP nur auf solche Dateien und Verzeichnisse zugreifen kann, die denselben Eigentümer haben wie das gerade ausgeführte Skript. Bevor Sie safe_mode konfigurieren, sollten Sie sich das entsprechende Kapitel in der PHP-Dokumentation durchlesen.

Selbst wenn Sie alle diese Strategien beherzigen - es gibt keine absolute Sicherheitsgarantie. Trotzdem können Sie die Risiken erheblich einschränken. Hacker, die nur in »irgendeinen Server« einsteigen wollen, werden sich ein anderes Opfer suchen, wenn Ihre Site kein leichtes Ziel ist.

Fehlersuche beim Programmieren

Okay, jetzt ist ein Geständnis fällig: Ich habe Ihnen bisher in weit über hundert Seiten ein Skript nach dem anderen vorgesetzt. Sie haben möglicherweise dutzende von Stunden damit verbracht, sie Zeile für Zeile zu studieren und nachzuvollziehen.

Nun glauben Sie hoffentlich nicht, dass ich mir solche Skripte und Klassen mal so eben im Zehnfingersystem an meiner Tastatur als Fließtext heruntertippe. Im Gegenteil: Tippfehler sind mir alles andere als fremd, und der erste Entwurf eines Skripts, den ich von meinem Browser aus aufrufe, ist meist nur eine grobe Idee. Auf Anhieb klappt bei mir fast nichts.

Wenn Sie also bei einem Ihrer Skripte Anlaufschwierigkeiten haben, sind wir da schon zu zweit. Die Frage ist also: Wie kommt man von den ersten Fehlermeldungen zu einem lauffähigen Skript, das genau das macht, was es soll?

Software schrittweise bauen

Der erste Schritt zur Fehlerbehebung fängt bereits an, bevor Sie Ihre erste PHP-Fehlermeldung im Browser sehen. Es ist nämlich keineswegs gute Praxis, Ihre Software monolithisch und in einem Durchgang zu schreiben. Wenn Sie das tun, wird die Fehlersuche extrem schwierig.

Zunächst ist es eine gute Idee, so viel Funktionalität wie möglich in Klassen und Methoden zu packen. Diese lassen sich oft mit geringem Aufwand durch ein kleines Testskript überprüfen. Damit können Sie sich darauf konzentrieren, die Klassen und Methoden einzeln fehlerfrei zum Laufen zu bekommen, bevor Sie sie zu einem Anwendungsskript zusammenfassen. Es ist ein bisschen wie in der Musik: Erst lernt der Musiker ein Instrument, dann das Zusammenspiel im Orchester.

Klassen können Sie oft schrittweise bauen. Fangen Sie mit wenigen Eigenschaften und (falls erwünscht) der Konstruktormethode an. Probieren Sie in einem Testskript aus, ob Sie ein Objekt der Klasse mit new erzeugen können. Dann können Sie sehen, ob der Konstruktor auch alle Eigenschaften so setzt wie erwartet. Wenn das läuft, können Sie Schritt für Schritt weitere Methoden hinzufügen und testen.

Syntaxfehler

Wenn ich ein Skript schreibe, sind die ersten Fehler, die ich bekomme, meist Syntaxfehler (Syntax Error). Ein Syntaxfehler bedeutet, dass PHP nicht versteht, was Sie in einer Anweisung wollen. Syntaxfehler machen sich in der Regel entweder als Parse Error (Problem trat bereits beim Einlesen der Syntaxstruktur auf) bemerkbar oder als Fatal Error (Syntaxstruktur war in Ordnung, aber bei der Ausführung wurde ein Fehler gefunden, z.B. eine unbekannte Funktion).

Typische Syntaxfehler sind:

Wie so oft, ist auch hier Vorbeugen besser als Heilen. Wenn ich einen Ausdruck in Klammern schreibe (z.B. bei einem if-Statement), schreibe ich erst beide Klammern und danach den Inhalt derselben. So vermeide ich es, dass ich Klammern aus Versehen offen lasse. Wenn es mir doch passiert, ist das meist in Code der Fall, den ich durch Kopieren und Einfügen an eine andere Stelle verpflanzt habe. Bei Strings gilt natürlich dasselbe: Erst kommen beide Anführungszeichen, dann schreibe ich den eigentlichen String dazwischen.

In den meisten Fällen sagt Ihnen PHP, in welcher Zeile Ihres Skripts sich das Problem befindet, und oft auch, worum es sich bei dem Problem handelt. Wenn Sie das bemäkelte Statement für richtig halten, lohnt sich oft auch ein Blick in die vorhergehende Codezeile: Vielleicht fehlt dort ein Semikolon, was PHP aber erst in der darauf folgenden Zeile merkt. Beispiel:

$x = 5    // Semikolon fehlt hier.
 
$y = "Hallo";    // Fehler tritt hier auf.
 

Ähnliches gilt für Klammern - sowohl runde als auch geschweifte. Auch hier kann sich die fehlende, zu schließende oder zusätzliche öffnende Klammer in vorhergehenden Zeilen befinden, manchmal erheblich weiter oben im Skript.

Gerade bei if-Statements oder großen, verschachtelten Schleifen lässt sich das richtige Setzen von Klammern oft nur schwer prüfen. Auch hier hilft: sauberes Code-Schreiben (Einrücken) und so viel Funktionalität wie möglich in Funktionen bzw. Klassen verlagern. Es ist auch eine gute Praxis, den schließenden Klammern einen Kommentar beizufügen, z.B. so:

} // Ende der Hauptschleife, die die Tabellenzeilen ausgibt.
 

Auf diese Weise lassen sich verwaiste Klammern einfacher finden.

Bei Klassen-Deklarationen machen sich verwaiste Klammern in Methoden beispielsweise durch ein »Cannot instantiate non-existent class« bemerkbar.

Fehlende Anführungszeichen nach Strings können ebenfalls schwer zu finden sein:

$x = "In dieser Zeile beginnt ein endloser String;
 
$y = 6 * sin(2.134);
 
$z = "Hier gibt's Probleme!"; // Fehlermeldung verweist auf diese Zeile.
 

Ein Trick, der hier weiterhilft, ist das großzügige Auskommentieren großer Skriptblöcke mit /* und */, die ich aus diesem Grund sonst ungern als Kommentarzeichen verwende. Im Regelfall setze ich das Ende des Kommentars hinter die bemängelte Zeile. Den Anfang des Kommentars setze ich dann ein paar Zeilen oberhalb. Wenn die Fehlermeldung verschwindet, liegt das Problem innerhalb des Kommentars:

/*  $x = "In dieser Zeile beginnt ein endloser String;
 
 $y = 6 * sin(2.134);
 
 $z = "Hier gibt's Probleme!"; // Fehlermeldung verweist auf diese Zeile.
 
*/
 

Wenn nicht, ist der Kommentaranfang Teil des endlosen Strings, und das Problem muss weiter oben liegen:

 $x = "In dieser Zeile beginnt ein endloser String;
 
/* $y = 6 * sin(2.134);
 
 $z = "Hier gibt's Probleme!"; // Fehlermeldung verweist auf diese Zeile.
 
*/
 

Da PHP sowohl einfache als auch doppelte Anführungszeichen als String-Begrenzung zulässt, kann das Problem von beiden Arten verursacht werden.

Mein Code benimmt sich daneben!

Wenn ich meine Skripte ohne Syntaxfehler zum Laufen bringe, kommt meist gleich das nächste Problem: Mein Code verhält sich anders als erwartet. Bei mir hat das meist eine oder mehrere der folgenden Ursachen:

Für derartige Fälle gibt es ein altbewährtes Hausmittel: Sie überlegen sich, welche Variable oder Variablen an der Schurkerei beteiligt sein könnten. Dann fügen Sie an strategischen Stellen in Ihrem Code echo-Befehle ein, die Ihnen mitteilen, wie es um Ihre Variablen steht:

Beispiel 8-10
Debugging mit echo-Befehlen
echo "Der Wert von \$x vor dem Aufruf von Funktion xyz() ist ".$x."<br>";
 
$x = xyz($x);
 
echo "Der Wert von \$x nach dem Aufruf von Funktion xyz() ist ".$x."<br>";
 

Ich füge solche Befehle oft schon beim Schreiben ein, qualifiziere sie dann aber in der Regel mit if-Statements, z.B. so:

if ($debug) {
 
    echo "\$x hat den Wert ".$x."<br>";
 
}
 

Dann kann ich über die Variable $debug diese Meldungen nach Bedarf an- und abschalten. (Achtung: $debug muss innerhalb von Funktionen als global deklariert werden!)

Wenn Sie nicht so ohne weiteres echo-Statements einfügen können (z.B. weil Ihr PHP-Skript JavaScript-Quellcode zur Einbindung in ein JavaScript-Skript liefert), können Sie stattdessen auch eine Log-Lösung wie bei der Protokollierung von Datenbankbefehlen im Abschnitt 1

Fehlersuche in Grafikskripten

Bei der Fehlersuche in Grafikskripten bekommen Sie oft nur den Dateinamen des Skripts zu sehen, wenn etwas schief läuft. Nicht sehr informativ. Kommentieren Sie mit // das header-Kommando aus, das den Content-type-Header ausgibt. Dann sehen Sie alle Fehlermeldungen wie gewohnt. Wenn Sie statt einer Fehlermeldung eine Folge kryptischer Zeichen sehen, dürfte das Problem nicht an Ihrem Skript liegen - sehen Sie stattdessen in der (HTML-)Datei nach, die das Bild einbindet.

Wenn es sich um einen neuen Server handelt, verifiziere ich oft zuerst, ob PHP mit GD-Unterstützung installiert ist. Wenn ja, muss ich zumindest ein Testbild mit farbigem Hintergrund zu Stande bringen. Dazu verwende ich eine Mini-Testdatei, die mir nichts außer einem roten Quadrat zeigt:

Beispiel 8-11
Ein einfaches Testbild-Skript
<?php
 
    header("Content-type: image/png");
 
    $img = ImageCreate(100,100);
 
    $bg = ImageColorAllocate($img,255,0,0);
 
    ImagePNG($img);
 
?>
 

Wenn ich hier rot sehe, kann ich auch Grafikskripte installieren.

Debugging durch Textausgabe

Wenn Sie sich lieber die HTML-Quellcode-Ausgabe Ihres Skripts ansehen als die vom Browser interpretierte Version, können Sie das über die Option Ansicht > Seitenquelltext anzeigen Ihres Browsers machen.

Ein kleines Problem besteht hierbei darin, dass einige Browser (insbesondere Netscape) zur Ausführung dieser Option die Seite neu laden bzw. den Seitenquelltext nicht anzeigen, wenn er durch einen POST-Request erzeugt worden ist. Da gibt es Abhilfe. Fügen Sie am Anfang Ihres Comboskripts die folgenden Zeilen ein:

<?php
 
    if (sizeof($_POST) > 0) {
 
        header("Content-type: text/plain");
 
    }
 
?>
 

Durch diese Zeilen wird bei einem POST-Request ein Response-Header eingefügt, der die folgende Skriptausgabe als Text ausweist. Damit wird sie auch so im Browser-Fenster dargestellt.

Skriptausführung über die Kommandozeile

PHP kann sowohl als Server-Modul als auch als CGI-Programm ausgeführt werden. Die letztere Option beinhaltet ein Programm namens php bzw. php.exe. Dieses Programm können Sie auch ganz ohne Beteiligung des Webservers von der Kommandozeile aus aufrufen. Unter Windows geht das z.B. so:

c:\php\cli\php.exe "c:\Eigene Dateien\PHP-Skripte\meinSkript.php"
 

Damit können Sie nicht nur auf Fehlersuche gehen, sondern auch fast den gesamten Funktionsumfang von PHP ohne Server benutzen.

So, jetzt können Sie in Sachen PHP schon ziemlich komplexe Probleme selbst angehen. Da kann ich Ihnen nur noch viel Spaß wünschen!


TOC PREV NEXT INDEX

Copyright © 2004 by O'Reilly Verlag GmbH & Co.KG