Moin!
Ich will mal grundsätzlich vorausschicken, dass ich großer Fan von "Test-Driven Design" (= TDD) bin. Manche übersetzen es leider auch als "Test-driven development" - das unterschlägt, dass einem TDD enorm beim DESIGN hilft - und das ist mehr, als "nur" Development.
So ein Testframework (ich bevorzuge SimpleTest) erleichtert mir die Arbeit enorm. Ich muß mich nicht darum kümmern, irgendwie selbst ein Skript zu schreiben, welches die Methoden der zu entwickelnden Klasse aufruft, damit ich prüfen kann, ob es funktioniert. Ich muß insbesondere bei verschachtelten Klassen nicht dafür sorgen, dass zuerst die inneren Klassen, dann die äußeren, alle instanziiert und mit korrekten Daten (wohlmöglich aus einer Datenbank) versorgt werden, um dann Input zu simulieren und Output auszugeben, der dann visuell manuell auf Korrektheit geprüft wird.
TDD bzw. SimpleTest liefert mir schon eine Vielzahl von Testmöglichkeiten, und insbesondere die Möglichkeit, dass ich Fake-Objekte in die zu testende Klasse hineingebe, die anstelle der echten Klasse prüfen, ob sie mit korrekten Parametern aufgerufen werden und die dann korrekte Rückgabewerte liefern, die ich einfach definiere. Diese Fake-Objekte verhalten sich äußerlich genauso, wie die echte Klasse, die sie ersetzen - und deren Verhalten teste ich separat in einem eigenen Testskript. So kann ich also insgesamt schön separiert entwickeln und Abhängigkeiten aus dem Weg gehen.
Der Design-Aspekt bei TDD ist folgender: Dadurch, dass ich mir zuerst überlege, in welcher Form ich eine zu testende Klasse bzw. Methode aufrufen will, und welche Rückgabewerte ich erwarte, setze ich die wirkliche Designentscheidung "Wie soll die Klasse nach außen wirken?" an den Anfang. Ich kann sogar, wenn ich diese Klasse derzeit noch nicht schreiben will, mir erstmal überlegen, wie sie nach außen wirkt, dann ein passendes MockObjekt als Fake schreiben, und erstmal die darauf aufbauende Klasse schreiben.
Und dadurch, dass ich einen einmal geschriebenen Test nicht mehr lösche (es sei denn, er ist nicht mehr zutreffend), sondern im Prinzip nur immer neue Tests hinzufüge, die auf neue bzw. neu entdeckte Anforderungen testen oder aufgetretene Fehler vom Wiederauftreten abhalten sollen, entsteht während der Entwicklungsarbeit ein komplexes, die Klassen teilweise sogar gut dokumentierendes Gerüst, welches am Ende eine hohe Softwarequalität gewährleistet.
Ich empfehle dir wirklich sehr, dass du dir mal die verlinkte Seite durchliest und dir das Framework herunterlädst. Die Seite ist leider auf englisch - sofern das ein Problem darstellt, sollte Google aber auch deutsche Tutorials zu TDD im Allgemeinen oder TDD mit PHP und SimpleTest im speziellen finden.
Aber jetzt erstmal ins Detail:
Meine Klassen orientieren sich immer an der zu erledigenden Aufgabe - nicht an der Datenbank.
Aber angenommen ich möchte nun alle Suchnamen eines Produktes auslesen, wie mache ich das dann am besten. Da muss es doch eine Verbindung geben. Oder meinst du damit das ich das ohne Klassen machen sollte und einfach ne Datenbank Abfrage starten soll, da die beziehung ja schon in der Datenbank besteht.
Wenn du Datenbankzugriff hast, dann bedeutet das automatisch, dass du eine Datenbankzugriffsklasse hast, die allen anderen Klassen, die das benötigen, DB-Zugriffe liefert. Nirgendwo sonst, außer in der DB-Klasse, tauchen irgendwelche mysql-Befehle auf. Wobei: mysql_*-Befehle tauchen sowieso besser nirgendwo mehr auf, nutze mysqli_* stattdessen! Die MySQLi-Erweiterung kann viel mehr, als das veraltete MySQL-Interface.
Diese Datenbankklasse sollte als Singleton ausgeführt werden, damit sichergestellt ist, dass dein Skript wirklich nur eine Instanz der DB-Klasse, und damit nur eine Connection zur DB, aufbaut, und darüber den gesamten Datenverkehr fließen läßt. Ausnahmen wären nur erlaubt, wenn du auf mehr als eine Datenbank zugreifen mußt - da wäre es dann aber je Datenbank ein Singleton.
Da die Datenbankklasse nur funktionieren kann, wenn die Datenbank existiert und funktioniert, ist die DB-Klasse in der Teststrategie als "Border-Klasse" (d.h. mit Kontakt zu nicht fakebaren externen Einrichtungen) zu separieren und abgetrennt von den "inneren" Klassen zu testen. Das SimpleTest-Tutorial erklärt dir näheres.
Die erste spannende Design-Entscheidung beim Programmieren: Wo stehen die SQL-Querys, die die DB-Klasse letztendlich ausführen soll.
Wenn das Datenmodell klein, überschaubar und garantiert nicht erweiterbar ist, könnte man die Querys auch noch mit in die DB-Klasse packen. Das ist aber keine gute Idee. Erstens bläht sich die Klasse dann mit Methoden auf, die im Zweifel doch immer mehr werden. Zweitens lassen sich, wie erwähnt, "Border-Klassen" nur schlecht testen, man muß die DB real laufen haben, und echte Querys kosten Zeit.
Deshalb ist es schlauer, davor eine Art Layer zu packen, der in der Realität die DB-Klasse verwendet (und zum Testen ein DB-MockObjekt, dass vorprogrammiert einfach mit realen Daten antwortet, dabei aber viel schneller ist, als ein echter Query - und auch keine laufende DB benötigt), und seinerseits die benötigten Querys kennt. So eine Layer-Klasse könnte beispielsweise dafür sorgen, dass auf Anfrage einer anderen Klasse ein User-Objekt entsteht und zurückgegeben wird, welches aus der Datenbank alle notwendigen Daten enthält (wie Username, Mailadresse, Rechte, etc.). Da es vermutlich höchst unterschiedliche Abfragen aus der Datenbank zu unterschiedlichen Themengebieten gibt, wird es mit Sicherheit auch mehr als eine solche Layerklasse geben.
Was noch typische Dinge sind, die sich für eine Klasse eignen:
-
Sessiondatenzugriff. Zwar könnte man auch $_SESSION direkt verwenden, aber diese Zugriffsart erfordert wieder, dass eine Session läuft, die erstmal gestartet werden muß, und die Drumherum benötigt - also: Border-Klasse für den Zugriff auf $_SESSION, und alles schön objektorientiert kapseln. Fühlt sich dann deutlich besser an.
-
Zugriff auf Browserdaten ($_GET, $_POST): Eines der unabdingbarsten Arbeiten mit diesen Daten ist die Validierung. Ich habe mir mal eine Validierungsklasse geschrieben, der ich ein Array mit den vorzunehmenden Tests übergebe, und das zu prüfende Datenarray (z.B. $_POST). Das Objekt erlaubt mir dann, abzufragen, ob die Daten valide sind (sind alle Felder vorhanden, fehlen welche, erfüllen alle Felder die Anforderungen), und liefert mir auch in jedem Falle in einem Ergebnisarray immer alle Felder, auf die ich teste, selbst wenn sie in den Ausgangsdaten nicht vorhanden sind. Das macht das Affenformular-Wiederausfüllen sehr praktisch, da ich nicht mehr testen muß, ob das Feld $_POST['name'] nun existiert, oder nicht (nichtexistente Arrayfelder liefern einen NOTICE-Fehler, und sowas vermeide ich grundsätzlich).
Es hängt von der Komplexität der Anwendung ab, ob nach den Abfragelayern noch eine weitere Schicht Klassen kommt, die die Abfragen zu sinnvollen komplexen Dingen zusammensetzt, oder ob die dadurch geschaffene Struktur schon ausreicht, dass man im Hauptprogrammteil einfach nur noch die jeweils notwendigen Klassen instanziiert, mit Daten versorgt, und das Resultat dann der Template-Engine rüberreicht und die Ausgabe veranlaßt.
also bevor ich deine Antwort gelesen habe, habe ich folgendes geschrieben:
class prod_such_zuo {
private $array_suchname_ids = array();
function prod_such_zuo($produkt_id) {
$erg = mysql_query(
"SELECT suchname_id " .
"FROM prod_such_zuo " .
"WHERE produkt_id='$produkt_id'"
);
while($row = mysql_fetch_object($erg))
$this->array_suchname_ids[] = $row->suchname_id;
}public function get_array_suchname_ids(){
return $this->array_suchname_ids;
}}
>
> Macht das Sinn? Ist das eventuell das was du gemeint hast mit dem speichern in einem Array? Geht es besser?
Es ist schlecht, den DB-Query direkt in der Klasse zu haben. Nirgendwo wird, falls noch nicht geschehen, die DB-Connection gestartet - sowas würde eine DB-Singleton-Klasse, die du dieser prod\_such\_zuo-Klasse übergibst, im Zweifel selbsttätig erledigen.
Zweitens würfelst du PHP4- und PHP5-Objektorientiertheit durcheinander. In PHP 4 mußte der Konstruktor als Funktion den Namen der Klasse tragen, und es gab keinen Destruktor. In PHP 5 heißt der Konstruktor als Funktion immer "\_\_construct", und der Destruktor "\_\_destruct" (dass die PHP4-Variante noch funktioniert, liegt an der Abwärtskompatibilität - so zu programmieren ist aber keine gute Idee mehr, PHP 4 ist offiziell tot seit dem 8.8.2008).
Drittens ist die Frage: Was willst du mit dieser Suchnamenzuordnung bezwecken? Wenn du in der DB eine n:m-Zuordnung hast, so wirst du dennoch selten in EINEM Objekt die gesamte Menge n der einen Tabelle abfragen und deshalb gezwungen sein, auch noch die gesamte Menge m der anderen Tabelle mit auszulesen.
Üblicher erscheint mir stattdessen, dass ein "Hauptobjekt" aus der Tabelle n ausgelesen wird, und alle damit tatsächlich (über die n:m-Hilfstabelle) verknüpften Einträge aus m werden in dieses Objekt ebenfalls hineingetan, z.B. als Array.
Und wenn sich an diesem Objekt durch Benutzerinteraktion irgendetwas verändern sollte, so weiß das Objekt auch direkt, wie es diese Änderung wieder in die DB-Konstruktion der n:m-Relation speichern kann, ohne dass du selbst dich auf dieser Ebene der Applikation mit den Details rumärgern mußt.
Und schon sparst du dir zwei Klassen und viele unnütze Objekte.
- Sven Rautenberg
--
"Love your nation - respect the others."