Herangehensweise

Wie besprochen, wollen wir die Programmieraufgabe mit Hilfe von Objekten lösen. Alle Content-Elemente sollen in einem Datencontainer gelagert und erst bei Benutzung (Darstellung) "ausgepackt" werden.

Dazu benötigen wir zwei Objekte: ein Objekt "Rubrik" und ein Objekt "Nachricht". Kurz zur Erinnerung: Objekte sind besondere Datenstrukturen; sie besitzen Eigenschaften und Methoden. Würden wir beispielsweise ein Auto als Objekt ansehen, könnte eine Eigenschaft seine Farbe (rot) und eine Methode "Beschleunigen" sein. Merke: Eigenschaften können gelesen oder geschrieben werden, Methoden sind einzelne Code-Blöcke, die irgendeine Funktionalität haben und Eigenschaften verändern können. Methoden können Parameter übergeben bekommen oder Daten zurückliefern.

In PHP werden Objekte als Klassen über das Sprachkonstrukt class definiert; ihre Eigenschaften werden mit var eingeleitet. Methoden werden wie gewöhnliche Funktionen innerhalb des Klassenblocks geschrieben. Diejenige Methode, die den gleichen Namen wie die Klasse trägt, wird als Konstruktor bezeichnet und wird automatisch aufgerufen, wenn wir uns eine Instanz der Klasse erzeugen.

Container-Konstruktion

// Objekt "Rubrik"
class Rubrik {
   // Eigenschaften
   var $rubrik_id;
   var $bezeichnung;

   // Konstruktor
   function Rubrik() {}

   // Daten lesen
   function Lesen() {}
}

// Objekt "Nachricht"
class Nachricht {
   // Eigenschaften
   var $artikel_id;
   var $datum_uhrzeit;
   var $ueberschrift;
   var $anreisser;
   var $volltext;
   var $rubrik_id;
   var $rubrik;
   var $suchbegriff;
   var $_anzahl;

// Konstruktor
function Rubrik() {}

// Daten lesen
function Lesen() {}

Die Eigenschaften beider Objekte bieten den Platz für alle Datenfelder einer Rubrik bzw. einer Nachricht. Jedes Objekt besitzt neben seinem Konstruktor eine Methode Lesen(), die später mit der Datenbank kommunizieren und die Daten entsprechend aufbereiten wird.

Erster Container: Rubriken

Wenden wir uns als erstes den Rubriken zu - gewünscht war eine Liste aller verfügbaren Rubriken. Da wir nicht wissen, wie viele Rubriken es gibt - Onlineredakteure reden nicht mit Programmierern - können wir im Vorfeld also keine festgelegte Zahl von Rubrik-Objekten instanzieren. Stattdessen lassen wir uns eine Liste der Rubriken geben: die Lesen()-Methode soll daher ein Array von Rubrik-Objekten lesen.

Als erstes brauchen wir den Konstruktor. Seine Aufgabe besteht darin, die übermittelten Daten als Objekteigenschaften zu speichern:

// Konstruktor
function Rubrik($rubrik_id, $bezeichnung) {
   $this->rubrik_id = $rubrik_id;
   $this->bezeichnung = $bezeichnung;
}

Merke: alle Variablen innerhalb einer Klassenmethode sind lokal - wie bei herkömmlichen Funktionen auch. Möchte man auf die Eigenschaften einer Klasse zugreifen, muss der Klassenname vorangestellt werden. Handelt es sich um die "eigene" Klasse, wird $this verwendet. Der Pfeiloperator trennt den Klassennamen von der Eigenschaft (oder der Methode) ab. Letztere darf übrigens auf keinen Fall mit einem $ beginnen, sonst wird sie als variable Variable interpretiert - was in einem anderen Fall durchaus sinnvoll einzusetzen ist, führt in den meisten Fällen aber nur zu schwer nachvollziehbaren Fehlern. Aber dazu später mehr.

Der zugehörige Code für Lesen() innerhalb von Rubrik sieht so aus:

// Rubrikdaten lesen
function Lesen() {
   global $dbh;

   $ret_val = array();

   // SQL an Datenbank senden
   $query = mysql_query("select rubrik_id, bezeichnung from rubrik order by bezeichnung asc", $dbh);
   // Ergebnis in Objekt wandeln...
   while($row = mysql_fetch_array($query)) $ret_val[] = new Rubrik($row["rubrik_id"], $row["bezeichnung"]);

   // ...und zurueckliefern
   return $ret_val;
}

Da wir eine ganze Liste von Objekten geliefert haben wollen, erstellen wir uns zunächst ein leeres Array ($ret_val). Anschließend wird die Datenbank befragt und das gewonnene Recordset in ein Objekt umgewandelt: unser $ret_val wird mit immer neuen Instanzen des Rubrik-Objektes befüllt und zum Schluss via return an den aufrufenden Code geliefert. Sollten keine Rubriken vorhanden gewesen sein (weil der Online-Redakteur gerade Urlaub hatte), dann bekommen wir ein leeres Array zurück. Noch praktischer wäre natürlich, in diesem Fall ein false zurück zu liefern. Damit könnte zusätzlich geprüft werden, ob tatsächlich eine Liste empfangen wurde:

If($rubriken = Rubrik::Lesen()) {
  ..
}

Mit dem folgenden Code lesen wir die Rubrikdaten als Objektliste und geben sie anschließend aus:

$rubriken = Rubrik::Lesen();
foreach($rubriken as $rubrik) {
  echo $rubrik->bezeichnung . "\n";
}

Moment! Hieß es nicht, man müsse den Pfeiloperator verwenden, um auf Methoden oder Eigenschaften einer Klasse zuzugreifen? Im Prinzip ja; allerdings wurde der ::-Operator im Beispiel aus gutem Grund verwendet, denn es existiert ja zum Zeitpunkt des Aufrufs noch gar kein Objekt. Via :: können wir jedoch auf Klassenmethoden zugreifen, von deren Klasse es keine Instanz gibt. Ganz wichtig: da es noch kein Objekt gibt, dürfen auch keine Objektvariablen oder der Ausdruck $this verwendet werden. Sollten dennoch Objektvariablen gesetzt werden, gehen ihre Inhalte beim Rücksprung aus der Klassenmethode unweigerlich verloren.

Der Aufruf von Lesen() liefert und das gewünschte Array mit den Rubrikinhalten zurück, die wir in einer foreach-Schleife ausgeben. Et voilá: Aufgabe 1 wurde gelöst.

Zweiter Container: News

Nachdem wir das grundlegende Know-how erlernt haben, gehen wir bei den News ganz ähnlich vor: auch hier verwenden wir eine Methode Lesen(), um eine Liste aller derjenigen Nachrichten zu erhalten, die auf unsere Suchkriterien passen. Diese übermitteln wir natürlich ebenfalls in Objektform: dazu wird ein "Muster" vom Objekttyp Nachricht instanziert und mit denjenigen Eigenschaften beschrieben, welche die zu liefernden Objekte erfüllen müssen. Mit diesem Trick können wir Aufgabe 2 und drei gleich auf einen Streich lösen und gewissermaßen "zwei Fliegen mit einer Klasse" erschlagen. Die Eigenschaft $_anzahl (zur besseren Unterscheidung mit einem Unterstrich eingeleitet) soll die Anzahl der passenden Nachrichten liefern:

// Vorlage erstellen
$vorlage = new Nachricht();
// Suchkriterium setzen
$vorlage->suchbegriff = "ferrari";
// ggf. weitere Kriterien
...

$nachrichten = Nachricht::Lesen(&$vorlage);
echo $vorlage->_anzahl . " Treffer<br><br>";
foreach($nachrichten as $nachricht) {
  echo $nachricht->ueberschrift . "<br>";
}

Als erstes instanzieren wir eine $vorlage und weisen der Eigenschaft $suchbegriff einen Wert zu. Diese $vorlage übergeben wir der Lesen-Methode der Nachrichtenklasse als Parameter. Auffällig ist dabei das "kaufmännische Und". Es weist den PHP-Interpreter an, das Originalobjekt $vorlage als Parameter zu übergeben (call by reference). PHP verwendet bei der Parameterübergabe jedoch grundsätzlich einen call by value, d.h. es wird die Kopie des Parameters übergeben. Wird innerhalb der Funktion oder der Klassenmethode der Wert des Parameters verändert, hat das nur Auswirkungen innerhalb der Funktion - Parameter und Funktionsvariable sind grundsätzlich lokal.

Innerhalb der Lesen()-Methode müssen wir die zuvor gesetzten Suchkriterien auswerten und in das SQL-Statement integrieren, welches die passenden Datensätze aus der Datenbank liest. Um die Anzahl der benötigten IF-Konstrukte (und die der Codezeilen) möglichst klein zu halten, verwenden wir ein Array namens $params, dem wir alle Suchkriterien in vorgefertigter SQL-Syntax zuordnen (so sie gesetzt wurden). Vorher prüfen wir, ob überhaupt eine Vorlage übergeben wurde:

function Lesen($muster="") {
  global $dbh;

  // Grundlegendes SQL-Statement
  $sql = "select n.*, r.rubrik_id, r.bezeichnung from nachricht n, rubrik r where n.rubrik_id = r.rubrik_id";

  // "Muster-Objekt" erhalten?
  if(is_object($muster)) {
    $params = array();
    // Suchkriterien in Array speichern...
    if($muster->rubrik_id!="") $params[] = "n.rubrik_id = " . $muster->rubrik_id;
    if($muster->artikel_id!="") $params[] = "n.artikel_id = " . $muster->artikel_id;
    if($muster->suchbegriff!="") $params[] = "(n.ueberschrift like '%" . addslashes($muster->suchbegriff) . "%' or n.anreisser like '%" . addslashes($muster->suchbegriff) . "%' or n.volltext like '%" . addslashes($muster->suchbegriff) . "%')";

    // ...und ggf. an SQL-Statement anhaengen
    if(count($params)>0) $sql .= " and " . implode(" and ", $params);
  }
  // SQL-Statement abschliessen und an DB senden
  $sql .= " order by n.rubrik_id, n.zeitstempel desc";
  $query = mysql_query($sql, $dbh);

  // Anzahl der Ergebnisse merken
  $muster->_anzahl = mysql_affected_rows($dbh);
  // Ergebnisdaten in Objekt wandeln...
  while($row = mysql_fetch_array($query, MYSQL_ASSOC)) $ret_val[] = new Nachricht($row);

  // ...und zurueckliefern
  return $ret_val;
}

Wie schon bei den Rubriken erstellen wir ein Rückgabe-Array von passenden Nachrichten-Objekten. Da es hier diesmal eine ganze Reihe von Eigenschaften zu übergeben gilt und wir natürlich unnötige Tipparbeit sparen wollen, übergeben wir diesmal gleich einen kompletten Datensatz als assoziatives Array und lassen den Konstruktor die eigentliche Arbeit übernehmen:

// Konstruktor
function Nachricht($row="") {
  // Ergebnis-Array erhalten?
  if(is_array($row)) {
    // Alle Felder durchgehen und Klasseneigenschaften zuweisen
    while(list($key,$value) = each($row)) $this->$key = $value;
  }
}

An dieser Stelle setzen wir bei der Eigenschaftenzuweisung ganz einfach variable Variablen ein - denn das übergebene assoziative Array bringt die Namen der Eigenschaften ja passenderweise als Key gleich mit.

Mit diesem Code haben wir Aufgabe 2 gelöst; es müssen nur die Suchkriterien in der Vorlage richtig gesetzt werden. Sollten keinerlei Kriterien gesetzt werden, erhalten wir alle Nachrichten. Das gleiche gilt für Aufgabe 3 - schließlich ist die Eigenschaft $nachricht_id ja ebenfalls ein Suchkriterium.

Performance

Man könnte einwenden, dass der Code insofern nicht performant ist, als dass er auch solche Eigenschaftsdaten liefert, die möglicherweise gar nicht verwendet werden. In der Regel ist das aber zu vernachlässigen, da PHP die Daten eh im Cache-RAM zwischenpuffert. Bei highload-Sites dagegen sollte man sich dagegen vielmehr fragen, inwieweit PHP das richtige Instrument dafür ist.

Ausblick und Verbesserung

Die Nachrichtenklasse könnte um einige Zusatzfeatures erweitert werden:

Weitere Verbesserungsvorschläge inklusive Beispiel bringt die "Laderampe" im Kapitel Erweiterung.