Stand: 10.05.2019

13.1 Erstkontakt

Im letzten Kapitel haben wir gelernt, wie wir mit PHP programmieren. PHP wird auf dem Server ausgeführt. Ein Server erhält Anfragen (Requests) von Usern von ihren Laptops, Smartphones etc., die eine Internetseite anfordern. Mit PHP können wir diese Seiten vor der Rücksendung (Response) manipulieren.

Aber wie bekommen wir Daten vom User/Client, die unser Server mit Hilfe von PHP verwenden kann? Eine einfache Möglichkeit sind Formulare, wo der User Informationen eintippt (z.B. Usernamen, Nachricht), die dann an den Server geschickt werden.

13.1.1 Formulare, GET und POST

Ein Formular ist ein HTML-Konstrukt, das es erlaubt, verschiedene Eingabefelder zu definieren. Das HTML-Element heißt form und enthält verschiedene Elemente vom Typ input.

Hier ein Beispiel:

<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="utf-8">
    <title>Formular</title>
  </head>
  <body>

    <h1>Wer sind Sie?</h1>

    <form action="formular.php" method="get">
       Vorname: <input type="text" name="firstname"></br>
       Nachname: <input type="text" name="lastname"></br>
       <input type="submit" value="Absenden">
    </form>

  </body>
</html>

Wichtig ist hier das input-Element:

<input type="submit" value="Absenden">

Dies wird als Button mit der Aufschrift "Absenden" realisiert. Was passiert, wenn man diesen Button drückt? Das steht im Element form:

<form action="formular.php" method="get">

Hier wird gesagt, dass zur Datei "formular.php" gesprungen werden soll, wenn auf den Submit-Button gedrückt wird, und zwar in Form eines HTTP-Requests. Die Methode kann entsprechend "get" oder "post" sein. Dabei ist "get" die Standardeinstellung, könnte hier also weggelassen werden.

Schauen wir uns die Datei "formular.php" an. Ich habe hier auf einen HTML-Rahmen verzichtet, d.h. man sieht im Browser nur den Text ohne Formatierung:

<?php
  echo 'Willkommen, ' . $_GET['firstname'] . ' ' . $_GET['lastname'] . '!';
?>

Wie Sie sehen, wird hier auf die superglobale Variable $_GET zugegriffen (hätten wir als Methode "post" gewählt, müssten wir $_POST verwenden). In der superglobalen Variablen sind die Werte der Inputfelder des Formulars abgelegt. Als Schlüssel verwendet man den String, der in "name" angegeben war:

<form action="formular.php" method="get">
   Vorname: <input type="text" name="firstname"></br>
   Nachname: <input type="text" name="lastname"></br>
   <input type="submit" value="Absenden">
</form>

Der Unterschied zwischen GET und POST ist folgender: Bei einem GET-Request werden die Informationen (hier also Vor- und Nachname) als URL-Parameter verschickt (s.u.), d.h. für jeden sichtbar im Adressfeld des Browsers. Bei einem POST-Request sind die Informationen "versteckt".

Ein GET-Request hat den Vorteil, dass man die URL mit Parametern speichern kann (z.B. als Bookmark), hat aber den Nachteil der Sichtbarkeit (will man nicht immer) und dass Sonderzeichen speziell kodiert werden müssen. komfortabler und sicherer ist dagegen der POST-Request.

URL-Parameter

Wenn Sie eine PHP-Datei über einen Browser aufrufen, können Sie Parameter übergeben, indem Sie die URL in der Adresszeile des Browsers erweitern. Ein solcher Aufruf ist immer ein GET-Request.

Einen Parameter spezifiziert man mit Hilfe von ?

test.php?id=5

Mehrere Parameter kann man mit & anhängen:

test.php?id=5&color=blue

Man nennt den Teil hinter dem ? auch den Query-String der URL.

Stellen wir uns nun vor, wir befinden uns in der anvisierten Seite test.php. In PHP werden diese Parameter über die superglobale Variable $_GET bereit gestellt. Sie beinhaltet einen assoziativen Array, bei dem die Schlüssel die Parameter sind (im Beispiel "id" und "color") und die Werte die übergebenen Werte (im Beispiel "5" und "blue").

<?php
  $id = $_GET['id'];
?>

Man verwendet häufig die Funktion isset, um zu prüfen, ob der Parameter wirklich mitgeliefert wurde und, falls nicht, diesen auf einen Defaultwert zu setzen.

<?php
  $id = $_GET['id'];
  if (!isset($id)) {
    $id = 0;
  }
?>

Parameter mit Sonderzeichen

Offensichtlich ist es ein Problem, wenn die Parameter ein Fragezeichen oder ein Gleichheitszeichen enthalten. Deshalb gibt es eine Tabelle mit numerischen Codes für Sonderzeichen, z.B.

! entspricht %21
# entspricht %23
= entspricht %3D
? entspricht %3F

Zum Enkodieren gibt es die Funktionen urlencode und rawurlencode. Der Unterschied:

Beachten Sie, dass Sie das dekodieren in der Regel nicht machen müssen, da PHP das bereits für Sie getan hat, wenn die Daten in der superglobalen Variablen gelandet sind.

Beispiel mit Begrüßung

Jetzt können wir für das obige Formular eine Seite erstellen, wo der User begrüßt wird. Wir nennen sie "formular.php", d.h. diese Seite wird aufgerufen, sobald wir "Absenden" anklicken.

Auf der Seite holen wir uns die Benutzereingabe aus den zwei Textfeldern mit Hilfe der superglobalen Variablen $_GET:

<?php
  $first = $_GET['firstname'];
  $last = $_GET['lastname'];
?>

Wir möchten die Namensteile zu einer Begrüßung zusammensetzen. Achten Sie auf die doppelten Anführungszeichen, sonst werden die Variablen nicht eingesetzt:

<?php
  $first = $_GET['firstname'];
  $last = $_GET['lastname'];
  echo "Hallo $first $last";
?>

Jetzt betten wir das in HTML ein:

<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="utf-8">
    <title>Formular</title>
  </head>
  <body>

    <h1>
    <?php
      $first = $_GET['firstname'];
      $last = $_GET['lastname'];
      echo "Hallo $first $last";
    ?>
    </h1>

    </body>
  </html>

Wenn Sie Ihren Namen eingeben und auf "Abschicken" klicken, werden Sie individuell begrüßt.

13.1.2 Sicherheitproblem Cross-Site Scripting

URL-Parameter im Query-Teil können natürlich von Benutzern direkt eingegeben und somit manipuliert werden. Hacker können hier ansetzen und die Parameter so formulieren, dass Befehle ausgeführt werden, die von den Erstellern der Webseite nicht vorgesehen waren.

Schauen wir uns einen URL-Parameter id auf einer Seite test.php an. In der URL würde z.B. stehen

test.php?id=5

In test.php wird der Wert einfach irgendwo in HTML eingefügt

<p>User ID: <?php echo $id; ?></p>

Ein Hacker kann jetzt hier JavaScript-Code eingeschmuggeln. Er kann einfach per Hand die URL wie folgt ändern:

test.php?id=<script>alert("Hey!");</script>

Dies Stückchen JavaScript würde einfach ausgeführt werden.

Um das zu verhindern, kann man den String in $id in harmlosen Text umwandeln. Dazu gibt es die PHP-Funktion htmlspecialchars. In dieser Funktion werden z.B. die spitzen Klammern in die HTML-Entitäten &gt; und &lt; umgewandelt. Hier sehen wir die Funktion in Aktion:

<p>User ID: <?php echo htmlspecialchars($id); ?></p>

13.1.3 Beispiel ChatWall

Wir schauen uns jetzt ein Beispiel an, wo ein User tatsächlich Input schickt und dieser gespeichert wird. Es handelt sich um eine "ChatWall", auf die jede/r eine Nachricht schreiben kann. Wir nutzen hier zum Speichern eine einfache Textdatei, da man dies sehr schnell umsetzen kann. Im Realfall wird dann häufig eine Datenbank eingesetzt, um solche Informationen zu speichern.

Startseite

Wir nennen unsere Datei mit der Startseite jetzt "index.php", also mit Endung "php", weil wir später noch PHP-Code hinzufügen:

<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="utf-8">
    <title>Wall</title>
  </head>
  <body>
    <h1>Formular</h1>
    <form action="message.php" method="get">
       User: <input type="text" name="user"></br>
       Nachricht: </br>
       <textarea name="message"></textarea></br>
       <input type="reset" value="Zurücksetzen">
       <input type="submit" value="Absenden">
    </form>
  </body>
</html>

Wir haben hier zwei Eingabefelder für den Namen des Users und die Nachricht. Außerdem gibt es einen weiteren Button "Zurücksetzen", mit dem man bequem die Textfelder löschen kann.

In der form steht als Action "message.php". Dorthin wird also weitergeleitet, sobald auf "Absenden" gedrückt wird.

Sie sollten dies sehen (wir haben hier etwas Styling hinzugefügt):

PHP-Beispiel mit Datei

Daten empfangen und in Datei schreiben

Für den PHP-Code benötigen wir also eine weitere Datei "message.php", wo wir folgendes schreiben:

<?php
  echo $_GET['user'] . ': ' . $_GET['message'];

  $file = fopen("wall.txt", "a+") or die("Konnte Datei nicht öffnen!");
  fwrite($file, $_GET['user'] . ': ' . $_GET['message'] . "\n");
  fclose($file);
?>

Die erste Zeile dient nur der Kontrolle: Wir schauen, ob Name und Nachricht korrekt übergeben wurden.

Sie sehen hier die Variante für einen GET-Request (wurde in dem form-Element der Hauptseite so angegeben). Sollten Sie sich für einen POST-Request entschieden haben, müsste die Zeile so aussehen:

echo $_POST['user'] . ': ' . $_POST['message'];

In den unteren drei Zeilen hängen wir die Nachricht des Users (zusammen mit seinem Namen) an die Datei "wall.txt". Dazu öffnen wir die Datei mit fopen. Das "a+" steht für "append" (engl. für Anhängen). Mit fwrite hängen wir einen String ans Ende der Datei. Wichtig ist, die Datei mit fclose zu schließen, sonst können Inhalte verloren gehen.

Damit das funktioniert, müssen wir die Datei "wall.txt" zuvor anlegen und mit Zugangsrechten "Schreiben für alle" ausstatten. So etwas stellt natürlich ein Sicherheitsrisiko dar und sollte nur zu Testzwecken gemacht werden. Stellen Sie also dieses Testsystem nicht wirklich online!

Nachrichten anzeigen

Damit wir auch die Nachrichten auf der Hauptseite "index.php" sehen, fügen wir dort noch PHP-Code hinzu, der die Nachrichten aus der Datei "wall.txt" ausliest und unterhalb des Formulars mit der Überschrift "Wall" anzeigt:

<!DOCTYPE html>
<html lang="de" dir="ltr">
  ...
  <body>
    <h1>Formular</h1>

    ...

    <h1>Wall</h1>

    <?php
      $file = fopen("wall.txt", "r") or die("Konnte Datei nicht öffnen!");
      while(!feof($file)) {
        echo '<p>' . fgets($file) . '</p>';
        echo '<hr>';
      }
      fclose($file);
     ?>

  </body>
</html>

Hier lesen wir den Inhalt der Datei "wall.txt" und schreiben für jede Zeile einen neuen Absatz in unser HTML. Sie sehen, dass fopen diesmal mit "r" (für read) aufgerufen wird. Die While-Scheife schaut zunächst, ob das Dateiende (eof = end of file) erreicht ist. Wenn nicht, wird mit fgets die nächste Zeile ausgelesen und mit echo ausgegeben. Wie immer schließen wir zum Schluss die Datei mit fclose.

Jetzt sieht man unter dem Formular die bisherigen Nachrichten:

PHP-Beispiel: Nachrichten anzeigen

Weiterleitung

Als nächstes möchten wir, dass man beim Absenden der Nachricht nicht auf der Seite "message.php" hängen bleibt. Man möchte schließlich wieder die Startseite sehen. Wir benutzen den Befehl header, den Browser anzuweisen, wieder zurück zu "index.php" zu gehen:

header("Location: index.php");

In "message.php" steht also:

<?php
  $file = fopen("wall.txt", "a+") or die("Konnte Datei nicht öffnen!");
  fwrite($file, "\n\n" . $_GET['user'] . ': ' . $_GET['message']);
  fclose($file);

  header("Location: index.php");
?>

Wenn Sie jetzt im Formular auf "Absenden" klicken, wird zunächst der Code in "message.php" ausgeführt und anschließend wieder auf die Seite "index.php" zurückgegangen. Aus Benutzersicht bleibt man auf der gleichen Seite.

Web-Applikation

Wir haben damit eine echte Web-Applikation geschrieben. Sie können z.B. auf Ihrem Rechner mit unterschiedlichen Browsern auf das System zugreifen und über das Formular Nachrichten hinzufügen. Natürlich sind noch viele Fragen offen, u.a. was passiert, wenn zwei Leute gleichzeitig eine Nachricht absetzen oder ob es nicht besser wäre, eine "Anmeldung" zu verlangen. Deshalb sollten Sie ein so einfaches System nicht einfach online stellen. Für eine echte Webapplikation sollten Sie sich noch weiter informieren, wie Sie Ihre Applikation absichern.

13.2 Cookies

13.2.1 Motivation und Funktion

Bislang haben wir immer mit Requests und Responses gearbeitet. Ein wichtiges Merkmal von Requests ist es, dass diese zustandsfrei sind, d.h. der Server weiß nicht, ein Request irgendetwas mit den früheren Requests zu tun hat (insbes. ob es z.B. der zweite Request eines Users ist).

Wenn der User z.B. die Sprache einstellen kann oder ein Farbschema für die Seite wählt, dann wird diese Auswahl beim nächsten Laden der Seite zurückgestellt. Und im obigen Beispiel mit der ChatWall haben Sie gesehen, dass eine Seite häufig mit jedem Request (z.B. wenn der User ein Formular abschickt) neu geladen wird.

Wir sehen hier eine Situation mit zwei Clients A und B und einem Server. Bei den vier dargestellten Requests weiß der Server nicht, welche zusammen gehören:

Requests und Responses sind zustandsfrei

Wenn also der User von Request 1 die Farbe verstellt, würden alle späteren User eine andere Farbe sehen. Nicht sehr sinnvoll.

Ein Cookie erlaubt uns, eine Information für einen bestimmten User zu "speichern". Speichern heißt hier, dass die Information, sobald sie einmal gesetzt ist, bei jedem weiteren Request des Users wieder mitgeschickt wird.

Funktionsweise

Wenn ein Server einen Request von einem Browser/Client A bekommt, kann er als Antwort (Response) einen Cookie setzen. Dies passiert im Header der HTTP-Nachricht:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Set-Cookie: id=42

Wenn Client A einen weiteren Request an den Server verschickt, wird dieser Cookie immer mitgeschickt, und zwar bei jedem weiteren Request.

GET /foo.php HTTP/1.1
Host: www.anyhost.de
Cookie: id=42

In unserem Beispiel haben wir einen ID gesetzt. So würde man vorgehen, um unterschiedliche Clients voneinander zu unterscheiden:

Cookies

Die Größe eines Cookies ist begrenzt auf 4 KB (ca. 4000 Zeichen). Das heißt, man kann keine großen Datenmengen speichern. Wichtig ist ferner, dass Cookies nicht geheim sind. Man kann Sie sehen und evtl. manipulieren, da sie immer in den HTTP-Nachrichten auftauchen und diese Nachrichten können relativ leicht abgefangen werden.

Cookies sind also zunächst mal eine Möglichkeit, eine gewisse Kontinuität zwischen einem individuellen Client und dem Server herzustellen. Sie haben aber auch viele Einschränkungen. Man könnte damit keinen Online-Shop betreiben, wo Konto- und Warenkorbinformationen für jeden User gespeichert werden müssen.

13.2.2 Cookies in PHP

In PHP ist ein Cookie als superglobale Variable verfügbar:

$_COOKIE

Einen Cookie enthält eine Tabelle mit Namen und Werten. Einen einzelnen Wert setzt man mit

setcookie($name, $wert, $ablaufdatum);

Das Ablaufdatum wird in Sekunden von 1.1.1970 gemessen. Die Funktion time() gibt die aktuelle Zeit an. Dann ist eine Woche von jetzt berechenbar durch:

$ablaufdatum = time() + 60*60*24*7;

Wichtig ist, dass Sie keine booleschen Werte einsetzen, denn dieses Kommando löscht einen Cookie:

setcookie($name, false);

Intern setzt PHP das Ablaufdatum auf 1 Jahr in der Vergangenheit.

Wir möchten jetzt für unsere ChatWall folgendes erreichen:

In unserem Hauptprogramm schauen wir also nach, ob es einen Cookie mit Namen "user" gibt. Wenn ja, zeigen wir "Du bist ..." (mit dem Usernamen eingesetzt). Wenn nein, zeigen wir das Eingabefeld für den Usernamen.

<form action="message.php" method="get">
  <?php
  if (!isset($_COOKIE['user'])) {
    echo 'Name: <input type="text" name="username"><br/>';
  } else {
    $user = $_COOKIE['user'];
    echo "Du bist: $user</br>";
  }
  ?>
  Nachricht: <input type="text" name="message"><br/>
  <input type="submit" value="Absenden">
</form>

In "message.php" holen wir uns den Usernamen aus dem GET-Request. Wenn aber ein Cookie gesetzt ist, nehmen wir dort den Usernamen.

$user = $_GET['username'];
if (isset($_COOKIE['user'])) {
  $user = $_COOKIE['user'];
} else {
  setcookie('user', $user);
}

Zum Löschen des Cookies fügen wir im Hauptprogramm noch ein Kontrollelement (Button) hinzu:

<form action="abmelden.php" method="post">
  <input type="submit" value="Abmelden">
</form>

Wir erzeugen die entsprechende neue PHP-Datei namens "abmelden.php":

<?php
  setcookie('user', false);
  header("Location: index.php");
 ?>

Dieses Programm löscht also den Cookie und springt zurück zur Hauptseite.

13.3 Sessions

Wenn wir eine Webseite als Webapplikation betreiben (z.B. ein Online-Shop), interagieren mehrere User mit dieser Seite. Bei einem normalen Request ist es aber nicht möglich zu unterscheiden, von welchem User dieser Request kommt. Das ist natürlich für z.B. einen Online-Shop ein Problem. Cookies erlauben nur begrenzte Datenmengen und sind öffentlich. Sie lösen also das Problem nicht in Gänze.

Das Konzept der Session löst dieses Problem. Dieser Mechanismus vergibt einem User einen Session-ID und wir können auf der Serverseite immer nachschauen, von welchem User ein Request kommt und die Seite entsprechend zusammenbauen.

Session

Wie man sieht, werden die Daten zu jeder Session auf dem Server gespeichert. Das heißt, dass dies sicherer ist, als wie bei Cookies die Daten immer wieder zu schicken, und dass auch größere Datenmengen gespeichert werden können.

Ein mögliches Problem ist die Tatsache, dass ein Angreifer eine Session-ID erraten oder abfangen kann, um sich dann als der andere User auszugeben. Das nennt man auch Session Hijacking.

Funktionsweise

Sessions arbeiten mit Cookies. Erscheint ein neuer User, wird ein Cookie mit einem neuen ID etabliert und gleichzeitig eine Reihe von Daten zu diesem ID auf der Festplatte des Servers abgelegt (z.B. Name des Users etc.). Im Gegensatz zu Cookies haben Sessions den Vorteil, dass diese Daten auf dem Server liegen und nicht mit jedem Request neu verschickt werden (im Request wird lediglich der Session-ID verschickt). Eine wichtige Eigenschaft einer Session ist, dass sie mit dem Schließen des Browsers automatisch beendet wird.

Der Sessions-Mechanismus ist per Default ausgeschaltet (von PHP) und muss mit

session_start($options);

aktiviert werden.

Zugriff

Auch bei einer Session haben eine superglobale Variable, nämlich

$_SESSION

Auch diese ist - ähnlich wie ein Cookie - ein assoziativer Array, d.h. wir haben es mit Name-Wert-Paaren zu tun.

Sie setzen also Werte mit

$_SESSION['username'] = 'Holger';

Wenn Sie einen Wert auslesen wollen, sollten Sie immer den Fall abfangen, dass der Wert noch nicht gesetzt ist:

$name = isset($_SESSION['username']) ? isset($_SESSION['username']) : 'unbekannt';

Sie können auch ein Name-Wert-Paar löschen mit

unset($_SESSION['username']);