Unterabschnitte


Spruch des Abrufs

Was soll das Script eigentlich tun? Nun, es gibt häufiger den Spruch des Tages oder ähnliches. So etwas mit PHP zu realisieren wird schwierig. Wenn man einen Spruch des Tages realisieren will, hat man zwei Teilprobleme: Erstens muß man für diesen Tag einen Spruch auswählen und diesen zweitens anzeigen. Ersteres könnte man theoretisch dadurch lösen, daß man um Mitternacht ein Script aufruft, das den gewählten Spruch irgendwo speichert, von wo er dann den ganzen Tag lang ausgegeben werden kann.

PHP bietet allerdings von sich aus keine Möglichkeit, zu gewissen Zeiten irgendetwas automatisch zu machen. Die einzige Möglichkeit ist, mit einem Hilfsprogramm, wie z.B. CRON unter Unix, zum gewünschten Zeitpunkt das Ganze anzustoßen. Es gibt auch noch die Bastellösung, bei der bei der Ausgabe immer abgeprüft wird, ob es der erste Aufruf an diesem Tag ist und wenn ja, wird ein neuer Spruch ausgewählt.

Um das Ganze etwas einfacher zu halten, wird bei jedem Aufruf ein Spruch ausgewählt, der dann ausgegeben wird.

Kommen wir nun aber zum Programmieren. Als erstes stellt sich, wie immer, die Frage nach der Tabelle. Dies sollte hier kein großes Problem sein.

CREATE TABLE spruch (
  SID       int not null primary key auto_increment,
  Spruch    text,
  anzeigen  bool
) TYPE=MYISAM;

CREATE unique INDEX spruch_idx_Spruch ON spruch (Spruch(200));

Neben dem eigentlichen Text und einem eindeutigen Schlüssel habe ich noch eine kleine Spalte ,anzeigen` eingeführt, über die man später verhindern kann, daß ein Spruch angezeigt wird.

Viel interessanter ist die zweite Anweisung. Im Endeffekt will ich nur erreichen, daß ein Spruch nicht zweimal eingetragen werden kann. Im Prinzip kein Problem, ein UNIQUE im CREATE TABLE auf ,,Spruch`` sorgt schon dafür, sollte man meinen. Dem ist aber leider nicht so, da ,,Spruch`` vom Typ TEXT ist und dieser in Sachen Index etwas anders funktioniert als z.B. VARCHAR (d.h. er erlaubt keinen). Ab MySQL Version 3.23 ist es aber möglich, über diese zweite Anweisung einen Index auf eine TEXT-Spalte zu legen, wobei dann nur die ersten n Buchstaben genutzt werden (hier sind es 200).

Nachdem nun die Tabellenerstellung geklärt ist, kommen wir zu den eigentlichen PHP-Scripten. Hier stellt sich wieder die Frage, nämlich, in welcher Reihenfolge sie programmiert werden sollten. Ich habe mich für die folgende entschieden:

Kurze Begründung:
Das Einfügen in die Datenbank kann über ein einfaches SELECT von Hand überprüft werden. Der zufällige Spruch ist ja das eigentliche Ziel des Ganzen; hier könnte man jetzt auch schon fast aufhören; zumindest kann man jetzt das Ganze in Betrieb nehmen. Um einen Spruch zu löschen, muß man ihn irgendwie auswählen; deshalb die Gesamtausgabe vor dem Löschen. Über Löschen und anschließendes Einfügen kann man im Prinzip auch ändern; um das aber etwas komfortabler zu machen, gibt es als Letztes auch noch ein Ändern.

Neuen Spruch einfügen

Das folgende Script ist im Prinzip genauso aufgebaut, wie in 9.4 schon beschrieben. Im Prinzip sollte es durch die Kommentare selbsterklärend sein. Trotzdem noch zwei kleine Anmerkungen.

Es erfolgt keinerlei Authentifizierung, d.h. jeder kann beliebige Daten in die Datenbank einfügen. Wie man eine entsprechende Paßwortüberprüfung einbauen könnte, steht im Kapitel 11.1.3.

In der Variable $anzeigen bzw. im Checkbox-Inputfeld wird nicht der Wert übergeben, der nachher in der Datenbank stehen soll (0 oder 1), sondern ,,checked`` oder ,,``. Dadurch wird zwar die Wiederanzeige des Formulars einfacher, aber das Einfügen in die Datenbank etwas schwieriger. Es ist also eigentlich egal, wie man es realisiert.


<?php

// Verbindungsdaten MySQL DB
$DB_HOST = "localhost";
$DB_USER = "cr";
$DB_PASS = "123";
$DB_NAME = "cr";

$DatenOK = true;
$Fehler  = "";

if (isset($_GET["submit"])){
  // Formular wurde abgeschickt -> Daten pruefen
  if ($_GET["Spruch"] == ""){
    $DatenOK = false;
    $Fehler .= "Bitte noch einen Text eingeben!<br>\n";
    $daten["Spruch"] = "";
  }
  else {
    // Daten OK
    $daten["Spruch"] = $_GET["Spruch"];
  }

  // anzeigen kann nur true oder false sein
  // bei true ist es gesetzt
  if (isset($_GET["anzeigen"])){
    $daten["anzeigen"] = 1;
  }
  else {
    $daten["anzeigen"] = 0;
  }

  if ($DatenOK){
    // Daten in DB eintragen
    mysql_connect($DB_HOST, $DB_USER, $DB_PASS)
      OR die("Konnte DB nicht erreichen!");
    mysql_select_db($DB_NAME)
      OR die("Konnte DB nicht erreichen");

    mysql_query(sprintf('INSERT INTO spruch 
                                (Spruch, anzeigen)
                         VALUES ("%s"  , %d)',
			addslashes($daten["Spruch"]),
			$daten["anzeigen"]));
    switch (mysql_errno()){
    case 0:
      // Alles OK
      header('Location: http://'.$_SERVER["HTTP_HOST"].
	     substr($_SERVER["PHP_SELF"],0,
		    strrpos($_SERVER["PHP_SELF"],'/'))
	     .'/show_all.php4');
      exit;
      continue;
    case 1062:
      // Spruch doppelt eingetragen
      $DatenOK = false;
      $Fehler .= "Den Spruch gibt es schon<br>\n";
      continue;
    default:
      // Sonstiger Fehler
      // -> Fehlermeldung ausgeben
      $DatenOK = false;
      $Fehler .= "MySQL: ".mysql_errno().": ".
	         mysql_error()."<br>\n";
    }
  }
}
else {
  // Werte vorbelegen
  $daten["Spruch"]   = "";
  $daten["anzeigen"] = 1;
}
?>
<html>
<head>
  <title>SDA eintragen</title>
</head>
<body>
<?php
if (!$DatenOK){
  echo "<h1>$Fehler</h1>";
}
?>
<form action="<?php 
  echo $_SERVER["PHP_SELF"]; 
?>" method="GET">
<div align="center">

<p>
<textarea name="Spruch" rows="5" cols="80"><?php 
echo htmlentities($daten["Spruch"]); 
?></textarea>
</p>

Spruch anzeigen <input type="checkbox" name="anzeigen" 
value="checked" <?php 
  echo ($daten["anzeigen"] ? "checked" : ""); 
?>><p>

<input type="submit" name="submit" value=" Alles OK ">

</div>
</form>
</body>

Zufälligen Spruch ausgeben

Das ist jetzt fast der einfachste Teil des Ganzen. Wie man einen zufälligen Datensatz auswählt, ist bereits im Kapitel 7.1 erklärt, von daher spare ich mir hier die Erklärung.

Ansonsten sollte das Script eigentlich selbsterklärend sein. In der ersten Datei, die ich sda.php4 genannt habe, wird eine Funktion get_spruch() realisiert. Diese kann dann später aus beliebigen anderen Dateien per include() eingefügt werden.

Es gibt auch noch eine zweite Funktion get_sprueche, die ein Array mit mehreren Sprüchen zurück gibt. Diese Funktion ist praktisch, wenn du verhindern willst, das deine Besucher den Server mit ständigem Neuladen der Webseite überlasten, um an alle Sprüche zu kommen. ;-)


<?php

/**
 * Holt einen zufaelligen Spruch aus der Datenbank
 * und gibt ihn als String zurueck
 */
function get_spruch(){
  // Verbindungsdaten MySQL DB
  $DB_HOST = "localhost";
  $DB_USER = "cr";
  $DB_PASS = "123";
  $DB_NAME = "cr";
  
  mysql_connect($DB_HOST, $DB_USER, $DB_PASS)
    OR die("Konnte DB nicht erreichen!");
  mysql_select_db($DB_NAME)
    OR die("Konnte DB nicht erreichen");
  
  $res = mysql_query('SELECT Spruch, 
                             SID*0+rand() AS sort
                        FROM spruch
                       WHERE anzeigen = 1
                       ORDER BY sort
                       LIMIT 1');

  if (!$row = mysql_fetch_array($res)){
    echo "Fehler im Script!";
  }

  return $row["Spruch"];
}


/**
 * Holt die gewuenschte Zahl an Spruechen aus der
 * Datenbank und gibt diese als Array zurueck.
 * Wird nichts angegeben, werden 3 Sprueche aus der
 * Datenbank geholt.
 */
function get_sprueche($anzahl=3){
  // Verbindungsdaten MySQL DB
  $DB_HOST = "localhost";
  $DB_USER = "cr";
  $DB_PASS = "123";
  $DB_NAME = "cr";
  
  mysql_connect($DB_HOST, $DB_USER, $DB_PASS)
    OR die("Konnte DB nicht erreichen!");
  mysql_select_db($DB_NAME)
    OR die("Konnte DB nicht erreichen");
  
  $res = mysql_query(sprintf('SELECT Spruch, 
                                     SID*0+rand() AS sort
                                FROM spruch
                               WHERE anzeigen = 1
                               ORDER BY sort
                               LIMIT %d',
			     $anzahl));

  while($row = mysql_fetch_array($res)){
    $sprueche[] = $row["Spruch"];
  }

  return $sprueche;
}
?>

Und um zu sehen, daß die Funktion tatsächlich funktioniert, hier noch ein sehr kompliziertes Script.

<?php

  // Wir wollen sauber programmieren
error_reporting(E_ALL);

include('./sda.php4');
?>
<html>
<head>
</head>
<body>
<pre>
<?php echo get_spruch(); ?>
</pre>
</body>

Und hier das zweite Script, für mehrere Sprüche. Es liest aus einen GET-Parameter die gewünschte Anzahl der Sprüche.

<?php

  // Wir wollen sauber programmieren
error_reporting(E_ALL);

include('./sda.php4');

if (isset($_GET["anzahl"]) && 
    is_numeric($_GET["anzahl"]) &&
    $_GET["anzahl"] > 0) {
  $anzahl = $_GET["anzahl"];
}
else {
  $anzahl = 3;
}
?>
<html>
<head>
</head>
<body>
<?php 
$sprueche = get_sprueche($anzahl);
foreach ($sprueche AS $spruch){
  printf("<pre>\n%s\n</pre>\n",
	 $spruch);
}
?>
</body>

Alle Sprüche ausgeben

Wie schon anfangs geschrieben, wird zur Administration eine Übersicht über alle existierenden Sprüche benötigt. Dies soll hier realisiert werden. Dazu muß natürlich zu jedem Spruch noch ein Link zum Ändern bzw. Löschen angegeben werden. Die ID des zu ändernden bzw. löschenden Scripts wird per GET-Parameter an das dann noch zu schreibende Script übergeben.

Auch hier erfolgt keinerlei Paßwortabfrage oder ähnliches, das heißt jeder kann sich alle Sprüche ansehen.

Das Script ist nicht sehr elegant programmiert, aber es funktioniert.


<?php

// Verbindungsdaten MySQL DB
$DB_HOST = "localhost";
$DB_USER = "cr";
$DB_PASS = "123";
$DB_NAME = "cr";

mysql_connect($DB_HOST, $DB_USER, $DB_PASS)
     OR die("Konnte DB nicht erreichen!");
mysql_select_db($DB_NAME)
     OR die("Konnte DB nicht erreichen");

?>
<html>
<head>
  <title>SDA ansehen</title>
</head>
<body>
<center>
<a href="insertchange.php4">Neu einf&uuml;gen</a>
</center>
<ul>
<?php

$res = mysql_query('SELECT SID, Spruch
                    FROM spruch
                    ORDER BY SID');

while ($row = mysql_fetch_array($res)){
  printf('<li><pre>%s</pre>
<a href="insertchange.php4?SID=%d">&Auml;ndern</a> &nbsp;
<a href="delete.php4?SID=%d">L&ouml;schen</a>'."\n",
	 $row["Spruch"],
	 $row["SID"],
	 $row["SID"]);
}

?>
</ul>
</body>

Spruch löschen

Nachdem wir nun Sprüche eintragen und sogar ausgeben können, wäre es evtl. auch ganz praktisch, sie zu löschen[*]. Das Löschen ist eigentlich kein großes Problem; das Script braucht nur die ID des zu löschenden Spruchs und ruft dann ein DELETE auf. Den Aufruf für das Script haben wir ja eben schon geschrieben.

Nach dem Löschen kann man entweder eine Erfolgsmeldung ausgeben, oder einfach wieder auf z.B. die Übersichtsseite weiterleiten, was hier gemacht wird.


<?php

if (!isset($_GET["SID"]) || !is_numeric($_GET["SID"])){
  die("Fehler");
}

// Verbindungsdaten MySQL DB
$DB_HOST = "localhost";
$DB_USER = "cr";
$DB_PASS = "123";
$DB_NAME = "cr";

mysql_connect($DB_HOST, $DB_USER, $DB_PASS)
     OR die("Konnte DB nicht erreichen!");
mysql_select_db($DB_NAME)
     OR die("Konnte DB nicht erreichen");

mysql_query(sprintf('DELETE FROM spruch
                     WHERE SID=%d',
		    $_GET["SID"]));
    
header('Location: http://'.$_SERVER["HTTP_HOST"].
	     substr($_SERVER["PHP_SELF"],0,
		    strrpos($_SERVER["PHP_SELF"],'/'))
       .'/show_all.php4');
?>

Spruch ändern

Und als letztes wäre es natürlich auch ganz nett, einen Spruch auch ändern zu können. Wer jetzt faul ist (und wer ist das nicht?) wird denken: Das HTML-Formular haben wir doch schon. Stimmt, deshalb benutzen wir das auch; allerdings nicht per Copy and Paste, sondern wir erweitern das Einfügen-Script ein wenig.

Die Änderungen sind eigentlich relativ einfach. Als erstes wird die Datenbankverbindung immer aufgebaut, da sie an mehreren Stellen benötigt wird (hätte man auch durch eine Funktion eleganter lösen können). Als nächstes muß bei der Datenprüfung nun auch die Spruch-ID geprüft werden. Hier beschränke ich mich auf die Prüfung, ob es eine Zahl ist. Die eigentliche SQL-Anweisung muß natürlich auch erweitert werden, die Fehlerabfrage kann dann jedoch wieder dieselbe sein. Schließlich müssen auch irgendwo die alten Werte geholt werden und als letztes wird das HTML-Formular so erweitert, daß es bei Bedarf auch noch die Spruch-ID wieder übergibt. Das waren dann auch schon alle Änderungen und wir haben unser Änderungsscript fertig.


<?php

// Verbindungsdaten MySQL DB
$DB_HOST = "localhost";
$DB_USER = "cr";
$DB_PASS = "123";
$DB_NAME = "cr";

// erstmal ist alles OK
$DatenOK = true;
$Fehler  = "";

mysql_connect($DB_HOST, $DB_USER, $DB_PASS)
     OR die("Konnte DB nicht erreichen!");
mysql_select_db($DB_NAME)
     OR die("Konnte DB nicht erreichen");
  

if (isset($_GET["submit"])){
  // Formular wurde abgeschickt -> Daten pruefen
  if ($_GET["Spruch"] == ""){
    $DatenOK = false;
    $Fehler .= "Bitte noch einen Text eingeben!<br>\n";
    $daten["Spruch"] = "";
  }
  else {
    // Daten OK
    $daten["Spruch"] = $_GET["Spruch"];
  }

  // anzeigen kann nur true oder false sein
  // bei true ist es gesetzt
  if (isset($_GET["anzeigen"])){
    $daten["anzeigen"] = 1;
  }
  else {
    $daten["anzeigen"] = 0;
  }

  if (isset($_GET["SID"])){
    // Eine Spruch-ID wurde übergeben
    // -> sie muß geprüft werden
    //
    // Überprüfung, ob sie tatsächlich existiert,
    // könnte auch noch erfolgen
    if (!is_numeric($_GET["SID"])){
      $DatenOK = 0;
      $Fehler .= "Ungültige Spruch-ID &uuml;bergeben!<br>\n";
    }
    else {
      // Daten OK
      $daten["SID"] = $_GET["SID"];
    }
  }    
  
  if ($DatenOK){
    // Daten in DB eintragen
    if (isset($daten["SID"])){
      // Spruch-ID wurde übergeben
      // -> Spruch wird geändert
      mysql_query(sprintf('UPDATE spruch 
                           SET Spruch="%s", anzeigen=%d
                           WHERE SID=%d',
			  addslashes($daten["Spruch"]),
			  $daten["anzeigen"],
			  $daten["SID"]));
    }
    else {
      // neuer Spruch -> einfügen
      mysql_query(sprintf('INSERT INTO spruch 
                                    (Spruch, anzeigen)
                             VALUES ("%s"  , %d)',
			  addslashes($daten["Spruch"]),
			  $daten["anzeigen"]));
    }
    
    // MySQL-Rückgabewert auswerten
    // Wenn OK -> Weiterleiten
    // sonst -> Fehlermeldung
    switch (mysql_errno()){
    case 0:
      // Alles OK
      header('Location: http://'.$_SERVER["HTTP_HOST"].
	     substr($_SERVER["PHP_SELF"],0,
		    strrpos($_SERVER["PHP_SELF"],'/'))
	     .'/show_all.php4');
      exit;
      continue;
    case 1062:
      // Spruch doppelt eingetragen
      $DatenOK = false;
      $Fehler .= "Den Spruch gibt es schon<br>\n";
      continue;
    default:
      // Sonstiger Fehler
      // -> Fehlermeldung ausgeben
      $DatenOK = false;
      $Fehler .= "MySQL: ".mysql_errno().": ".
	         mysql_error()."<br>\n";
    }
  }
}
elseif (isset($_GET["SID"])){
  // es soll der Spruch SID geändert werden
  // -> bisherige Werte laden
  //
  // SID muss an der Stelle nicht auf numerisch
  // getestet werden, da es nur mit %d im sprintf()
  // genutzt wird
  $res= mysql_query(sprintf('SELECT SID, Spruch, anzeigen
                             FROM spruch
                             WHERE SID=%d',
			    $_GET["SID"]));
  
  if (!$row = mysql_fetch_array($res)){
    $DatenOK = false;
    $Fehler .= "Konnte Spruch nicht laden";
  }
  $daten["SID"]      = $row["SID"];
  $daten["Spruch"]   = $row["Spruch"];
  $daten["anzeigen"] = $row["anzeigen"];
}
  else {
    // Werte vorbelegen
    $daten["Spruch"]   = "";
    $daten["anzeigen"] = 1;
  }
?>
<html>
<head>
  <title>SDA eintragen</title>
</head>
<body>
<?php
if (!$DatenOK){
  echo "<h1>$Fehler</h1>";
}
?>
<form action="<?php 
  echo $_SERVER["PHP_SELF"]; 
?>" method="GET">
<div align="center">

<p>
<textarea name="Spruch" rows="5" cols="80"><?php 
echo htmlentities($daten["Spruch"]); 
?></textarea>
</p>

Spruch anzeigen <input type="checkbox" name="anzeigen" 
value="checked" <?php 
  echo ($daten["anzeigen"] ? "checked" : ""); 
?>><p>

<?php
if (isset($daten["SID"])){
  // zum Ändern auch die SID übergeben
  printf('<input type="hidden" name="SID" value="%d">',
	 $daten["SID"]);
}
?>

<input type="submit" name="submit" value=" Alles OK ">

</div>
</form>
</body>

Schlußbemerkung

Dieses Beispiel erhebt keine Anspruch darauf, ein elegant programmiertes Script zu sein. So ist es z.B. ungeschickt, die Datenbankverbindungsdaten in jede Datei zu schreiben. Eine Änderung und man muß jede Datei ändern. Es bietet sich also an, diese Daten in eine zentrale Datei zu speichern. Bei der Gelegenheit könnte man dann auch gleich noch den ganzen Datenbankverbindungsaufbau mit auslagern. Noch geschickter wäre natürlich die Verwendung von OOP, wie sie in Kapitel 18 beschrieben wird.

Ähnliches gilt für das HTML-Layout der Adminseiten[*]. Hier steht es fest in jeder Datei; flexibler wären dann z.B. HTML-Header und HTML-Footer Dateien, die jeweils per include() eingebunden würden.

Ein Programmieren mit Copy and Paste geht zwar am Anfang schneller, aber sobald man etwas ändern muß, wird es aufwendig, weil jede Stelle geändert werden muß. Beim Ändern hätte man auch das Einfügescript kopieren und dann anpassen können, dann hätte man sich die Abfragen gespart. Allerdings wäre der Aufwand beim Einfügen eines weiteren Feldes größer gewesen. Bei dieser Lösung muß an 4 Stellen erweitert werden: bei der Datenprüfung, den beiden SQL-Anweisungen und dem HTML-Formular. Bei der Kopierlösung müßte man zwei mal 3 Stellen ändern.


Übung

Wieso habe ich bei der Auswahl von mehrere Sprüchen eine extra Funktion get_sprueche implementiert? Ich hätte doch auch mit Hilfe einer for-Schleife mehrmals die Funktion get_spruch aufrufen können.

Christoph Reeg