3. Workshop: Notizbuch in PHP mit mySQL (von Michael Christoph)

Hat jetzt bloß keiner meinen Workshop zu PHP in der letzten AMIGA aktuell
gelesen oder hat sich jeder darauf verlassen, dass schon irgendjemand auf
den Aufruf antwortet? Fakt ist, dass keine einzige Reaktion erfolgte! Somit 
ist es natürlich für mich, wie auch die anderen Artikelschreiber, schwierig 
zu erfahren, worüber man schreiben soll, damit es euch interessiert.

* ALSO WEITER MIT mySQL

Da jedoch mySQL recht populär ist und auch als Portierung für den Amiga
vorliegt, wird es wohl den einen oder anderen doch interessieren.

mySQL-Logo:
mySQL-Logo

Also gibt es diesmal als Fortsetzung zum Kalender eine Notizfunktion.
Die komplette Datenverwaltung überlassen wir dabei der mySQL-Datenbank.

* DIE UMGEBUNG KOMMT ZUERST

Diesmal vorweg ein paar einführende Worte über den Aufbau und die 
Funktionsweise. Als "notizbuch.html" ist eine Einstiegsseite definiert, die 
Frames benutzt. Dadurch muss nicht immer die komplette Seite neu aufgebaut 
werden, sondern es kann ein Teil ausgetauscht werden (bei uns die Notizen 
rechts). Der linke Frame enthält ein leicht erweitertes PHP-Skript des 
Kalenders, genannt "notizkalender.php". Jede einzelne Monatszahl ist jetzt 
ein Link in der Form:

  <a href="notizeintrag.php?tag=$tag&monat=$monat&jahr=$jahr" target=''> [hier steht die Monatszahl] </A>

Beim Anklicken der Zahl wird also der Link geladen beziehungsweise unser 
neues Skript "notizeintrag.php" aufgerufen. Übergeben werden in URL-
Notation die einzelnen Parameter: Tag, Monat und Jahr. Mittels ‚target‘ 
wird der Frame definiert, in dem das Ergebnis angezeigt werden soll (also 
die rechte Fensterhälfte).

Zusätzlich wurde der Kalender noch so abgeändert, dass der Vorgängermonat 
über dem Kalender und der Nachfolgemonat unter dem Kalender platziert wird, 
und nicht mehr links/rechts davon. Eine Erweiterungsmöglichkeit wäre zum 
Beispiel, mehrere Monate im Voraus auszugeben (einfach mittels for-
Schleife).

* DAS IST NEU

Wenden wir uns jetzt der neuen Datei "notizeintrag.php" zu. Es handelt sich 
wieder um PHP-Code, wie die Endung bereits verrät. Neu ist vor allem die 
Benutzung der mySQL-Funktionen.

* MEHR ZU mySQL GIBT'S DORT

Auch hierfür möchte ich Interessierte wieder auf das Amiga Magazin 08/2001 
beziehungsweise online http://www.meicky-soft.de/amiga-magazin/mysql.html 
verweisen. Dort habe ich ausführlich die Installation von mySQL am Amiga 
beschrieben. Auch Datenbankdesign und SQL sind dort Themen des zweiten 
Kursteiles.

* EINE DATENBANK MUSS HER

Bevor die Notizdatenbank benutzt werden kann, muss sie erst einmal 
definiert werden. Da in der Amiga-Portierung das "CREATE DATABASE" Kommando 
nicht korrekt funktioniert, ist mittels "makedir <datenbankname>" ein 
Verzeichnis für die neue Datenbank anzulegen. Standardmäßig hat dies im 
Verzeichnis "mysql:data/" zu erfolgen (wenn nicht anders in my.cnf 
definiert).
Als nächstes muss noch die eigentliche Tabelle (die umgangssprachlich auch 
gern als Datenbank bezeichnet wird) angelegt werden. Zum Beispiel

  CREATE TABLE notizbuch (id INT NOT NULL AUTO_INCREMENT,
                          datum DATE,
                          uhrzeit TIME,
                          text CHAR(100),
                          PRIMARY KEY(id)
                         );

Mögliche Datentypen sind (unter anderem) INT für Zahlen, REAL für 
Fließkommazahlen, DATE für Datumswerte, TIME für Zeitwerte, CHAR für eine 
definierte Anzahl an Zeichen. VARCHAR kann dynamisch bis zu 4096 Zeichen
aufnehmen.

Jeder Satz erhält bei uns eine eindeutige Nummer (id). Damit wird er für 
Änderungen oder zum Löschen gefunden. Die Nummer wird automatisch von mySQL 
vergeben (der letzte Tabelleneintrag wird um eins erhöht). Für schnelleren 
Zugriff erklären wir dieses Feld auch als Indexfeld. PRIMARY legt außerdem 
fest, dass jede 'id' nur einmal benutzt werden kann, also nicht zwei Sätze 
mit identischer 'id' vorhanden sein können.

* EINE VERBINDUNG ZUR DATENBANK MUSS HERGESTELLT WERDEN

Mittels mysql_connect() wird eine Verbindung zum Datenbankserver 
hergestellt. Alternativ gibt es das mysql_pconnect()-Kommando, das eine 
persistente Verbindung herstellt. Das heißt, wenn mehrmals auf die 
Datenbank zugegriffen werden soll, bleibt die Verbindung bestehen und wird 
nicht immer neu aufgebaut.
Beide Funktionen erwarten den Namen des Hosts, einen Benutzernamen und 
dessen Passwort. Da alle diese Werte im Klartext übergeben werden müssen, 
sollten sie nicht direkt im PHP-Skript abgelegt werden, sondern per 
include-Anweiung eingebunden werden. Die separate Datei mit diesen Werten 
muss außerdem außerhalb des normalen Webspaces liegen (in der Regel htdocs-
Verzeichnis). Nur so ist sichergestellt, dass niemand von außen per HTTP 
oder FTP auf die Datei zugreifen und deren Inhalt lesen kann.
Wenn die Verbindung hergestellt werden konnte, liefert die Funktion einen 
positiven Wert. Dann muss nur noch die Datenbank ausgewählt werden, mit der 
gearbeitet werden soll. Im Fehlerfall wird lediglich ein entsprechender 
Hinweis ausgegeben und das PHP-Skript abgebrochen.

  $connID = @mysql_pconnect($mysqlhost, $mysqluser, $mysqlpasswd);
 
  if($connID)
  {
    mysql_select_db($mysqldatabase);  /* Datenbank connectieren */
    return( $connID );
  }
  else
  {
    echo "<BR><font color='red'>Keine Verbindung zur Datenbank möglich !</font><BR>\n";
    echo "Fehlerursache: " . mysql_error() . "<BR>\n";
    exit();                        /* Skript abbrechen */
  }

* UND SCHON KANN DER TABELLENINHALT ANGEZEIGT WERDEN

Mittels SELECT wird in mySQL nach Datensätzen gesucht. Dabei können auch 
sehr komplexe Suchbedingungen und Gruppierungsanweisungen verwendet werden. 
Zusätzlich können auch noch Verknüpfungen mit anderen Tabellen ins Spiel 
kommen (JOIN). Wir beschränken uns jedoch auf eine ganz einfache Syntax: 
Alle Einträge zu einem Datum sollen angezeigt werden. Die zugehörigen Daten 
liefert der Aufruf von

  SELECT * FROM notizbuch WHERE datum = '$suchdatum' ORDER BY uhrzeit;

wobei die gefundenen Treffer nach der Uhrzeit sortiert ausgegeben werden 
sollen. Im PHP-Code muss das ganze noch in eine mysql_query() gepackt 
werden.
Weitere benötigte Funktionen sind
- mysql_num_rows     : liefert die Anzahl an Treffern (Zeilen)
- mysql_fetch_object : liefert einen kompletten Datensatz

Jetzt muss nur noch eine Tabelle im HTML-Code ausgespuckt werden, damit 
eine saubere Formatierung im Browser möglich ist. Dabei werden die 
gefundenen Daten als Formular aufbereitet, so dass jederzeit einfach 
Änderungen an den Einträgen möglich sind.

Zuerst kommt jetzt der PHP-Code für die ganze Datenbankabfrage:

  /* ab hier Anzeige des Tabelleninhalts (der Notizen) */
  if(isset($jahr))
  {
    $suchdatum = date("Y-m-d",mktime(0,0,0,$monat,$tag,$jahr));
                           /* formatieren als jahr-monat-tag */
 
    echo "Eintragungen für\n<H1>$tag. $monatstr[$monat] $jahr</H1><BR><BR>\n";
 
    echo "<table border=0>\n";
    echo "  <tr><th>Uhrzeit</th><th>Eintragung</th><th>Aktionen</th></tr>\n";
 
    /* Datenbankabfrage starten */
    $result = mysql_query("SELECT * FROM notizbuch WHERE datum = '$suchdatum' ORDER BY uhrzeit");
    if($result)
    {
      /* Anzahl Zeilen ermitteln */
      $rows = mysql_num_rows($result);
      if($rows > 0)
      {
        /* Tabellen-Inhalt ausgeben */
        while($dbentry = mysql_fetch_object($result))
        {
          eintrag_zeigen($dbentry->id,
                         $dbentry->datum,
                         substr($dbentry->uhrzeit,0,5),  /* Sekunden weg */
                         $dbentry->text);
        }
      }
      else
      {
        echo "<tr><td colspan=3><i>keine Eintragungen vorhanden</i></td></tr>\n";      }
    }
    //else: Datenbank existiert nicht oder SQL-Fehler
 
    /* entfernt die Ergebnisdaten aus dem Speicher */
    mysql_free_result($result);

Fehlt uns noch die Funktion eintrag_zeigen(), die einen einzelnen 
gefundenen Datensatz als Formular aufbereitet erzeugt:

  function eintrag_zeigen($dbid,$dbdatum,$dbuhrzeit,$dbtext)
  {
    echo "<tr>\n";
    echo "<form name='notizbuch_$dbid' action='notizeintrag.php' method='post'>\n";
    echo "  <td><input type=text   name='uhrzeit'       value='$dbuhrzeit' maxlength=5></td>\n";
    echo "  <td><input type=text   name='text'          value='$dbtext'    maxlength=100></td>\n";
    echo "  <td><input type=hidden name='datum'         value='$dbdatum'>\n";
    echo "      <input type=hidden name='id'            value='$dbid'>\n";
    echo "      <input type=submit name='form_loeschen' value='löschen'>\n";
    echo "      <input type=submit name='form_aendern'  value='ändern'>\n";
    echo "  </td>\n";
    echo "</form>\n";
    echo "</tr>\n";
  }

Die beiden 'hidden'-Felder werden nicht im Browser angezeigt, sondern 
dienen uns lediglich als Merkfeld. Zum einen für die Datensatznummer und 
zum anderen für das Tagesdatum. Ein Eintrag kann also nur innerhalb des 
aktuellen Tages durch Änderung der Uhrzeit verschoben werden, nicht aber 
auf einen anderen Tag. Die beiden 'submit'-Einträge erzeugen einen Button 
im Browser, mit dem die Änderungen in die Tabelle zurückgeschrieben werden 
können oder ganz aus der Tabelle gelöscht werden.

Fehlt uns also nur noch die Möglichkeit, auch Daten in die Tabelle hinein 
zu bekommen. Dazu wird ganz einfach am Ende ein leeres Formular erzeugt und 
mit einem passenden Button versehen:

  echo "<tr>\n";
  echo "<form name='notizbuch_0' action='notizeintrag.php' method='post'>\n";
  echo "  <td><input type=text name='uhrzeit' value='' maxlength=5></td>\n";
  echo "  <td><input type=text name='text'    value='' maxlength=100></td>\n";
  echo "  <td><input type=hidden name='datum' value='$suchdatum'>\n";
  echo "      <input type=hidden name='id'    value='0'>\n";
  echo "      <input type=submit name='form_neu' value='neu erfassen'>\n";
  echo "  </td>\n";
  echo "</form>\n";
  echo "</tr>\n";

* DIE BUTTONS BRAUCHEN EINEN VERTEILER

Wenn einer der Buttons angeklickt wird, wird das Formular an uns selbst 
geschickt (bewirkt der Teil action='notizeintrag.php'). Man könnte hier 
zwar auch eine andere Datei verwenden, da aber auch bei 
Änderungen/Löschungen die Notizeinträge wieder am Bildschirm angezeigt 
werden sollen, bietet es sich geradezu an, alles in einer Datei 
zusammenzufassen.

Allerdings wird ein Unterscheidungsmerkmal benötigt, mit dem wir 
feststellen können, ob wir durch Anklicken eines Datums oder Buttons 
aufgerufen wurden. Eine ganz einfache Möglichkeit stellt hierbei das 'id'-
Feld dar. Bei den Aktionen Neu, Bearbeiten und Löschen ist dieses immer 
vorhanden, während es bei der Auswahl des Tagesdatums nicht da ist.
In einer zweiten Abfrage kann dann unterschieden werden, welche Aktion 
genau zu erfolgen hat.

  /* wurde ein Formular übergeben? */
  if(isset($id))
  {
    if(isset($form_neu))      eintrag_neu($datum,$uhrzeit,$text);
    if(isset($form_aendern))  eintrag_aendern($id,$datum,$uhrzeit,$text);
    if(isset($form_loeschen)) eintrag_loeschen($id);
 
    /* zur anzeige unten wird das Datum wieder zerlegt erwartet */
    /* mit substr() wird der String zerlegt und */
    /* mit sprintf() evtl. führende Nullen entfernt */
    $jahr  = sprintf("%d",substr($datum,0,4));
    $monat = sprintf("%d",substr($datum,5,2));
    $tag   = sprintf("%d",substr($datum,8,2));
  }

Nach jeder dieser Aktionen soll auch wieder der aktuelle Tabelleninhalt 
angezeigt werden. Problematisch dabei ist jedoch, dass die Felder tag, 
monat und jahr nicht mehr existieren - sie werden nur vom Kalender 
mitgeliefert.
Eine Möglichkeit wäre nun, diese drei Felder in weiteren hidden-Einträgen 
der Formulare unterzubringen. Da wir aber sowieso das aktuelle Datum haben,
können wir auch einfach dieses zerlegen. Zu beachten ist lediglich, dass es
in amerikanischer Notation vorliegt. Also zuerst das Jahr, dann Monat und 
ganz rechts der Tag. Da das Datum mit führenden Nullen erzeugt wird, müssen 
wir noch zu einem Trick greifen, um diese wieder zu entfernen. Denn 
ansonsten, wird ein "09" als Oktalzahl 9 = nicht möglich/ungültig 
betrachtet.
Also wird der Teilstring mittels sprintf() in eine Zahl umgewandelt
(%d = Dezimal). Dabei verschwinden dann die Nullen. Alternativ könnte auch 
abgefragt werden, ob das erste Zeichen eine Null ist und dann der String 
direkt manipuliert werden.

Nach diesem Ausflug fehlen uns nur noch die oben aufgeführten drei 
Funktionen, um neue Einträge in die Tabelle zu schreiben, vorhandene 
Tabelleneinträge zu verändern oder Tabelleneinträge zu löschen.

* EINEN NEUEN EINTRAG ERZEUGEN

Um einen neuen Tabelleneintrag zu erzeugen, werden das Datum, die Uhrzeit 
und der Text erwartet. Die 'id' wird von mySQL selbständig hochgezählt. Es 
hat keine Bedeutung, was als erstes Value übergeben wird. Optional könnte 
auch jedes einzelne Feld mittels 'SET <feldname> = <wert>' belegt werden. 
Dabei spielt die Reihenfolge keine Rolle, und es müssen auch nicht alle 
Felder angegeben werden.

  function eintrag_neu($dbdatum,$dbuhrzeit,$dbtext)
  {
    $result = mysql_query("INSERT INTO notizbuch VALUES(0,'$dbdatum','$dbuhrzeit','$dbtext');");
    if($result)
    {
      /* liefert die Satz-Id, interessiert uns aber nicht weiter */
      $newid = mysql_insert_id($result);
    }
    else
    {
      echo "<BR><font color='red'>EINFÜGEN WAR NICHT MÖGLICH!</font><BR>\n";
      echo "Fehlerursache: " . mysql_error() . "<BR>\n";
    }
  }

* EINTRAG BEARBEITEN

Wenn ein Eintrag bereits in der Liste war, muss dieser mit REPLACE oder 
UPDATE bearbeitet werden. Anhand der 'id' erkennt mySQL automatisch den 
Satz, der geändert werden muss. Übergeben werden in diesem Fall alle 
Inhalte, wobei sich aber das Datum niemals ändern kann, da der Benutzer nur 
die Uhrzeit und den Text im Browser bearbeiten kann.

  function eintrag_aendern($dbid,$dbdatum,$dbuhrzeit,$dbtext)
  {
    $result = mysql_query("REPLACE INTO notizbuch VALUES($dbid,'$dbdatum','$dbuhrzeit','$dbtext');");
    if($result)
    {
    }
    else
    {
      echo "<BR><font color='red'>ÄNDERN WAR NICHT MÖGLICH!</font><BR>\n";
      echo "Fehlerursache: " . mysql_error() . "<BR>\n";
    }
  }

* EINTRAG LÖSCHEN

Das Löschen eines Satzes gestaltet sich recht einfach. Es wird nur ein 
eindeutiger Schlüssel benötigt. In unserem Fall ist dies wieder die 'id'.

  function eintrag_loeschen($dbid)
  {
    $result = mysql_query("DELETE FROM notizbuch WHERE id = $dbid;");
    if($result)
    {
    }
    else
    {
      echo "<BR><font color='red'>LÖSCHEN WAR NICHT MÖGLICH!</font><BR>\n";
      echo "Fehlerursache: " . mysql_error() . "<BR>\n";
    }
  }

In allen drei Funktionen wird im Fehlerfall ein kurzer Hinweis im Browser 
ausgegeben und die Fehlerursache mittels mysql_error() von mySQL abgefragt.

Damit wäre die komplette Funktionalität beschrieben.

Diese Datei ist im Browser anzugeben
Hiermit wird der linke Kalender erzeugt
Hiermit werden die rechten Eintragungen verarbeitet
Die ausgelagerte Connect-Funktion

* NOCH EIN PAAR GRAFIKEN

Abschließend noch Grafiken der drei verschiedenen Amiga-Browser (Voyager 
3.3 konnte ich nicht zum Offline-Betrieb überreden).

AWebII 3.3:
AWebII 3.3
IBrowse-Demo 1.12:
IBrowse-Demo 1.12
Voyager-Demo 2.95:
Voyager-Demo 2.95

Beim Testen ist noch zu beachten, dass der Browser nicht auf seinen Cache
zurückgreift, sondern die Dateien immer frisch vom Server anfordert. Bei 
AWeb ist unter Einstellungen/Netzwerkeinstellungen auf der Seite "Puffer" 
der Eintrag "überprüfe Pufferkopie" auf "immer" zu setzen. Zwar ist in 
jedem Header <META HTTP-EQUIV="Pragma" CONTENT="no-cache"> gesetzt, doch 
beachtet kein einziger Amiga-Browser diesen Meta-Tag.

* UND WAS KOMMT DANN?

Das hängt auch von euch, den Lesern ab. Über Zuschriften aller Art freue
ich mich genauso wie Carsten.

Michael Christoph <michael@meicky-soft.de>