Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
1 Einführung
2 Mathematische und technische Grundlagen
3 Hardware
4 Netzwerkgrundlagen
5 Betriebssystemgrundlagen
6 Windows
7 Linux
8 Mac OS X
9 Grundlagen der Programmierung
10 Konzepte der Programmierung
11 Software-Engineering
12 Datenbanken
13 Server für Webanwendungen
14 Weitere Internet-Serverdienste
15 XML
16 Weitere Datei- und Datenformate
17 Webseitenerstellung mit (X)HTML und CSS
18 Webserveranwendungen
19 JavaScript und Ajax
20 Computer- und Netzwerksicherheit
A Glossar
B Zweisprachige Wortliste
C Kommentiertes Literatur- und Linkverzeichnis
Stichwort

Jetzt Buch bestellen
Ihre Meinung?

Spacer
IT-Handbuch für Fachinformatiker von Sascha Kersken
Der Ausbildungsbegleiter
Buch: IT-Handbuch für Fachinformatiker

IT-Handbuch für Fachinformatiker
Rheinwerk Computing
1216 S., 6., aktualisierte und erweiterte Auflage, geb.
34,90 Euro, ISBN 978-3-8362-2234-1
Pfeil 18 Webserveranwendungen
Pfeil 18.1 PHP
Pfeil 18.1.1 Sprachgrundlagen
Pfeil 18.1.2 Klassen und Objekte
Pfeil 18.1.3 Include-Dateien, Autoloader und Namespaces
Pfeil 18.1.4 Webspezifische Funktionen
Pfeil 18.1.5 Zugriff auf MySQL-Datenbanken
Pfeil 18.1.6 Unit-Tests mit PHPUnit
Pfeil 18.2 Ruby on Rails
Pfeil 18.2.1 Grundlagen
Pfeil 18.2.2 Ein Praxisbeispiel
Pfeil 18.3 Weitere Technologien im Überblick
Pfeil 18.3.1 Content Management, Weblogs & Co.
Pfeil 18.3.2 Blogs und Wikis
Pfeil 18.4 Zusammenfassung

18 WebserveranwendungenZur nächsten Überschrift

PHP is about as exciting as your toothbrush. You use it every day, it does the job, it is a simple tool, so what? Who would want to read about toothbrushes?[Anm.: PHP ist ungefähr so aufregend wie deine Zahnbürste. Du benutzt sie jeden Tag, sie tut ihren Dienst, sie ist ein einfaches Werkzeug, also was soll’s? Wer würde etwas über Zahnbürsten lesen wollen?]
– Rasmus Lerdorf

Das Grundprinzip von Webserveranwendungen ist immer dasselbe: Wenn ein Benutzer eine bestimmte URL anfordert, die auf einen Teil einer solchen Anwendung verweist, liefert der Webserver nicht einfach ein fertiges Dokument aus. Stattdessen startet er irgendeine Art von Programm, das aus einer Vorlage und variablen Daten »on the Fly« eine Webseite erstellt, und liefert diese dynamisch erzeugte Seite an den Browser des Besuchers aus.

Bei dem Programm, das der Webserver aufruft, handelt es sich je nach verwendeter Serverlösung um ein externes Programm, das separat gestartet wird, oder aber um ein Modul des Webservers selbst. Letzteres ist erheblich effizienter – der Webserver kann die Anfrage selbst bearbeiten und muss kein separates Programm starten. Bedenken Sie, dass bei einem externen Programm für jeden Aufruf ein neuer Prozess gestartet wird, was bei vielen zeitgleichen Benutzern zu erheblichen Engpässen führen kann.

In diesem Kapitel wird zuerst die beliebte Webserver-Programmiersprache PHP vorgestellt. Da größere Webanwendungen so gut wie immer auf einer Datenbank basieren, wird zusätzlich die Zusammenarbeit mit dem Datenbankserver MySQL beschrieben. Im zweiten Abschnitt erhalten Sie eine kompakte Praxiseinführung in das moderne Web-Framework Ruby on Rails. Zum Schluss wird dann ein Schnellüberblick über einige weitere Technologien für serverseitige Webanwendungen geliefert, einschließlich integrierter Lösungen wie etwa Content-Management-Systemen.


Rheinwerk Computing - Zum Seitenanfang

18.1 PHPZur nächsten ÜberschriftZur vorigen Überschrift

Die Sprache PHP ist eines der beliebtesten Werkzeuge zur Erstellung dynamischer Webinhalte für kleine und mittlere Websites. Der Name dieser 1995 von Rasmus Lerdorf unter der ursprünglichen Bezeichnung Personal Homepage Tools entwickelten Server-Skriptsprache steht inzwischen für das rekursive Akronym PHP: Hypertext Preprocessor.

Die Sprache ist für viele verschiedene Plattformen wie Windows und etliche Unix-Varianten verfügbar. Besonders verbreitet ist die Kombination aus dem Betriebssystem Linux, dem Webserver Apache, der freien Datenbank MySQL und der Programmiersprache PHP (manchmal auch Perl oder Python) – kurz ein LAMP-System. Unter dem Betriebssystem Windows wird dieselbe Softwarekombination WAMP genannt. Wie Sie PHP in Ihrem Apache-Webserver installieren, wird in Kapitel 13, »Server für Webanwendungen«, beschrieben.


Rheinwerk Computing - Zum Seitenanfang

18.1.1 SprachgrundlagenZur nächsten ÜberschriftZur vorigen Überschrift

Der PHP-Interpreter akzeptiert gewöhnliche HTML-Dateien, in denen speziell markierte PHP-Abschnitte verarbeitet und durch ihre Ausgabe ersetzt werden. Sie müssen also kein HTML ausgeben, sondern können PHP-Anweisungen an die passende Stelle des HTML-Dokuments schreiben. In größeren Anwendungen ist es allerdings sehr zu empfehlen, Logik und Ausgabe voneinander zu trennen. Solche Konstrukte sollten daher höchstens in reinen Ausgabedateien verwendet werden – wenn nicht ohnehin ein eigenständiges Template-System zum Einsatz kommt, das für die Ausgabe selbst kein PHP verwendet.

Der PHP-Code wird in einen Bereich hineingeschrieben, der folgendermaßen gekennzeichnet wird:

<?php
// PHP-Anweisungen
?>

Ein solcher Bereich kann an einer beliebigen Stelle im HTML-Dokument stehen, sogar innerhalb von HTML-Tags oder ihren Attributwerten. Außerdem können sich HTML- und PHP-Blöcke an einer beliebigen Stelle und selbst innerhalb derselben Zeile abwechseln. Konstrukte wie das folgende sind ohne Weiteres möglich und sind in den besagten Ausgabedateien mitunter praktisch:

<?php if ($punkte > 100) { ?>
<h2>Herzlichen Gl&uuml;ckwunsch!</h2>
<?php } else { ?>
<h2>Sie sollten noch &uuml;ben!</h2>
<?php } ?>

Dieses Beispiel gibt die Überschrift »Herzlichen Glückwunsch!« aus, falls die Variable $punkte einen höheren Wert als 100 hat, ansonsten den Text »Sie sollten noch üben!«. Die folgende Schreibweise ist synonym, aber für eine reine Ausgabedatei unhandlicher:

<?php

if ($punkte > 100) {
echo "<h2>Herzlichen Gl&uuml;ckwunsch!</h2>";
} else {
echo "<h2>Sie sollten noch &uuml;ben!</h2>";
}

?>

Das schließende PHP-Tag ?> können Sie übrigens beim letzten (oder einzigen) PHP-Block in einer Datei weglassen. Bei PHP-Dateien, die ausschließlich Programmlogik enthalten, ist dies sogar empfehlenswert. Whitespace hinter dem schließenden Tag könnte nämlich als Ausgabeinhalt interpretiert werden, sodass Mechanismen wie Weiterleitungen oder Cookies, die auf HTTP-Headern basieren, dann unter Umständen nicht mehr funktionieren.

Der Sprachkern von PHP wurde stark von Programmiersprachen wie C und Perl inspiriert. PHP war ursprünglich eine prozedurale Sprache, wurde aber in neueren Versionen um objektorientierte Merkmale erweitert.

Wie die meisten anderen Skriptsprachen ist PHP nicht typisiert, eine Variable kann also nacheinander Werte beliebiger Datentypen annehmen. Grundlegende Kontrollstrukturen wie Fallunterscheidungen und Schleifen funktionieren genau wie in C und in allen davon abgeleiteten Sprachen.

Variablenbezeichner beginnen grundsätzlich mit einem $-Zeichen. Anders als in Perl gibt es keine besonderen Zeichen, die Arrays oder Hashes kennzeichnen (diese sind in PHP ohnehin dasselbe; ein Array kann sowohl Zahlen als auch andere Objekte als Indizes und Schlüssel verwenden). Hinter dem $ können Buchstaben, Ziffern und Unterstriche folgen; das erste Zeichen darf allerdings keine Ziffer sein. Es wird zwischen Groß- und Kleinschreibung unterschieden.

Funktions- und Klassennamen kommen dagegen ohne Dollarzeichen aus, abgesehen davon werden Groß- und Kleinschreibung weder bei selbst definierten noch bei eingebauten Funktions- und Klassennamen unterschieden. Allerdings wäre es sehr schlechter Programmierstil, dies auszunutzen. Üblicherweise sollten Sie Variablen- und Funktionsbezeichner mit kleinem und Klassennamen mit großem Anfangsbuchstaben schreiben. Bestehen die Bezeichner aus mehreren Wörtern, sollten Sie CamelCase (Binnenmajuskeln für jedes neue Wort) verwenden und keine Unterstriche (also beispielsweise $myVariable statt $my_variable).

Variablendefinition und -verwendung

Eine Variable wird in PHP durch die erste Wertzuweisung (Initialisierung) erzeugt. Wertzuweisungen sehen genauso aus wie in den meisten anderen Programmiersprachen:

$test = 9;
$text = "hallo";

In dem Moment, in dem einer Variablen zum ersten Mal ein Wert zugewiesen wird, existiert sie. Eine Deklaration im eigentlichen Sinn gibt es nicht.

Variablen haben in PHP auch keinen festgelegten Datentyp. Sie können einer Variablen nacheinander verschiedene Arten von Werten zuweisen, zum Beispiel:

$a = 5;     // Ganzzahl
$a = 3.78; // Fließkommazahl
$a = "hi"; // String

Mitunter werden die Werte von Variablen in einem neuen Zusammenhang automatisch anders interpretiert. Hier sehen Sie zwei Beispiele:

$b = "67";      // String, wegen Anführungszeichen
$c = $b + 9; // Ergebnis: 76
$a = 22; // Ganzzahl
$b = $a . "33"; // "2233"

Genau wie in Perl ist der Verkettungsoperator für Strings der Punkt (.) und nicht das in Java und JavaScript verwendete Pluszeichen, das häufig wegen der Verwechslung mit der numerischen Addition Ärger bereitet. Die meisten anderen Operatoren entsprechen ebenfalls ihrer Verwendung in Perl. Der einzige wichtige Unterschied zu Perl besteht darin, dass Strings in PHP mit den normalen Vergleichsoperatoren wie ==, != oder <= verglichen werden und nicht mit speziellen Operatoren. Genaueres über die Perl-Operatoren finden Sie in Kapitel 9, »Grundlagen der Programmierung«.

Variablen gelten in PHP ab dem Zeitpunkt ihrer Wertzuweisung im gesamten Dokument. Es gibt keine untergeordneten Gültigkeitsbereiche innerhalb von Blöcken wie Fallunterscheidungen oder Schleifen. Die Ausnahme bilden Variablen, die innerhalb der im weiteren Verlauf des Kapitels behandelten Funktionen definiert werden. Sie sind lokal und gelten nur innerhalb der jeweiligen Funktion.

Arrays

Wie in den meisten anderen Programmiersprachen gibt es auch in PHP die Möglichkeit, Arrays zu bilden. Ein Array ist eine Variable, die eine Liste von Werten enthält, auf die über einen Index zugegriffen werden kann. PHP unterscheidet nicht grundsätzlich zwischen einem gewöhnlichen Array mit numerischen Indizes und einem Hash, bei dem die Indizes Strings (oder andere Objekte) sind. In jedem Array können beide Indexarten gleichzeitig existieren.

Um ein klassisches Array mit numerischen Indizes zu erzeugen, genügt es, einem einzelnen Element dieses Arrays einen Wert zuzuweisen:

$monate[0] = "Januar";

Mithilfe der eingebauten Funktion array() können Sie auch gleich ein numerisches Array mit mehreren Elementen erzeugen:

$jahreszeiten = array(
"Fruehling",
"Sommer",
"Herbst",
"Winter"
);

Seit PHP 5.4 ist alternativ auch die folgende Schreibweise erlaubt:

$jahreszeiten = [
"Fruehling",
"Sommer",
"Herbst",
"Winter"
];

Besonders interessant ist im Übrigen die Tatsache, dass Sie leere eckige Klammern statt eines konkreten Indexes verwenden können, um ein Element am Ende des Arrays anzufügen:

$wochentage[] = "Sonntag";
$wochentage[] = "Montag";
// und so weiter

Über die Funktion array_push($array, $wert1, $wert2, ...) können Sie aber auch eine Liste mehrerer Elemente am Ende des Arrays einfügen. Umgekehrt liefert die Funktion array_pop($array) das letzte Element des Arrays zurück und entfernt es aus dem Array.

Um einen Hash oder ein assoziatives Array zu erzeugen, können Sie ebenfalls mit der Zuweisung des Wertes für ein einzelnes Element beginnen:

$monate['jan'] = "Januar";

Möchten Sie die Werte mehrerer Elemente gleichzeitig zuweisen, funktioniert dies mithilfe der folgenden Form der Funktion array():

$wochentage = array(
'So' => "Sonntag",
'Mo' => "Montag",
'Di' => "Dienstag",
'Mi' => "Mittwoch",
'Do' => "Donnerstag",
'Fr' => "Freitag",
'Sa' => "Samstag"
);

Die Verwendung einfacher Anführungszeichen für die Indizes und doppelter für die Werte ist nicht vorgeschrieben, aber eine gängige Konvention. Wie in Perl werden innerhalb doppelter Anführungszeichen Variablen und alle üblichen Escape-Sequenzen ausgewertet, innerhalb von einfachen aber nicht (lediglich \' für ein einzelnes Anführungszeichen als solches und \\ für einen Backslash werden erkannt):

$geld = 100;
echo "Ich habe $geld \$.";
// Ausgabe: Ich habe 100 $.
echo 'Ich habe auch $geld $.';
// Ausgabe: Ich habe auch $geld $.

Die Funktion count($array) – ein gültiges Synonym ist sizeof($array) – liefert die Anzahl der Elemente im Array zurück. Auf diese Weise können Sie alle Elemente eines numerischen Arrays in einer Schleife ausgeben, zum Beispiel folgendermaßen:

$zimmer = array(
"Wohnzimmer",
"Schlafzimmer",
"Kinderzimmer",
"Arbeitszimmer"
);
$zahl = sizeof($zimmer);
for ($i = 0; $i < $zahl; $i++) {
echo $zimmer [$i]."<br />";
}

Dieses kleine Beispiel gibt untereinander die Bezeichnungen der vier Zimmer aus. Bei Hashes sollten Sie dagegen eine andere Methode verwenden, die Elemente aufzuzählen: Die Funktion each($array) gibt bei jedem Aufruf das nächste Schlüssel-Wert-Paar zurück. Dieses Paar können Sie mithilfe von list() einer Liste aus zwei Variablen zuweisen. Das Ganze funktioniert so:

$rechner = array (
'cpu' => "Intel Core 2 Duo",
'ram' => "2048 MB DDR2 RAM",
'hdd' => "Maxtor 500 GB",
'dvd' => "16xDVD / 52xCD"
);
reset($rechner); // Sicherheitshalber auf Anfang
while (list($key, $val) = each ($rechner)) {
echo "$key: <b>$val</b><br />";
}

Noch klarer und einfacher sind foreach-Schleifen, die für die beiden Arrays wie folgt aussehen:

foreach ($zimmer as $z) {
echo "$z<br />";
}
foreach ($rechner as $key => $val) {
echo "$key: <b>$val</b><br />";
}

Interessant ist auch noch die Funktion explode(). Sie funktioniert nach folgendem Schema:

$array = explode($trennString, $string);

Die Funktion zerlegt den String $string an den Stellen, an denen $trennString vorkommt, in die einzelnen Elemente des Arrays $array. Zum Beispiel:

$string = "a,b,c,d";
$array = explode(",", $string);
/* $array[0] ist "a"
$array[1] ist "b"
$array[2] ist "c"
$array[3] ist "d" */

Wenn Sie statt eines einfachen Strings einen regulären Ausdruck als Trennzeichen angeben möchten, müssen Sie die Anweisung preg_split($regexp, $array) statt explode() verwenden. Näheres zur Verwendung regulärer Ausdrücke in PHP lesen Sie im nächsten Abschnitt.

Die umgekehrte Aufgabe erledigt die Funktion implode(), die die Elemente eines Arrays – getrennt durch das angegebene Muster – zu einem String zusammenfasst:

$string = implode($trennString, $array);

Hier sehen Sie ein Beispiel:

$array = array ("So", "Mo", "Di", "Mi", "Do", "Fr", "Sa");
$string = implode (", ", $array);
// $string ist "So, Mo, Di, Mi, Do, Fr, Sa";

Auch mehrdimensionale Arrays sind kein Problem. Das folgende Beispiel definiert die Variable $jahr als Aufzählung der Jahreszeiten mit ihren Monaten:

$jahr = array(
'fruehling' => array("März", "April", "Mai"),
'sommer' => array("Juni", "Juli", "August"),
'herbst' => array("September", "Oktober", "November"),
'winter' => array("Dezember", "Januar", "Februar")
);

Wenn Sie nun beispielsweise auf den zweiten Monat im Sommer zugreifen möchten, können Sie die Schreibweise $jahr['sommer'][1] verwenden – die Rückgabe lautet natürlich »Juli«.

Perl-kompatible reguläre Ausdrücke

Beinahe der gesamte in Kapitel 10, »Konzepte der Programmierung«, beschriebene Perl-Komfort für reguläre Ausdrücke steht erfreulicherweise auch in PHP zur Verfügung. Die Syntax der regulären Ausdrücke selbst ist praktisch identisch, allerdings unterscheidet sich ihre Handhabung ein wenig, da PHP mit konventionellen Funktionen statt mit seltsamen Sonderzeichen operiert.

Die Funktion preg_match($regexp, $string) überprüft, ob der reguläre Ausdruck $regexp auf den String $string passt. Das folgende Beispiel gibt eine Meldung aus, falls $eingabe nicht komplett aus Ziffern besteht:

if (preg_match('/\D/', $eingabe)) {
echo "Dies ist keine Zahl!";
}

Innerhalb der String-Anführungszeichen steht der reguläre Ausdruck zwischen zwei // (Slashes) oder wahlweise zwischen anderen Trennzeichen wie ~~ oder Klammerpaaren. Hinter dem schließenden Trennzeichen können – wie in Perl – Modifikatoren stehen. Der wichtigste ist i, um Unterschiede zwischen Groß- und Kleinschreibung zu ignorieren. Das folgende Beispiel sucht nach »PHP« in beliebiger Schreibweise:

if (preg_match('(php)i', $eingabe)) {
echo("PHP gefunden!");
} else {
echo("PHP nicht vorhanden!");
}

preg_replace($regexp, $ersatz, $string[, $limit]) ersetzt den regulären Ausdruck $regexp in $string durch den Ersatztext $ersatz. Normalerweise wird jedes Vorkommen von $regexp ersetzt; der optionale Parameter $limit gibt die maximale Anzahl von Ersetzungen an. Die folgende Anweisung ersetzt in $eingabe jedes Vorkommen von »Perl« in beliebiger Schreibweise durch »PHP«:

$eingabe = preg_replace('(perl)i', 'PHP', $eingabe);

Mithilfe von $n (mit n-Werten zwischen 0 und 99) können Sie sich im Ersatztext auf geklammerte Ausdrücke aus dem regulären Ausdruck beziehen. Das folgende Beispiel fügt an den korrekten Stellen einer alten, zehnstelligen ISBN Bindestriche an den gängigsten Stellen ein:

$isbn = preg_replace(
'{(\d)(\d{5})(\d{3})(\d)}',
"$1-$2-$3-$4",
$isbn
);

Aus 3836214202 (einer unformatierten ISBN) würde so beispielsweise 3-83621-420-2.

Kommentare

PHP unterstützt drei Arten von Kommentaren, um es Programmierern leicht zu machen, die verschiedene Sprachen beherrschen. Als Erstes wird der einzeilige Kommentar im C++-Stil unterstützt:

// einzeiliger Kommentar

Dabei wird der Rest der Zeile ignoriert. Auch der mehrzeilige Kommentar im C-Stil ist erlaubt:

/* mehr-
zeiliger
Kommentar */

Hier werden alle betroffenen Zeilen ignoriert. Eine spezielle Variante dieses Kommentars ist der sogenannte Docblock-Kommentar, der von einem Dokumentationsgenerator wie PHPDocumentor oder der empfehlenswerteren Neuentwicklung phpdox ausgewertet wird:

/**
* Dokumentation ...
* Mehr Dokumentation ...
*/

Docblock-Kommentare werden vor Klassen, Methoden, Funktionen oder Attribute geschrieben, und für jedes dieser Elemente gibt es eine Reihe von Annotationen, die mit einem @-Zeichen beginnen und spezielle Aspekte des jeweiligen Elements beschreiben. Hier zum Beispiel der Docblock-Header einer Methode oder Funktion mit einem Parameter und einem Rückgabewert:

/**
* Return the square of the argument
*
* @param integer $value
* @return integer
*/
function square($value) {
return $value * $value;
}

Schließlich ist noch der einzeilige Kommentar im Stil von Perl und der Unix-Shell-Sprachen zulässig:

# einzeiliger Kommentar

Funktionen

Eine PHP-Funktion ist ein benannter Codeblock, der mit seinem Namen aufgerufen wird. Ständig wiederkehrende Anweisungsfolgen in Funktionen zu verpacken schafft Übersicht und schont Ressourcen, da eine Funktion innerhalb eines Skripts nur einmal kompiliert wird. Sie sollten Funktionen in einem PHP-Block zu Beginn Ihres Dokuments unterbringen oder sie in einer externen Include-Datei (wird in Abschnitt 18.1.3, »Include-Dateien, Autoloader und Namespaces«, beschrieben) speichern. Die allgemeine Syntax ist einfach:

function funktionsname() {
// Anweisungen ...
}

Nach Ausführung der letzten Anweisung wird die Kontrolle an die aufrufende Stelle zurückgegeben. Wie bereits erwähnt, können Funktionen lokale Variablen enthalten: Jede Variable, die in einer Funktion verwendet wird, ist lokal – sogar dann, wenn im globalen Code eine gleichnamige Variable existiert. Betrachten Sie etwa den Wert der beiden Variablen namens $a im folgenden Beispiel:

function a_aendern() {
$a = 9; // lokales $a hat den Wert 9.
}

$a = 7; // globales $a hat den Wert 7.
a_aendern();
// $a hat hier immer noch den Wert 7.

Möchten Sie innerhalb einer Funktion auf eine globale Variable zugreifen, müssen Sie am Anfang der Funktion ausdrücklich das Schlüsselwort global verwenden. Hier sehen Sie das zuvor gezeigte Beispiel noch einmal, allerdings wird innerhalb der Funktion auf die globale Variable $a zugegriffen:

function a_aendern() {
global $a;
$a = 9; // globales $a hat nun den Wert 9.
}

$a = 7; // globales $a hat den Wert 7.
a_aendern();
// $a hat jetzt den Wert 9.

Eine Funktion kann Parameter entgegennehmen. Zu diesem Zweck müssen Sie bei der Definition die Namen der gewünschten Parametervariablen in den Klammern des Funktionskopfes angeben:

function topCell($inhalt) {
echo "<td valign=\"top\">$inhalt</td>";
}

Diese Funktion gibt den übergebenen Wert, der in der Parametervariablen $inhalt gespeichert wird, als Tabellenzelle mit der häufig verwendeten vertikalen Ausrichtung oben aus. Parametervariablen haben innerhalb der Funktion dieselbe Bedeutung wie lokale Variablen – der Standardfall beim Funktionsaufruf ist in PHP der Call by Value, das heißt die Übergabe eines Wertes ohne Rückbezug auf die aufrufende Stelle. Betrachten Sie dazu das folgende Beispiel:

function halbieren($wert) {
$wert /= 2;
}

$zahl = 9; // $zahl hat den Wert 9.
halbieren ($zahl);
// $zahl hat weiterhin den Wert 9.

Dies gilt jedoch nur für einfache Datentypen; beim Einsatz von objektorientiertem PHP sind die übergebenen Objekte stets Referenzen und keine Kopien. Das folgende kurze Beispiel definiert zunächst eine Klasse namens Test mit einem öffentlichen Attribut $value, das den Anfangswert 41 erhält. Anschließend wird außerhalb der Klasse eine globale Funktion namens modifyValue() definiert, die das Attribut $value eines übergebenen Objekts um 1 erhöht. Schließlich wird Test instanziiert, modifyValue() wird mit der neuen Instanz als Argument aufgerufen, und der Wert des Attributs wird ausgegeben:

<?php
class Test {
public $value = 41;
}

function modifyValue($test) {
$test->value++;
}

$test = new Test();
modifyValue($test);
echo $test->value."\n";

Sie brauchen das Beispiel nicht im Browser aufzurufen, sondern können es auch mit

> php Dateiname

auf der Konsole starten. In jedem Fall lautet die Ausgabe 42, da die Instanz $test als Referenz übergeben wird. Näheres zur objektorientierten Programmierung in PHP erfahren Sie in Abschnitt 18.1.2, »Klassen und Objekte«.

Falls Sie für einfache Datentypen einen Call by Reference benötigen, das heißt den Wert der Variablen verändern möchten, mit der die Funktion aufgerufen wird, müssen Sie der entsprechenden Parametervariablen bei der Funktionsdefinition ein &-Zeichen voranstellen. Wenn Sie die Funktion halbieren() folgendermaßen umschreiben, wird $zahl also tatsächlich halbiert:

function halbieren(&$wert) {
$wert /= 2;
}

$zahl = 10; // $zahl hat den Wert 10.
halbieren($zahl);
// $zahl hat nun selbst den Wert 5.

Beachten Sie bei einem Call by Reference, dass Sie der Funktion eine Variable übergeben müssen. Ein literaler Wert ist nicht gestattet und erzeugt eine Fehlermeldung.

Sie können Funktionsparametern Standardwerte zuweisen, um sie optional zu machen. Beim Aufruf können diese dann von rechts an weggelassen werden. Hier eine Variante der Tabellenzellen-Ausgabe, bei der Sie sich die vertikale Ausrichtung aussuchen können; 'top' ist Standard:

function tableCell($inhalt, $valign = 'top') {
echo ("<td valign=\"$valign\">$inhalt</td>");
}

Eine oben ausgerichtete Zelle können Sie dabei ohne Angabe des zweiten Arguments erzeugen:

tableCell("Oben ausgerichtet");

Es schadet aber auch nichts, den Wert "top" dennoch anzugeben:

tableCell("Explizit oben ausgerichtet", "top");

Eine Funktion kann auch einen Wert zurückgeben, sodass Sie das Ergebnis an der aufrufenden Stelle in einem Ausdruck einsetzen können. Dafür ist die Anweisung return zuständig. Sie verlässt die Funktion sofort und gibt den entsprechenden Wert zurück. Hier ein Beispiel:

function verdoppeln($wert) {
return $wert * 2;
}

Wenn Sie diese Funktion aufrufen, wird der Wert des übergebenen Arguments verdoppelt. Der fertig berechnete Ausdruck wird anschließend zurückgegeben. Sie können einen Aufruf dieser Funktion in einem beliebigen Ausdruck verwenden. Bevor dieser Ausdruck ausgewertet wird, erfolgt die Ausführung der Funktion, und es wird mit dem zurückgegebenen Wert weitergerechnet. Beispiel:

echo verdoppeln($zahl);

Diese Anweisung gibt den doppelten Wert der Variablen $zahl aus.

Die folgende Funktion zieht die n-te Wurzel aus einer Zahl. Wird der zweite Parameter weggelassen, wählt sie die Quadratwurzel (zweite Wurzel) als Standard:

function wurzel($zahl, $n = 2) {
return pow($zahl, 1 / $n);
}

Probieren Sie die Funktion aus, indem Sie zum Beispiel die zweite und die dritte Wurzel aus 64 ziehen:

$w = wurzel(64);      // ergibt 8
$w = wurzel(64, 3); // ergibt 4

Beachten Sie, dass eine Funktion mehrere return-Anweisungen enthalten kann, die von Fallentscheidungen abhängen. Sie benötigen noch nicht einmal else-Blöcke für solche if-Anweisungen, weil return die Ausführung der Funktion unmittelbar beendet. Die folgende Funktion macht sich das zunutze, um einen übergebenen Ausdruck daraufhin zu überprüfen, ob er ein Integer zwischen 1 und 4 ist:[Anm.: Wichtig: Benutzereingaben aus Webformularen sind grundsätzlich Strings, und eine Prüfung mit is_int() liefert false zurück. Allerdings können Sie mit is_numeric() überprüfen, ob die Eingabe Zahlen enthält.]

function antworttest($antwort) {
if (!is_int($antwort)) {
return FALSE;
}
if ($antwort < 1 || $antwort > 4) {
return FALSE;
}
// An dieser Stelle kann $antwort nur OK sein.
return TRUE;
}

In dem Beispiel wird als Erstes überprüft, ob $antwort überhaupt ein Integer ist – falls nicht, wird sofort FALSE zurückgegeben. Anschließend wird getestet, ob $antwort außerhalb des zulässigen Bereichs liegt – in diesem Fall erfolgt ebenfalls die Rückgabe von FALSE. Wenn keine der beiden if-Abfragen zutrifft, wird automatisch TRUE zurückgegeben.

Die verwendete eingebaute Funktion is_int() liefert übrigens true zurück, wenn es sich bei dem übergebenen Wert um einen Integer handelt, andernfalls false. In PHP steht eine Reihe solcher Funktionen zur Verfügung, um die Datentypen von Ausdrücken zu testen, beispielsweise is_string(), is_float(), is_numeric(), is_array(), is_object() oder is_null(), die das übergebene Argument daraufhin überprüfen, ob es ein String, eine Fließkommazahl, eine allgemeine Zahl, ein Array, ein Objekt (Instanz einer Klasse) oder NULL (leeres Element) ist.

Die verwandte Funktion empty() liefert TRUE zurück, wenn das untersuchte Element ein leerer String, die Zahl 0, ein leeres Array, FALSE oder NULL ist.

Eine ähnliche Aufgabe erfüllen die Funktionen isset() und isunset(), die überprüfen, ob eine Variable überhaupt jemals definiert wurde. Mithilfe der Anweisung unset() können Sie PHP sogar anweisen, eine Variable oder auch ein Element eines Arrays (anhand seines Indexes) vollständig zu vergessen.


Rheinwerk Computing - Zum Seitenanfang

18.1.2 Klassen und ObjekteZur nächsten ÜberschriftZur vorigen Überschrift

Auch wenn PHP ursprünglich als prozedurale Skriptsprache entwickelt wurde, unterstützt sie seit der Version 4 eine einfache Art der Objektorientierung, die in PHP 5 – und nochmals in Version 5.3 – erheblich erweitert wurde. Sie können Klassen mit Eigenschaften, Methoden und Konstruktoren definieren und voneinander ableiten. Wenn Sie mit diesen Grundbegriffen der Objektorientierung nichts anfangen können, lesen Sie bitte Abschnitt 9.2, »Java«, und/oder Abschnitt 9.4, »Ruby«.

Das folgende Beispiel definiert eine Klasse namens Hyperlink, die einen HTML-Hyperlink kapselt, erzeugt zwei Objekte dieser Klasse und gibt deren Inhalt aus:

<?php

class Hyperlink {
private $href = '';

private $caption = '';

public function __construct($href, $caption = '') {
$this->href = $href;
$this->caption = $caption;
}

public function __toString() {
$caption = ($this->caption != '') ? $this->caption : $this->href;
return sprintf(
'<a href="%1$s" title="%2$s">%2$s</a>',
$this->href,
htmlspecialchars($caption)
);
}
}

$linkWithCaption = new Hyperlink(
'https://www.galileo-press.de',
'Galileo Computing'
);

$linkWithoutCaption = new Hyperlink('http://www.heise.de');

printf("%s\n%s\n", $linkWithCaption, $linkWithoutCaption);

Wie Sie in diesem Beispiel sehen, wird eine Klasse mit dem Schlüsselwort class deklariert. Die Methoden sind einfache Funktionen, die in den Block der Klasse hineinverschachtelt werden; die Veröffentlichungsstufe public gibt dabei an, dass die Methoden von außen sichtbar sind. Ein Objekt einer Klasse wird über den Operator new erzeugt und ruft, falls vorhanden, den Konstruktor der Klasse auf (siehe im Folgenden). Der Zugriff auf Methoden und Attribute eines Objekts erfolgt mithilfe des Operators ->.

Das Beispiel erzeugt die folgende HTML-Ausgabe:

<a href="https://www.rheinwerk-verlag.de/?GPP=openbook" title="Rheinwerk Computing">Galileo Computing</a>
<a href="http://www.heise.de" title="http://www.heise.de">http://www.heise.de</a>

Eine Besonderheit in der gezeigten Klasse bildet die Methode __toString(). Es handelt sich um eine sogenannte magische Methode; sie wird automatisch aufgerufen, wenn die Instanz in einem String-Kontext verwendet wird – hier beispielsweise in der Ausgabe mithilfe von printf(). Der Konstruktor ist übrigens ebenfalls eine magische Methode; im weiteren Verlauf des Kapitels werden Sie noch weitere kennen lernen.

Beachten Sie in der Methode __toString() noch die beiden folgenden Besonderheiten:

  • Es wird eine erweiterte Form von sprintf() verwendet, um die Ausgabe zu formatieren. Da die Linkbeschriftung $caption (explizite Beschriftung oder, falls sie leer ist, der Link selbst) zweimal benötigt wird – einmal für das Attribut title und einmal für die Textausgabe –, wird bei den jeweiligen Platzhaltern die Position angegeben: %1$s ist das erste Ersetzungselement (durch das s als String formatiert) und %2$s das zweite. Denken Sie daran, dass Sie das Dollarzeichen in doppelten Anführungszeichen escapen müssten – also beispielsweise %1\$s statt %1$s.
  • Die Linkbeschriftung wird mithilfe von htmlspecialchars() escapet – das heißt, eventuell enthaltene HTML-Sonderzeichen werden durch ungefährliche Entity-Referenzen ersetzt; konkret < durch &lt;, > durch &gt;, & durch &amp; und " durch &quot;. Natürlich bedeutet dies, dass die Linkbeschriftung hier kein verschachteltes HTML enthalten könnte. Wann immer Benutzereingaben oder andere dynamische Inhalte in einem HTML-Kontext ausgegeben werden, sollten Sie diese jedoch in jedem Fall escapen. Alternativ können Sie auch mithilfe von strip_tags($string) die enthaltenen HTML-Tags entfernen. Die Funktion nimmt einen optionalen zweiten Parameter an, der die erlaubten HTML-Tags enthält – als String in einem Format wie beispielsweise "<a><b><i>".

Als Attribute einer Klasse gelten automatisch alle Variablen, die in der Klasse, aber nicht innerhalb einer Methode definiert werden. Die Deklaration erfolgt entweder mit einer der Veröffentlichungsstufen public, protected oder private oder mit dem Schlüsselwort var (veraltet). Aus einer Methode heraus wird das Schlüsselwort $this verwendet, um auf Attribute zuzugreifen. Im zuvor gezeigten Beispiel sind $href und $caption die beiden privaten Attribute. Das folgende Beispiel zeigt den Umgang mit einem Attribut genauer:

class Test {
private $value = 0;

public function setValue($value) {
$this->value = $value;
}

public function getValue() {
return $this->value;
}
}

$object = new Test();
$obj->setValue(2);
echo $obj->getValue();

Zunächst wird das private Attribut $value deklariert und mit dem Wert 0 initialisiert. Die öffentliche Methode setValue() ändert sie auf einen von außen angegebenen Wert. getValue() gibt den Inhalt von $value dagegen zurück. Achten Sie darauf, dass Sie den Variablennamen hinter $this und dem Zugriffsoperator -> nicht mit einem weiteren Dollarzeichen versehen.

Falls Sie einen expliziten Konstruktor definieren möchten: Es handelt sich um eine Funktion mit dem speziellen Namen __construct() – mit zwei Unterstrichen. Innerhalb des Konstruktors werden typischerweise Initialisierungsarbeiten vorgenommen, die zu Beginn des Lebenszyklus eines Objekts erforderlich sind. Insbesondere können die übergebenen Parameter als Anfangswerte für die Attribute des Objekts gesetzt werden.

Das folgende Beispiel definiert drei Klassen namens Table, Row und Cell; sie definieren HTML-Tabellen sowie deren Zeilen und Zellen. Zwei der drei Klassen besitzen explizite Konstruktoren:

<?php

class Table {
private static $allowedAttributes = array(
'title', 'style', 'border', 'cellpadding', 'cellspacing'
);

private $attributes = array();

private $rows = array();

public function __construct($attributes = array()) {
if (!empty($attributes)) {
foreach ($attributes as $name => $value) {
$this->addAttribute($name, $value);
}
}
}

public function addAttribute($name, $value) {
if (in_array($name, self::$allowedAttributes)) {
$this->attributes[$name] = $value;
}
}

public function addRow(Row $row) {
$this->rows[] = $row;
}

public function __toString() {
$result = '<table';
foreach ($this->attributes as $name => $value) {
$result .= sprintf(
' %s="%s"',
$name,
htmlspecialchars($value)
);
}
$result .= ">\n";
foreach ($this->rows as $row) {
$result .= $row->__toString()."\n";
}
$result .= "</table>\n";
return $result;
}
}

class Row {
private $cells = array();

public function addCell(Cell $cell) {
$this->cells[] = $cell;
}

public function __toString() {
$result = "<tr>\n";
foreach ($this->cells as $cell) {
$result .= $cell->__toString()."\n";
}
$result .= "</tr>";
return $result;
}
}

class Cell {
private static $allowedAttributes = array(
'title', 'style', 'align', 'valign'
);

private $type = 'td';

private $attributes = array();

private $contents = '';

public function __construct($contents, $type = 'td',
$attributes = array()) {
$this->contents = $contents;
if ($type == 'th') {
$this->type = 'th';
}

if (!empty($attributes)) {
foreach ($attributes as $name => $value) {
$this->addAttribute($name, $value);
}
}
}

public function addAttribute($name, $value) {
if (in_array($name, self::$allowedAttributes)) {
$this->attributes[$name] = $value;
}
}

public function __toString() {
$result = '<'.$this->type;
foreach ($this->attributes as $name => $value) {
$result .= sprintf(
' %s="%s"',
$name,
htmlspecialchars($value)
);
}
$result .= '>';
$result .= $this->contents;
$result .= sprintf("</%s>", $this->type);
return $result;
}
}

$table = new Table(array('border' => 2, 'cellpadding' => 4));
$row = new Row();
$row->addCell(new Cell('Zeile 1, Zelle 1'));
$row->addCell(new Cell('Zeile 1, Zelle 1'));
$table->addRow($row);
$row = new Row();
$row->addCell(new Cell('Zeile 2, Zelle 1'));
$row->addCell(new Cell('Zeile 2, Zelle 1'));
$table->addRow($row);
echo $table;

Zum Testen können Sie alle Klassen und den globalen Code in eine einzelne Datei schreiben. In realen Projekten empfiehlt es sich dagegen, jede Klasse in einer eigenen Datei zu speichern, die den Klassennamen trägt. In diesem Fall müssen die jeweils verwendeten Klassen mithilfe von Include-Anweisungen oder per Autoloader geladen werden (Näheres dazu später).

Wie Sie sehen, beginnt das Beispiel mit der Klasse Table. Diese besitzt die Instanzattribute $attributes für die Attribute des <table>-Tags und $rows für die in der Tabelle enthaltenen Zeilen, also <tr>-Elemente.

Zusätzlich ist das statische Attribut $allowedAttributes enthalten, das die Liste der zulässigen Attribute enthält. Statische Attribute oder Klassenvariablen werden durch das Schlüsselwort static gekennzeichnet, und sie gehören nicht zu einzelnen Instanzen, sondern besitzen für die gesamte Klasse denselben Wert. Der Zugriff erfolgt innerhalb der Klasse mithilfe von self::$attribut und von außen – falls das Attribut public ist – mit Klasse::$attribut. Statische Methoden können übrigens auf dieselbe Weise definiert und angesprochen werden.

Der Konstruktor von Table nimmt optional ein assoziatives Array mit Attributen entgegen. Diese werden – falls vorhanden – mithilfe der öffentlichen Methode addAttribute() hinzugefügt. Diese überprüft zunächst anhand von self::$allowedAttributes, ob ein Attribut mit dem angegebenen Namen zulässig ist, und fügt es in diesem Fall zur Liste der Attribute hinzu.

Die Methode addRow() wird verwendet, um eine Tabellenzeile hinzuzufügen. Interessant ist hier die Angabe des Klassennamens Row vor dem eigentlichen Parameter. Es handelt sich um einen sogenannten Type Hint; wenn ein Argument übergeben wird, das keine Instanz von Row ist, führt dies zu einem Fatal Error. Type Hints sind seit PHP 5.1 verfügbar, und zwar für Klassen und Arrays (Schlüsselwort array). Wird einem solchen Parameter der Standardwert NULL zugewiesen, ist dieser Wert zusätzlich erlaubt. In Version 5.4 wurden auch Type Hints für einfache Datentypen wie Integer, String und so weiter eingeführt.

Auch hier wird die Methode __toString() verwendet, um die fertige Tabelle als String zurückzugeben. Bei der Ausgabe der Attribute wird nur der Wert escapet, der Name dagegen nicht, weil dieser bereits durch addAttribute() gefiltert wurde. Zum Hinzufügen der Zeilen wird wiederum deren __toString()-Methode aufgerufen.

Die Klasse Row ist wesentlich weniger umfangreich als Table, weil hier beispielsweise keine Attribute vorgehalten werden. Instanzen der Klasse enthalten ein Array von Zellen, die mithilfe von addCell() hinzugefügt werden können, und __toString() erledigt die Ausgabe unter Zuhilfenahme der gleichnamigen Methode in Cell.

Cell schließlich besitzt wieder Attribute, einen String mit dem Zellinhalt und schließlich das Attribut $type, das den Zelltyp (<td> oder <tr>) kapselt. Der Konstruktor nimmt den Inhalt, den Typ und ein Array mit Attributen entgegen. Die Parameter $type und $attributes sind optional und können von rechts an weggelassen werden. Beachten Sie, dass Zellen in dieser Implementierung keinen verschachtelten HTML-Code enthalten können, da der Inhalt mithilfe von htmlspecialchars() escapet wird.

Zum Schluss wird eine Instanz der Klasse Table erzeugt, und es werden Zeilen und Zellen hinzugefügt. Die Ausgabe erfolgt durch ein einfaches echo $table, da __toString() in diesem Fall wieder automatisch aufgerufen wird. Die HTML-Ausgabe des Beispiels sieht folgendermaßen aus:

<table border="2" cellpadding="4">
<tr>
<td>Zeile 1, Zelle 1</td>
<td>Zeile 1, Zelle 1</td>
</tr>
<tr>
<td>Zeile 2, Zelle 1</td>
<td>Zeile 2, Zelle 1</td>
</tr>
</table>

Vererbung

Die Vererbung funktioniert in PHP, ähnlich wie in Java, mithilfe des Schlüsselworts extends. Das folgende Beispiel leitet die Klassen Table, Row und Cell von der neuen Klasse HtmlTag ab, die die gemeinsame Funktionalität wie beispielsweise die Verwaltung der Attribute bereitstellt:

<?php

class HtmlTag {
protected static $allowedAttributes = array('title');

protected $tagName = '';

protected $attributes = array();

protected $content = '';

public function __construct($tagName, $attributes = array(),
$content = '') {
$this->tagName = $tagName;
if (!empty($attributes)) {
foreach ($attributes as $name => $value) {
$this->addAttribute($name, $value);
}
}
$this->content = $content;
}

public function addAttribute($name, $value) {
if (in_array($name, self::$allowedAttributes)) {
$this->attributes[$name] = $value;
}
}

public function __toString() {
$result = sprintf('<%s', htmlspecialchars($this->tagName));
foreach ($this->attributes as $name => $value) {
$result .= sprintf(
' %s="%s"',
htmlspecialchars($name),
htmlspecialchars($value)
);
}
if (!empty($this->content)) {
$result .= ">\n";
$result .= $this->content;
$result .= sprintf("</%s>\n", htmlspecialchars($this->tagName));
} else {
$result .= "/>\n";
}
return $result;
}
}

class Table extends HtmlTag {
private $rows = array();

public function __construct($attributes = array()) {
parent::__construct('table', $attributes);
self::$allowedAttributes = array(
'title', 'style', 'border', 'cellpadding', 'cellspacing'
);
}

public function addRow(Row $row) {
$this->rows[] = $row;
}

public function __toString() {
foreach ($this->rows as $row) {
$this->content .= $row->__toString();
}

return parent::__toString();
}
}

class Row extends HtmlTag {
private $cells = array();

public function __construct($attributes = array()) {
parent::__construct('tr', $attributes);
}

public function addCell(Cell $cell) {
$this->cells[] = $cell;
}

public function __toString() {
foreach ($this->cells as $cell) {
$this->content .= $cell->__toString();
}
return parent::__toString();
}
}

class Cell extends HtmlTag {
public function __construct($content, $type = 'td',
$attributes = array()) {
if ($type != 'td' && $type != 'th') {
$type = 'td';
}
parent::__construct($type, $attributes, $content);
self::$allowedAttributes = array(
'title', 'style', 'align', 'valign'
);
}
}

$table = new Table(array('border' => 1, 'cellpadding' => 4));
$table->addAttribute('style', 'background-color: #ff0;');
$row1 = new Row();
$row1->addCell(new Cell('Hello World!'));
$row1->addCell(new Cell('New Table'));
$table->addRow($row1);
$row2 = new Row();
$row2->addCell(new Cell('Top content', 'td', array('valign' => 'top')));

$row2->addCell(new Cell('Normal content'));
$table->addRow($row2);
echo $table;

Die Attribute von HtmlTag sind als protected deklariert, damit der Zugriff von den abgeleiteten Klassen aus möglich bleibt. Das Überschreiben der Inhalte in den statischen Attributen ist übrigens nicht durch erneute Deklaration möglich, sondern muss – wie in den Klassen Table und Cell gezeigt –im Konstruktor erfolgen.

Der Konstruktor der Elternklasse wird nicht automatisch aufgerufen, sondern muss implizit in der Form parent::__construct() aufgerufen werden, falls er benötigt wird. Entsprechend werden Methoden der Elternklasse mit parent::methode() angesprochen.

Die HTML-Ausgabe des Beispiels sieht so aus:

<table style="background-color: #ff0;">
<tr>
<td>
Hello World!</td>
<td>
New Table</td>
</tr>
<tr>
<td valign="top">
Top content</td>
<td>
Normal content</td>
</tr>
</table>

Magische Methoden

Zuvor wurde bereits die magische Methode __toString() gezeigt, die automatisch aufgerufen wird, wenn Sie eine Instanz im String-Kontext verwenden. PHP kennt noch andere magische Methoden. Hier die wichtigsten im Überblick:

  • __get($name) wird aufgerufen, wenn ein Lesezugriff auf ein nicht vorhandenes öffentliches Attribut einer Instanz erfolgt, also $instanz->attribut. Innerhalb der Methode können Sie anhand des in $name stehenden Attributnamens entscheiden, was Sie zurückgeben möchten – sogar die nachträgliche Initialisierung von Attributen ist auf diese Weise möglich.
  • __set($name, $value) kommt entsprechend beim Schreibzugriff auf ein nicht vorhandenes Attribut zum Tragen, das heißt bei einer Wertzuweisung der Form $instanz ->attribut = $value. Dabei steht der Attributname in $name und der Wert in $value.
  • __isset($name) wird aufgerufen, wenn Sie isset() auf ein nicht vorhandenes Attribut aufrufen, also isset($instanz->attribut).
  • __unset($name) schließlich kommt bei einem unset()-Aufruf auf ein nicht definiertes Attribut zum Einsatz, das heißt bei unset($instanz->attribut).
  • __call($name, $arguments) wird beim Zugriff auf eine nicht definierte Methode einer Instanz aufgerufen. Der Methodenname steht dabei in $name, während sämtliche Argumente im Array $arguments gesammelt werden.

Rheinwerk Computing - Zum Seitenanfang

18.1.3 Include-Dateien, Autoloader und NamespacesZur nächsten ÜberschriftZur vorigen Überschrift

Damit die Objektorientierung ihren Hauptnutzen entfalten kann, nämlich den der einfachen Wiederverwendbarkeit von Code, sollten Sie die Klassendefinitionen in externe Dateien schreiben, die Sie zur Laufzeit importieren können. Eine externe Datei wird mithilfe der Anweisungen require() oder include() importiert. Der Unterschied besteht darin, dass require() für unkonditionale Includes am Dateibeginn eingesetzt wird, include() dagegen für konditionale innerhalb von Funktionen oder Methoden.

Bei größeren PHP-Projekten empfiehlt es sich, include_once() beziehungsweise require_once() statt des einfachen include() oder require() zu verwenden – diese Variante sorgt dafür, dass jede benötigte Datei nur einmal inkludiert wird, was Speicher und Rechenzeit spart.

Der Pfad wird durch die zuerst vom Interpreter aufgerufene Datei festgelegt; zusätzlich werden die Verzeichnisse des in der Konfigurationsdatei php.ini festgelegten include_path durchsucht. Es ist daher keineswegs sichergestellt, dass der Import ohne Pfadangabe funktioniert, nur weil sich zwei Dateien im selben Verzeichnis befinden. Deshalb sollten Sie dem Pfad in einem solchen Fall die Pseudokonstante __DIR__ voranstellen, die für das Verzeichnis der aktuellen Datei steht.[Anm.: __DIR__ wurde erst in der PHP-Version 5.3 neu eingeführt; in älteren Versionen können Sie stattdessen dirname(__FILE__) schreiben – die Funktion dirname() liefert den Verzeichnisteil eines Pfads, und __FILE__ ist der absolute Pfad der aktuellen Datei. Beachten Sie, dass __DIR__ und __FILE__ vorn und hinten mit je zwei Unterstrichen geschrieben werden.]

Das folgende Beispiel importiert die Datei Table.php, die sich im selben Verzeichnis befindet wie die aktuelle Datei:

require_once(__DIR__.'/Table.php');

Wenngleich es in PHP keine Pflicht ist wie in Java, ist es auch hier empfehlenswert, Klassen- und Dateinamen übereinstimmen zu lassen. Üblicherweise wird ein mehrgliedriger CamelCase-Klassenname dabei in entsprechenden Unterverzeichnissen gespeichert. Angenommen, die Klasse Table hieße OutputHtmlTable. Dann wäre es eine gute Idee, sie innerhalb des Projektverzeichnisses als Output/Html/Table.php zu speichern.

Wenn Sie solche Namenskonventionen einhalten, können Sie einen Autoloader schreiben, der die benötigten Klassen automatisch lädt, sobald sie gebraucht werden. Es handelt sich dabei um eine magische Funktion[Anm.: Im Unterschied zu __get() und dergleichen handelt es sich nicht um eine magische Methode, weil __autoload() nicht innerhalb einer Klasse deklariert wird.] namens __autoload(); die Datei, in der sie steht, muss ihrerseits natürlich durch ein manuelles Include geladen werden. Für die hier behandelte Namenskonvention könnte die Funktion so aussehen, wenn sie im obersten Verzeichnis der Webanwendung steht, das heißt noch über dem Verzeichnis Output:

function __autoload($className) {
$classPath = preg_replace('(([A-Z]))', '/$1', $className);
var_dump($classPath);
require_once(__DIR__.$classPath.'.php');
}

Hier werden alle Großbuchstaben mithilfe von preg_replace() durch Slashes gefolgt von dem jeweiligen Großbuchstaben ersetzt.

Seit PHP 5.3 sind Namespaces verfügbar. Ein Namespace oder Namensraum dient der logischen Unterteilung von Klassenbezeichnern; das Konzept ähnelt beispielsweise den Packages in Java. Mit Namespaces könnten Sie die Klasse Output\Html\Table nennen, das heißt, sie läge im Unter-Namespace Html des Namespaces Output. Dazu muss der Namespace als erste Anweisung im Skript deklariert werden:

<?php
namespace Output\Html;

class Table {
// ...
}

Der Autoloader müsste dann wie folgt angepasst werden:

function __autoload($className) {
$classPath = str_replace('\\', '/', $className);
require_once(__DIR__.$classPath.'.php');
}

Hier genügt das einfache str_replace() statt preg_replace(), weil lediglich die Backslashes durch Slashes ersetzt werden.


Rheinwerk Computing - Zum Seitenanfang

18.1.4 Webspezifische FunktionenZur nächsten ÜberschriftZur vorigen Überschrift

Die Hauptaufgabe von PHP ist das Erstellen dynamischer Webanwendungen. Zu diesem Zweck wird eine Reihe spezieller Funktionen und Fähigkeiten angeboten. Dazu gehören das Auslesen von Formulardaten, das Erzeugen und Lesen von Cookies oder auch das Session-Tracking. Letzteres steuert eine Funktionalität bei, die HTTP von Haus aus nicht besitzt: Es ermöglicht das Verfolgen der Aktivitäten eines Benutzers über mehrere besuchte Seiten hinweg. Dies ist wichtig für Warenkorbsysteme oder andere Anwendungen, bei denen eine Reihe aufeinanderfolgender Aktivitäten registriert werden muss.

Formulardaten auslesen

HTML-Formulare wurden im vorigen Kapitel vorgestellt. In PHP stehen empfangene Formulardaten je nach Methode in einem der beiden Arrays $_GET oder $_POST zur Verfügung. Ein name=wert-Paar aus dem Formular wird dabei als $_GET['name'] beziehungsweise $_POST['name'] angesprochen.

Betrachten Sie als Beispiel das folgende kurze Formular:

<form action="auswert.php" method="get">
Name: <input type="text" name="user" /><br />
E-Mail: <input type="text" name="mail" /><br />
<input type="submit" value="Abschicken" />
</form>

Klickt ein Benutzer den Button Abschicken an, wird das PHP-Skript auswert.php aufgerufen. Da die HTTP-Methode GET ausgewählt wurde, befinden sich die Formulardaten in dem Array $_GET. Der folgende Codeblock liest sie und gibt sie anschließend aus:

$user = $_GET['user'];
$mail = $_GET['mail'];
echo ("Hallo $user.<br />");
echo ("Best&auml;tigung geht an $mail.");

Sicherer ist es, wenn Sie zuerst überprüfen, ob die gewünschten Formularfelder überhaupt Daten enthalten. Prüfen Sie dazu zunächst mit isset(), ob ein erwartetes Feld überhaupt gesetzt ist, und anschließend, ob es einen Wert besitzt. Dazu können Sie beispielsweise folgende praktische Funktion verwenden:

function readParam($field, $default = '') {
// Variable zunächst auf Default-Wert setzen
$var = $default;
if (isset($_POST[$field] && $_POST[$field] != '') {
$var = $_POST[$field];
} elseif (isset($_GET[$field] && $_GET[$field] != '') {
$var = $_GET[$field];
}
// Ermittelten Wert zurückgeben
return $var;
}

Die Funktion erwartet als Parameter den Namen des auszulesenden Feldes sowie optional einen Default-Wert, der geliefert wird, falls das Feld nicht verfügbar oder leer ist. Die beiden Felder aus dem zuvor gezeigten Formularbeispiel lassen sich mithilfe dieser Funktion folgendermaßen lesen:

// User auslesen, Standardwert "Anonymous"
$user = readParam("user", "Anonymous");
// E-Mail auslesen, Standardwert "" (automatisch)
tipp_li$mail = readParam ("mail");

Schreiben Sie diese Funktion einfach in einen PHP-Block am Beginn jeder Datei, die Formulardaten auslesen muss, oder speichern Sie sie in einer Include-Datei. Bei einer modernen, objektorientierten Anwendung können Sie sie als Methode in die Basisklasse Ihres Frameworks einbauen.

Datei-Uploads

Bereits im vorigen Kapitel haben Sie das spezielle HTML-Formularfeld kennengelernt, das Benutzern den Versand einer lokalen Datei mit dem Formular ermöglicht. Es handelt sich um ein <input>-Element mit dem Attribut type="file". Damit der Upload funktionieren kann, muss das Formular mit der HTTP-Methode POST und der speziellen MIME-Codierung multipart/form-data versandt werden. PHP verarbeitet optional ein zusätzliches Formularfeld mit der Bezeichnung MAX_FILE_SIZE, das die maximal zulässige Dateigröße in Byte enthält und meist als Hidden-Formularfeld gesetzt wird. Sollte der angegebene Wert größer sein als die php.ini-Einstellung upload_max_filesize (siehe Kapitel 13, »Server für Webanwendungen«), wird er ignoriert.

Das folgende Beispiel definiert ein Formular für den Versand einer GIF-Bilddatei (dies wird erst auf dem Server geklärt!) von maximal 400 Kilobyte Größe:

<form action="bildupload.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="409600" />
Bitte eine GIF-Bilddatei w&auml;hlen:<br />
<input type="file" name="bild" />
<br />
<input type="submit" value="Abschicken" />
</form>

Die Datei lässt sich in PHP über das globale Array $_FILES auslesen; der Index ist der Feldname. Das entsprechende Array-Element ist wiederum ein Array mit folgenden Elementen:

  • tmp_name: der temporäre Dateiname, unter dem der Server die hochgeladene Datei gespeichert hat
  • type: der MIME-Type der Datei (aus der Dateiendung oder aus dem Content-Type-Header des Formularabschnitts ermittelt)
  • size: Größe der Datei oder 0 bei einem Fehler
  • name: ursprünglicher Pfad und Dateiname der Datei auf dem Clientrechner
  • error: Fehlercode bei einem Upload-Fehler. Die symbolischen Konstanten entsprechen dabei den in Klammern angegebenen numerischen Werten:
    • UPLOAD_ERR_OK (0): Es ist kein Fehler aufgetreten.
    • UPLOAD_ERR_INI_SIZE (1): Datei ist größer als die in php.ini angegebene Höchstgrenze upload_max_filesize.
    • UPLOAD_ERR_FORM_SIZE (2): Datei ist größer als die mit dem Formular übertragene Höchstgrenze MAX_FILE_SIZE.
    • UPLOAD_ERR_PARTIAL (3): Die Datei wurde nur zum Teil hochgeladen.
    • UPLOAD_ERR_NO_FILE (4): Es wurde gar keine Datei hochgeladen.

Das folgende PHP-Fragment kopiert die hochgeladene Datei unter dem Namen bild.gif in das Verzeichnis des Skripts selbst, falls es sich um eine Bilddatei vom Typ GIF handelt:

if (is_uploaded_file($_FILES['bild']['tmp_name'])) {
if ($_FILES['bild']['type'] == 'image/gif') {
move_uploaded_file (
$_FILES['bild']['tmp_name'],
__DIR__.'/bild.gif'
);
} else {
echo "Falscher Dateityp!<br />";
}
} else {
echo "Upload-Fehler (Datei zu gro&szlig;)?<br />";
}

Sessions

Eines der interessantesten Webfeatures von PHP ist das Session-Tracking. HTTP ist ein zustandsloses Protokoll, das heißt, jede Clientanfrage steht für sich allein. Für größere Webanwendungen, die sich über viele Seiten erstrecken, wird deshalb ein Verfahren zur Datenweitergabe an nachfolgende Anfragen benötigt. PHP stellt sehr praktische Funktionen zur Verwaltung von Session-Daten bereit.

Beachten Sie, dass Sie Session-Befehle in einen PHP-Block zu Beginn Ihres Skripts setzen müssen: Session-Daten werden als Cookies übertragen, wenn der Client dies zulässt, ansonsten über eine Session-ID im Query-String. Beides erfordert, dass das eigentliche geparste Dokument noch nicht begonnen hat, sodass mit dem ersten Zeichen Ihres Skripts die Sequenz <?php einsetzen muss.

Jedes Skript, das auf Session-Daten zugreifen soll, muss zunächst folgende Anweisung enthalten:

session_start();

Anschließend können Sie das spezielle Array $_SESSION mit Name-Wert-Paaren bestücken oder Werte von einer zuvor besuchten Seite daraus lesen. Aus dem soeben erwähnten Grund muss das Lesen vor dem Schreibzugriff erfolgen:

// Stückzahl auslesen
$stueckzahl = $_SESSION['stueck'];
// GET-Formularfeld zahlungsart auslesen
$zahlungsart = $_GET['zahlungsart'];
// Zur zukünftigen Verwendung in Session speichern
$_SESSION['zahlungsart'] = $zahlungsart;

Cookies

Für die meisten Anwendungen ist die Verwendung von Session-Daten derjenigen von Cookies vorziehen. Der einzige Nachteil von Sessions besteht darin, dass die Daten beim nächsten Besuch eines Benutzers nicht mehr zur Verfügung stehen. Dies lässt sich oft durch eine persönliche Anmeldung und die Speicherung der Daten in einer Datenbank beheben.

Dennoch gibt es Fälle, in denen Cookies praktischer sind. Es ist für einen Besucher unter Umständen nützlich, Einstellungen, die er auf einer Seite vorgenommen hat, beim nächsten Besuch wieder vorzufinden. Sie sollten allerdings niemals eine Webanwendung programmieren, die von Cookies abhängt: Aufgrund des Missbrauchs, der häufig zu Werbe- und Usertracking-Zwecken mit Cookies getrieben wird, schalten viele Benutzer Cookies generell ab.

In PHP wird ein Cookie mithilfe der folgenden Funktion gesetzt:

setcookie($name, $wert[, $verfallsdatum[, $pfad
[, $domain[, $secure[, $httponly]]]]])

Hier eine Übersicht über die Parameter:

  • $name: der Name des Cookies, über den es später wieder abgefragt werden kann
  • $wert: der Wert, den Sie für den entsprechenden Namen festlegen möchten
  • $verfallsdatum: der Verfallszeitpunkt für das Cookie in Sekunden seit EPOCH. Sie können einfach time() + $sekundenzahl verwenden, da die Funktion time() den aktuellen Zeitpunkt in diesem Format liefert. Lassen Sie diesen Wert weg, so erzeugen Sie automatisch ein Session-Cookie, das nur während der aktuellen Clientsitzung gilt.
  • $pfad: der URL-Pfad, unter dem das Cookie zur Verfügung steht. Wenn Sie den Wert nicht angeben, ist das Cookie nur in dem Verzeichnis verfügbar, in dem Sie es gesetzt haben (das heißt das Verzeichnis der ersten PHP-Datei, die durch die HTTP-Anfrage aufgerufen wurde).
  • $domain: die Domain, unter der das Cookie gültig ist. Standardwert ist der vollständige Hostname, von dem das Cookie gesetzt wurde. Haben Sie aber beispielsweise die Subdomains www.example.com und cgi.example.com, sollten Sie den Wert .example.com setzen, damit das Cookie für beide Domains gilt.
  • $secure: Wenn Sie diesen Wert auf TRUE setzen, wird das Cookie nur über eine gesicherte HTTPS-Verbindung übertragen.
  • $httponly (seit PHP 5.2.0): Der Wert TRUE sorgt dafür, dass das Cookie nur über eine HTTP(S)-Verbindung ausgelesen und verändert werden kann und beispielsweise nicht per JavaScript. Dies erschwert Cross-Site-Scripting-Attacken (XSS).

Wie bereits erwähnt, werden Cookies als HTTP-Header gesetzt. Deshalb muss dieser Befehl – genau wie die Session-Anweisungen – vor jeglichem HTML-Code in der PHP-Datei stehen. Die folgende Anweisung setzt ein sieben Tage gültiges Cookie namens lastvisit, dessen Wert die aktuelle Uhrzeit ist:

setcookie("lastvisit", time(), time() + 7 * 24 * 60 * 60);

Die Cookies, die der Client bei der Anfrage mitgeschickt hat, können Sie aus dem globalen Array $_COOKIE lesen. Dieses Beispiel liest das Cookie lastvisit und speichert seinen Wert in einer gleichnamigen Variablen:

$lastvisit = $_COOKIE['lastvisit'];

Diese Variable lässt sich im weiteren Verlauf des Skripts etwa so verwenden:

$lastvisitformat = date("d.m.Y, H:i", $lastvisit);
echo "Ihr letzter Besuch: $lastvisitformat <br />";

Die Funktion date($format, $timestamp) erzeugt formatierte Datumsangaben. Die Zeitangabe ist ein Unix-Timestamp, das heißt ein Integer, der die Sekunden seit EPOCH (01.01.1970, 00:00 Uhr UTC) zählt. Im Formatstring können unter anderem folgende Komponenten vorkommen:

  • j ist der Tag im Monat.
  • d steht ebenfalls für den Tag, gibt ihn allerdings zweistellig an (aus 9 wird etwa 09).
  • w gibt den Wochentag als Zahlenwert an, wobei 0 für Sonntag steht, 1 für Montag, bis 6 für Samstag.
  • n ist der numerisch angegebene Monat.
  • m ist noch einmal die Nummer des Monats, aber zweistellig.
  • y gibt das zweistellige Jahr an.
  • Y bedeutet ebenfalls das Jahr, allerdings vierstellig.
  • G ist die Stunde im 24-Stunden-Format.
  • H ist ebenfalls die Stunde im 24-Stunden-Format, jedoch mit erzwungener Zweistelligkeit.
  • i gibt die Minuten zweistellig an.
  • s steht für die zweistelligen Sekunden.

Rheinwerk Computing - Zum Seitenanfang

18.1.5 Zugriff auf MySQL-DatenbankenZur nächsten ÜberschriftZur vorigen Überschrift

Es gibt drei verschiedene PHP-Schnittstellen für den Zugriff auf MySQL-Datenbanken: die klassische mysql-Schnittstelle, die neuere Entwicklung mysqli und die Abstraktionsschicht PHP Data Objects (PDO). mysqli und PDO existieren nur in PHP 5 und arbeiten nur mit MySQL ab Version 4.1 zusammen. Diese Einführung beschränkt sich auf die Gegenüberstellung von mysql und mysqli.

Eine Testdatenbank

Die folgenden einfachen MySQL-Beispiele beziehen sich alle auf eine kleine Datenbank namens programmiersprachen, in der eine einzige Tabelle mit dem Namen sprachen enthalten ist. Die Felder dieser Datenbanktabelle sehen Sie in Tabelle 18.1. Damit die Beispiele nicht allzu umfangreich werden, ist die Tabelle sehr einfach gehalten.

Tabelle 18.1 Struktur der Beispieldatenbanktabelle »sprachen«

Feldbezeichnung Datentyp

id

int, auto_increment, Primärschlüssel

name

varchar(30)

architektur

enum('imperativ', 'oop', 'sonstige')

implementierung

enum('Compiler', 'Interpreter', 'VM', 'Mischform')

systeme

set('Unix', 'Windows', 'sonstige')

kurzinfo

varchar(255)

jahr

year

Sie können diese Tabelle mit phpMyAdmin oder einem anderen grafischen Datenbankverwaltungstool anlegen. Alternativ öffnen Sie die mysql-Konsole (siehe Kapitel 12, »Datenbanken«) und tippen nacheinander folgende SQL-Befehle ein (die Ausgabe des Clients wurde hier weggelassen):

mysql> create database programmiersprachen;
mysql> use programmiersprachen
mysql> create table sprachen (
-> id int auto_increment,
-> name varchar(30),
-> architektur enum('imperativ', 'oop',
-> 'sonstige'),
-> implementierung enum('Compiler', 'Interpreter',
-> 'VM', 'Mischform'),
-> systeme set('Unix', 'Windows', 'sonstige'),
-> kurzinfo varchar(255),
-> jahr year,
-> primary key(id),
-> index(name)
-> );

create database und create table sind SQL-Standardbefehle zum Erzeugen einer neuen Datenbank beziehungsweise Tabelle; use <Datenbank> ist dagegen ein interner Befehl des MySQL-Clients, der zur Auswahl der gewünschten Datenbank verwendet wird.

Als Nächstes benötigt die Datenbank einige Beispielinhalte. Sie können entweder die Werte aus Tabelle 18.2 übernehmen oder Ihre eigenen benutzen.

Tabelle 18.2 Beispieldatensätze für die Datenbanktabelle »sprachen«

Name Arch. Impl. Systeme Kurzinfo Jahr

C

imperativ

Compiler

Unix,
Windows

älteste weitverbreitete Sprache; Syntax in vielen Sprachen verbreitet

1970

C++

oop

Compiler

Unix,
Windows

Weiterentwicklung von C mit OOP-Fähigkeiten

1983

Java

oop

VM

Unix,
Windows

OOP-Sprache mit Multi-Plattform-VM

1995

C#

oop

Mischform

Windows

Sprache für die CLR des .NET Frameworks

2000

Perl

imperativ

Interpreter

Unix,
Windows

Beliebte Skriptsprache für Admin- und Textbearbeitungsaufgaben; neuere Versionen bieten (umständliche) OOP.

1987

Ruby

oop

Interpreter

Unix,
Windows

Skriptsprache mit fast allen Perl-Features sowie sauberer, moderner OOP-Implementierung

1993

Verwenden Sie am besten einen grafischen Client, um die Beispielwerte einzugeben. Manuelle INSERT-Abfragen gemäß der Anleitung in Kapitel 12, »Datenbanken«, sind ebenfalls möglich, aber recht aufwendig und fehlerträchtig. Alternativ können Sie auch die Datei mksprachen.sql aus der Listing-Sammlung zu diesem Buch (http://buecher.lingoworld.de/fachinfo/listings/) ausführen. Dies funktioniert im MySQL-Kommandozeilen-Client beispielsweise folgendermaßen:

mysql> source mksprachen.sql

Die mysql-Schnittstelle

Hier wird zunächst einmal die Vorgehensweise für die klassische Schnittstelle vorgestellt; im weiteren Verlauf des Kapitels lernen Sie dieselben Anweisungen für mysqli kennen.

Als Erstes müssen Sie eine Verbindung zum MySQL-Server herstellen. Dies funktioniert mithilfe der klassischen Schnittstelle schematisch so:

$connID = mysql_connect ($host[, $user[, $passwort]]);

Angenommen, der Datenbankserver läuft auf demselben Host wie der Webserver, der Benutzername ist dbuser und das Passwort »geheim«. Dann wird der Befehl folgendermaßen aufgerufen:

$connID = mysql_connect("localhost", "dbuser", "geheim");

Sie sollten es bei einem Produktivsystem dringend vermeiden, anonyme Zugriffe auf den Datenbankserver zuzulassen. Es sollte also keine Webanwendung geben, bei der ein Zugriff auf MySQL ohne Benutzername und Passwort möglich ist.

Die Verbindungskennung $connID ist übrigens im Erfolgsfall ein positiver Integer, andernfalls erhält die Variable den Wert 0. Deshalb könnte die konkrete Verwendung folgendermaßen aussehen:

$connID = mysql_connect("localhost", "dbuser", "geheim");
if ($connID) {
// Datenbankverarbeitung ...
} else {
echo("Fehler: Kein Datenbankzugrif möglich!<br />");
}

Als Nächstes müssen Sie die Datenbank auswählen, auf die zugegriffen wird. Dies geschieht mit dem Befehl mysql_select_db(). Der erste Parameter ist der Name der gewünschten Datenbank. Optional geben Sie die Verbindungskennung an – dies ist aber nur erforderlich, falls mehrere Datenbankverbindungen bestehen. Beispiel:

mysql_select_db("programmiersprachen");
// Falls mehrere Datenbankverbindungen geöffnet sind:
mysql_select_db("programmiersprachen", $connID);

Datenbankfehler lassen sich jeweils mit den Funktionen mysql_errno() und mysql_ error() überprüfen, die eine Fehlernummer beziehungsweise eine Fehlermeldung zurückliefern. Falls kein Fehler aufgetreten ist, liefert mysql_errno() den Wert 0 zurück und mysql_error() einen leeren String. Auch bei diesen Funktionen können Sie optional die Verbindungs-ID angeben. Idealerweise überprüfen Sie sie nach jeder Datenbankoperation. Beispiel:

mysql_select_db('nicht_vorhandene_datenbank');
if (mysql_errno() > 0) {
$errno = mysql_errno();
$error = mysql_error();
echo "Datenbankfehler $errno: $error<br />";
} else {
// Datenbankoperation durchführen
}

Die tatsächliche Fehlermeldung sollten Sie nur während der Entwicklung ausgeben, weil sie den Enduser nur verwirren würde oder sogar mögliche Schwachstellen für Angriffe offenbaren könnte.

Nun können Sie der Datenbank SQL-Abfragen senden. Dies geschieht mithilfe der Funktion mysql_query($abfrage[, $connID]). Die Verbindungskennung braucht auch hier wieder nur dann angegeben zu werden, falls Sie mehrere Datenbankverbindungen gleichzeitig verwenden. Der Rückgabewert ist eine Ergebniskennung, deren Inhalt Sie mit weiteren Funktionen auslesen können, oder false, wenn die Abfrage fehlschlägt. Im letzteren Fall liefert die Funktion mysql_error() eine Fehlerbeschreibung.

Hier ein Beispiel für eine Auswahlabfrage; sie wählt alle Programmiersprachen aus, deren Name mit »C« beginnt:

$result = mysql_query("SELECT name, architektur,
implementierung,
systeme, kurzinfo,
jahr
FROM sprachen
WHERE name LIKE 'C%'");

Die einzelnen Zeilen des Ergebnisses lassen sich mithilfe von mysql_fetch_row ($result), mysql_fetch_assoc($result) oder mysql_fetch_array($result) auslesen. Trotz der unterschiedlichen Bezeichnungen liefern alle drei ein Array. Der Unterschied ist, dass die Indizes bei mysql_fetch_row() numerisch und bei mysql_fetch_assoc() die eigentlichen Feldnamen sind; bei mysql_fetch_array() wird standardmäßig sogar jeder Datensatz mit beiden Indexarten zurückgeliefert. Sie können diese Funktionen in einer while()-Schleife aufrufen, weil sie false zurückgeben, falls kein weiterer Datensatz mehr vorhanden ist. Hier ein Beispiel für mysql_fetch_row():

echo("<table border=\"2\">");
echo("<tr><th>Sprache</th>");
echo("<th>Architektur</th>");
echo("<th>Implementierung</th>");
echo("<th>Systeme</th>");
echo("<th>Kurzinfo</th>");
echo("<th>Erscheinungsjahr</th></tr>");
while ($record = mysql_fetch_row ($result)) {
$name = $record[0];
$arch = $record[1];
$impl = $record[2];
$syst = $record[3];
$info = $record[4];
$jahr = $record[5];
echo("<tr><td>$name</td>");
echo("<td>$arch</td>");
echo("<td>$impl</td>");
echo("<td>$syst</td>");
echo("<td>$info</td>");
echo("<td>$jahr</td></tr>");
}
echo("</table>");

Eine kürzere Fassung lässt sich mithilfe der Funktion list() erreichen, die die Elemente eines Arrays einer Liste von Einzelvariablen zuweisen kann:

while (list($name, $arch, $impl, $syst, $info, $jahr)
= mysql_fetch_row($result)) {
echo("<tr><td>$name</td>");
echo("<td>$arch</td>");
echo("<td>$impl</td>");
echo("<td>$syst</td>");
echo("<td>$info</td>");
echo("<td>$jahr</td></tr>");
}

Mit mysql_fetch_assoc() sieht der relevante Ausschnitt der while()-Schleife dagegen so aus:

while ($record = mysql_fetch_assoc($result)) {
$name = $record['name'];
$arch = $record['architektur'];
$impl = $record['implementierung'];
$syst = $record['systeme'];
$info = $record['kurzinfo'];
$jahr = $record['jahr'];
// ... wie gehabt ...
}

Bei den unterschiedlichen Arten von Änderungsabfragen (UPDATE, INSERT und DELETE) gibt es natürlich kein Ergebnis in Form zurückgegebener Datensätze. Stattdessen können Sie über die Funktion mysql_affected_rows() erfahren, wie viele Datensätze geändert (beziehungsweise hinzugefügt oder gelöscht) wurden. Beispiel:

// Neue Sprache hinzufügen
$name = "Python";
$arch = "oop";
$impl = "Interpreter";
$syst = "Unix,Windows";
$info = "Objektorientierte Skriptsprache";
$jahr = 1990;
$result = mysql_query
("INSERT INTO sprachen (name, architektur,
implementierung,
systeme, kurzinfo,
jahr)
VALUES (NULL, '$name', '$arch',
'$impl', '$syst', '$info',
'$jahr')");
// Hat es funktioniert?
if (mysql_affected_rows() == 1) {
echo("Sprache erfolgreich hinzugef&uuml;gt.<br />");
} else {
echo("Sprache konnte nicht eingef&uuml;gt werden.
<br />");
}

Wenn Sie eine Abfrage durchführen, die besonders viele Datensätze liefert, sollten Sie anschließend den Speicher leeren, den das Ergebnis belegt hat:

mysql_free_result ($result);

Zum Schluss sollten Sie die Datenbankverbindung ordnungsgemäß schließen:

mysql_close ($connID);
// $connID entfällt, wenn eindeutig

Die mysqli-Schnittstelle

Die neuere Datenbankschnittstelle mysqli kann sowohl prozedural als auch objektorientiert verwendet werden. Wenn Sie die prozedurale Schreibweise verwenden, unterscheiden sich ihre Funktionen nicht sehr stark von der gerade vorgestellten klassischen Schnittstelle mysql. Der objektorientierte Zugriff ist zu bevorzugen. Hier wird für jede Verbindung ein eigenes mysqli-Objekt erzeugt, dessen Methoden und Eigenschaften Sie für die Datenbankoperationen einsetzen können.

Hier sehen Sie das gesamte Beispiel: Auslesen aller mit »C« beginnenden Sprachen und Hinzufügen einer neuen – in objektorientierter mysqli-Syntax. Die ausführlichen Kommentare erläutern die Unterschiede:

// Datenbankverbindung herstellen,
// Standarddatenbank wählen
// (nun in einem Schritt)
$conn = new mysqli ("localhost", "dbuser",
"geheim", "programmiersprachen");
// Abfragen sind Instanzmethoden
// des mysqli-Objekts
$result = $conn->query("SELECT name, architektur,
implementierung,
systeme, kurzinfo,
jahr
FROM sprachen
WHERE name LIKE 'C%'");
echo("<table border=\"2\">");
echo("<tr><th>Sprache</th>");
echo("<th>Architektur</th>");
echo("<th>Implementierung</th>");
echo("<th>Systeme</th>");
echo("<th>Kurzinfo</th>");
echo("<th>Erscheinungsjahr</th></tr>");
// Das Auslesen der Datensätze ist
// eine Instanzmethode des Result-Objekts
while (list($name, $arch, $impl, $syst, $info, $jahr)
= $result->fetch_row()) {
echo("<tr><td>$name</td>");
echo("<td>$arch</td>");
echo("<td>$impl</td>");

echo("<td>$syst</td>");
echo("<td>$info</td>");
echo("<td>$jahr</td></tr>");
}
echo("</table>");
// Neuen Datensatz einfügen
$name = "PHP";
$arch = "oop";
$impl = "Interpreter";
$syst = "Unix,Windows";
$info = "Objektorientierte Web-Skriptsprache";
$jahr = 1995;

$conn->query
("INSERT INTO sprachen (name, architektur,
implementierung,
systeme, kurzinfo,
jahr)
VALUES (NULL, '$name', '$arch',
'$impl', '$syst', '$info',
'$jahr')");
// Hat es funktioniert?
// (affected_rows ist nun eine Eigenschaft
// des mysqli-Objekts)
if ($conn->affected_rows == 1) {
echo ("Sprache erfolgreich hinzugef&uuml;gt.<br />");
} else {
echo ("Sprache konnte nicht eingef&uuml;gt werden.<br />");
}
// Verbindung schließen
$conn->close();

Eine Klasse für vereinfachte Datenbankzugriffe

Bisher wurde gezeigt, wie Sie jede Abfrage manuell durchführen. Für Projekte ab einer gewissen Größe ist dies nicht mehr empfehlenswert. Stattdessen sollten Sie entweder eines der bestehenden PHP-Frameworks wie Zend oder Symfony nutzen oder aber eine eigene Klasse oder Klassenbibliothek für den Datenbankzugriff und andere wiederkehrende Aufgaben schreiben. Die folgende Klasse kapselt eine mysqli-Datenbankverbindung und stellt einige bequeme Methoden zur Verfügung:[Anm.: Die Dokumentationskommentare sind hier in Englisch, da dies in größeren Programmierteams üblicher ist als in Deutsch.]

<?php
/*
* Database access class
*
* @package Database
*/

require_once(__DIR__.'/config.inc.php');

/**
* Database access class
*
* @package Database
*/
class Database {
/**
* mysqli database connection
* @var mysqli
*/
private $con = NULL;

/**
* Set the mysqli database connection object to be used
*
* (Applying the Dependency Injection design pattern)
*
* @param mysqli $con
*/
public function setCon($con) {
$this->con = $con;
}

/**
* Get (and, if necessary, initialize) the mysqli connection object
*
* (Applying the Lazy Initialization design pattern)
*
* @throws RuntimeException
* @return mysqli
*/
public function getCon() {
if (!is_object($this->con)) {
$this->con = new mysqli(HOST, USER, PASSWORD, DATABASE);
if (!is_object($this->con)) {
throw new RuntimeException("No database connection!!!");
}
}
return $this->con;
}

/**
* Create a condition to be applied to a WHERE clause
*
* @param array $filter
* @return string
*/
public function getCondition($filter) {
$result = '';
$con = $this->getCon();
$fieldFirstRun = TRUE;
foreach ($filter as $field => $value) {
if (!$fieldFirstRun) {
$result .= " AND";
} else {
$fieldFirstRun = FALSE;
}
$result .= sprintf(" %s", $con->real_escape_string($field));
if (is_array($value)) {
$result .= " IN (";
$valFirstRun = TRUE;
foreach ($value as $val) {
if (!$valFirstRun) {
$result .= ", ";
} else {
$valFirstRun = FALSE;
}
$result .= sprintf("'%s'", $con->real_escape_string($val));
}
$result .= ")";
} elseif (strstr($value, '%') || strstr($value, '_')) {
$result .= sprintf(" LIKE '%s'", $con->real_escape_string($value));
} else {
$result .= sprintf(" = '%s'", $con->real_escape_string($value));
}
}
return $result;
}

/**
* Perform a database query
*
* @param string $sql sprintf()-style format string
* @param array $params parameters for the format string optional,
* default empty array
* @return mysqli_result
*/
public function query($sql, $params = array()) {
$con = $this->getCon();
if (!is_array($params)) {
$params = array($params);
}
foreach ($params as $key => $param) {
$params[$key] = $con->real_escape_string($param);
}
return $con->query(vsprintf($sql, $params));
}

/**
* Perform an update query
*
* @param string $table
* @param array $values associative array (field => value)
* @param string $condition sprintf()-style pattern
* (to be appended to WHERE... if not empty)
* @param array $params arguments for the sprintf()-style pattern
* @throws InvalidArgumentException
* @return integer number of affected rows
*/
public function updateQuery($table, $values, $condition = '',
$params = array()) {
if (empty($values) || !is_array($values)) {
throw new InvalidArgumentException(
"At least one value for update needed!"
);
}
$con = $this->getCon();
$sql = sprintf("UPDATE %s SET ", $con->real_escape_string($table));
$firstRun = TRUE;
foreach ($values as $field => $value) {
if ($firstRun) {
$firstRun = FALSE;
} else {
$sql .= ", ";
}
$sql .= sprintf(
"%s = '%s'",
$con->real_escape_string($field),
$con->real_escape_string($value)
);
}
if (trim($condition) != '') {
if (!is_array($params)) {
$params = array($params);
}
foreach ($params as $key => $param) {
$params[$key] = $con->real_escape_string($param);
}
$sql .= sprintf(" WHERE %s", vsprintf($condition, $params));
}
$con->query($sql);
return $con->affected_rows;
}

/**
* Perform real_escape_string() on connection
*
* @param string $string
* @return string
*/
public function escape($string) {
$con = $this->getCon();
return $con->real_escape_string($string);
}

/**
* Get the number of affected rows for previous query
*
* @return integer
*/
public function getAffectedRows() {
$con = $this->getCon();
return $con->affected_rows;
}

/**
* Get the last inserted auto_increment id
*
* @return integer
*/
public function getLastInsertId() {
$con = $this->getCon();
return $con->insert_id;
}
}

Wie Sie sehen, wird zunächst eine Konfigurationsdatei inkludiert, die die Verbindungsparameter in Form von Konstanten definiert. Diese Datei sieht beispielsweise so aus:

<?php
define('HOST', 'localhost');
define('DATABASE', 'mydatabase');
define('USER', 'dbuser');
define('PASSWORD', 'dbpassword');

Lesen Sie sich den Sourcecode der Klasse in Ruhe durch; in den Kommentaren wird vieles erläutert. Wenn Sie die Klasse in Ihrem Projekt verwenden möchten, stehen Ihnen folgende Methoden zur Verfügung:

  • getCondition(array $filter) erzeugt eine durch AND-Verknüpfungen verbundene Abfolge von Prüfungen, die als WHERE-Bedingung verwendet werden können. In dem Array sind die Schlüssel Tabellenfelder, die auf die angegebenen Werte geprüft werden sollen. Dabei entscheidet die Methode selbst, ob =, LIKE oder IN verwendet wird – je nachdem, ob der Vergleichswert ein einfacher String ist, LIKE-Platzhalter enthält oder ein Array ist.
  • query(string $sql, array $params) führt eine Abfrage durch. Das erste Argument ist ein sprintf()-Platzhalter, der zweite ein optionales Array mit denjenigen Werten, die aus Sicherheitsgründen escapet werden sollten. Hier sind besonders Benutzereingaben betroffen.
  • updateQuery(string $table, array $values, string $condition, array $params) führt eine UPDATE-Abfrage durch. $table ist die betroffene Tabelle, $values enthält die Namen und Werte der zu ändernden Spalten, und $condition und $params beschreiben die optionale Bedingung (diese Parameter funktionieren wie bei der Methode query()).
  • escape($string) ruft die mysqli-Methode real_escape_string() auf den angegebenen String auf, die potenziell gefährliche Zeichen im String escapet, bevor dieser in einer Abfrage verwendet wird.
  • getAffectedRows() liefert die Anzahl der bei der letzten Änderungsabfrage geänderten Datensätze zurück.
  • getLastInsertId() gibt die zuletzt eingefügte Auto-Increment-ID zurück.

Beachten Sie die Art und Weise, wie das mysqli-Objekt von außen gesetzt werden kann und nur dann instanziiert wird, wenn es nicht zuvor gesetzt oder eben instanziiert wurde. Ein solches Getter-Setter-Paar implementiert die beiden Entwurfsmuster Dependency Injection und Lazy Initialization. Sie werden verwendet, um die Implementierung einer Abhängigkeit jederzeit austauschen zu können, vor allem aber für Unit-Tests, um echte abhängige Objekte durch Simulationen zu ersetzen.


Rheinwerk Computing - Zum Seitenanfang

18.1.6 Unit-Tests mit PHPUnitZur nächsten ÜberschriftZur vorigen Überschrift

In Kapitel 11, »Software-Engineering«, wurde bereits auf die Bedeutung und die allgemeinen Grundlagen des Unit-Testings hingewiesen. Für PHP steht dazu insbesondere das Test-Framework PHPUnit von Sebastian Bergmann zur Verfügung.

Installieren Sie PHPUnit zunächst anhand der Anleitung auf der Projektwebsite www.phpunit.de. Danach können Sie Tests schreiben, um das Funktionieren Ihrer Klassen und Methoden sicherzustellen. Unit-Tests werden von der Klasse PHPUnit_Framework_TestCase abgeleitet, die per require_once() eingebunden werden muss. Alle öffentlichen Methoden, deren Name mit test beginnt, werden beim Ausführen des Tests aufgerufen.

Innerhalb von Testmethoden werden Assertions verwendet. Dies sind spezielle Methoden, deren Name mit assert anfängt. Im Allgemeinen steht innerhalb der Assertion als erstes Argument der erwartete Wert, während weitere Argumente einen von der getesteten Methode zurückgegebenen Wert, ein Attribut oder Ähnliches enthalten. Schlägt eine Assertion fehl, gibt PHPUnit für die entsprechende Methode ein »F« (für »failure«) und eine Fehlermeldung aus. Andernfalls wird ein Punkt (.) ausgegeben.

Betrachten Sie als Beispiel noch einmal die Klasse HtmlTag, die im Abschnitt »Vererbung« behandelt wurde. Zur Verwendung von Unit-Tests sollte ein PHP-Projekt mit folgender Verzeichnisstruktur erstellt werden:

+ [Wurzelverzeichnis der Anwendung]
|
+---+ [source]
| |
| +---+ [lib]
| |
| +---+ [Html]
| |
| +---+ Tag.php
|
+---+ [tests]
| |
| +---+ [lib]
| |
| +---+ [Html]
| |
| +---+ TagTest.php
|
+---+ index.php (und weitere Ausgabeseiten)

Wie Sie sehen, befindet sich unter tests dieselbe Verzeichnisstruktur wie unter source. Auf diese Weise ist stets völlig klar, welcher Unit-Test sich auf welche Klasse bezieht. Hier also der Unit-Test für die Klasse HtmlTag:

<?php

require_once('/usr/lib/php/PHPUnit/Framework/TestCase.php');
require_once(__DIR__.'/../../../source/lib/Html/Tag.php');

class HtmlTagTest extends PHPUnit_Framework_TestCase {
/**
* @covers HtmlTag::__construct
*/
public function testConstruct() {
$tag = new HtmlTag('p', array('title' => 'Test'), 'Test text');
$this->assertAttributeEquals('p', 'tagName', $tag);
$this->assertAttributeEquals(
array('title' => 'Test'), 'attributes', $tag);
$this->assertAttributeEquals('Test text', 'content', $tag);
}

/**
* @covers HtmlTag::addAttribute
*/
public function testAddAttribute() {
$tag = new HtmlTag('p');
$tag->addAttribute('title', 'Test Title');
$this->assertAttributeEquals(
array('title' => 'Test Title'), 'attributes', $tag);
}

/**
* @covers HtmlTag::__toString
*/
public function testToString() {
$tag = new HtmlTag('p', array('title' => 'Test'), 'Test text');
$expected = '<p title="Test">
Test text</p>
';
$this->assertEquals($expected, $tag->__toString());
}
}

Den Pfad von PHPUnit_Framework_TestCase müssen Sie auf Ihrem System eventuell anpassen. Danach können Sie den Unit-Test ausführen, indem Sie Folgendes auf der Konsole eingeben:

> phpunit HtmlTest.php

Wenn Sie alles korrekt abgespeichert haben, müssten drei Punkte herauskommen, die besagen, dass alle drei Methoden erfolgreich getestet wurden.

In diesem Beispiel kommen zwei Assertions zur Anwendung: assertEquals($expected, $actual) gilt als erfüllt, wenn der erwartete Wert $expected und der tatsächliche $actual übereinstimmen. assertAttributeEquals($expected, $attribute, $instance) funktioniert im Prinzip genauso, vergleicht jedoch den erwarteten Wert mit dem angegebenen Attribut der Instanz. Durch Reflexion ist hierbei sogar der Zugriff auf private- oder protected-Attribute möglich.

Die @covers-PHPDoc-Annotations über den Testmethoden werden übrigens für den sogenannten Coverage-Report verwendet: Wenn auf dem Entwicklungssystem die Debug-Erweiterung XDebug installiert ist, können Sie sich anzeigen lassen, welche Teile der Klassen von Unit-Tests abgedeckt sind und wie der prozentuale Anteil ist. Das Ziel sollten 100 % sein, aber die sind nicht immer zu erreichen.

Mock-Objekte verwenden

Wenn eine Klasse andere Objekte als Abhängigkeiten enthält, sollten diese Objekte bei einem reinen Unit-Test nicht mitgetestet werden, denn Ziel des Unit-Tests ist das Testen möglichst kleiner, eigenständiger Codeeinheiten. Um diese Abhängigkeiten nicht mittesten zu müssen, werden sie wenn möglich durch eigene Objekte ersetzt, die sich ganz nach Wunsch verhalten – die sogenannten Mock-Objekte. Als Beispiel sehen Sie hier einen Unit-Test für die Datenbank-Abstraktionsklasse Database, der ein Mock-Objekt für die eigentliche Datenbankverbindung verwendet:

<?php

require_once('/usr/lib/php/PHPUnit/Framework/TestCase.php');
require_once(__DIR__.'/../../source/lib/Database.php');

class DatabaseTest extends PHPUnit_Framework_TestCase {
/**
* @covers Database::setCon
*/
public function testSetCon() {
$db = new Database();
$con = $this->getMock('GenericDb');
$db->setCon($con);
$this->assertAttributeSame($con, 'con', $db);
}

/**
* @covers Database::getCon
*/
public function testGetCon() {
$this->markTestSkipped('Real database connection cannot be tested.');
}

/**
* @covers Database::getCondition
*/
public function testGetCondition() {
$db = new Database();
$con = $this->getMock('GenericDb', array('real_escape_string'));
$con
->expects($this->atLeastOnce())
->method('real_escape_string')
->will(
$this->onConsecutiveCalls(
$this->returnValue('field1'),
$this->returnValue(1),
$this->returnValue(2),
$this->returnValue('field2'),
$this->returnValue('test%'),
$this->returnValue('field3'),
$this->returnValue('test')
)
);
$db->setCon($con);
$expected = " field1 IN ('1', '2') AND field2 LIKE 'test%' AND
field3 = 'test'";
$this->assertEquals(
$expected,
$db->getCondition(
array('field1' => array(1, 2), 'field2' => 'test%', 'field3' =>
'test')
)
);
}

/**
* @covers Database::query
*/
public function testQuery() {
$db = new Database();
$con = $this->getMock('GenericDb', array('real_escape_string',
'query'));
$result = $this->getMock('GenericDbResult');
$con
->expects($this->atLeastOnce())
->method('real_escape_string')
->will(
$this->onConsecutiveCalls(
$this->returnValue('table'),
$this->returnValue(1)
)
);
$con
->expects($this->once())
->method('query')
->will($this->returnValue($result));
$db->setCon($con);
$this->assertEquals(
$result,
$db->query('SELECT test FROM %s WHERE field = %d', array('table', 1))
);
}

/**
* @covers Database::updateQuery
*/
public function testUpdateQuery() {
$db = new Database();
$con = $this->getMock('GenericDb', array('real_escape_string', 'query'));
$con
->expects($this->atLeastOnce())
->method('real_escape_string')
->will(
$this->onConsecutiveCalls(
$this->returnValue('table'),
$this->returnValue('name'),
$this->returnValue('SampleName'),
$this->returnValue('value'),
$this->returnValue('SampleValue'),
$this->returnValue(1)
)
);
$con->affected_rows = 1;
$db->setCon($con);
$this->assertEquals(
1,
$db->updateQuery(
'table',
array('name' => 'SampleName', 'value' => 'SampleValue'),
'id = %d',
array(1)
)
);
}

/**
* @covers Database::updateQuery
*/
public function testUpdateQueryExpectingException() {
$db = new Database();
try {
$db->updateQuery('test', array());
$this->fail('Expected InvalidArgumentException not thrown.');
} catch (InvalidArgumentException $e) { }
}

/**
* @covers Database::getAffectedRows
*/
public function testGetAffectedRows() {
$db = new Database();
$con = $this->getMock('GenericDb');
$con->affected_rows = 2;
$db->setCon($con);
$this->assertEquals(2, $db->getAffectedRows());
}

/**
* @covers Database::getLastInsertId
*/
public function testGetLastInsertId() {
$db = new Database();
$con = $this->getMock('GenericDb');
$con->insert_id = 3;
$db->setCon($con);
$this->assertEquals(3, $db->getLastInsertId());
}
}

Mock-Objekte werden mit der PHPUnit-Methode getMock(string $classname, array $methods) erstellt. Zusätzlich kann mit expects() festgelegt werden, was bei welchem Methodenaufruf auf das jeweilige Mock-Objekt herauskommen soll.

Wie gerade dieses etwas längere Beispiel zeigt, sind Unit-Tests unter anderem auch Teil der Dokumentation – denn Sie sehen hier ganz genau, wie die Klasse verwendet wird.



Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück




Copyright © Rheinwerk Verlag GmbH 2013
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern


  Zum Rheinwerk-Shop
Neuauflage: IT-Handbuch für Fachinformatiker






Neuauflage: IT-Handbuch für Fachinformatiker
Jetzt Buch bestellen


 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Rheinwerk-Shop: Java ist auch eine Insel






 Java ist auch
 eine Insel


Zum Rheinwerk-Shop: Linux Handbuch






 Linux Handbuch


Zum Rheinwerk-Shop: Computer Netzwerke






 Computer Netzwerke


Zum Rheinwerk-Shop: Schrödinger lernt HTML5, CSS3 und JavaScript






 Schrödinger lernt
 HTML5, CSS3
 und JavaScript


Zum Rheinwerk-Shop: Windows 8.1 Pro






 Windows 8.1 Pro


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo