Andreas Korthaus: DAO / MVC-Design in (PHP-)Webapplikationen - sinnvoll?

Beitrag lesen

Hallo Sven!

Jetzt habe ich unlängst einen Webshop geschrieben. Getreu dem Motto "beim nächsten Shop ist alles anders" wollte ich hierbei endlich mal konsequent auf Klassen setzen.

Was heißt bei Dir "konsequent"? Meinst Du richtig  mit OO Analyse, Entwurf, schön alles mit UML in Objekten/Klassen modelliert? Oder für das was Du früher mit Funktionen gemacht hast? Denn das war es was ich bisher gemacht habe. Ich verwende Smarty, ein paar PEAR Packages und ein paar eigene kleine Klassen, und dachte so, jetzt ist es auch anständig OOP... naja, denkste. Ich hatte so mitbekommen dass "PHP nicht Java ist" und dass es durchaus sinnvoll ist auch prozedural zu programmieren, und ich dachte OK, dann ist ja alles prima...
Naja, heute stehe ich vor einigen Problemen, alleine aufgrund der Komplexität des Projektes, welches sicherlich inzwischen über 1000 Din A4 Seiten Code füllt. Erst nachdem ich die Probleme dieses Ansatzes am eigenen Leib erfahren habe, habe ich wirklich den Sinn und die Vorteile einer wirklich durchgängig objektorientierten Entwicklung verstanden (wobei dem ja bekanntermaßen in PHP Grenzen gesetzt sind).
Ich werde versuchen alles in Objekte zu packen, und für alle Objekte eine DataAccessObject Klasse basteln, die den SQL-Code enthält, und für das laden der Daten aus der DB, und das Schreiben in selbige verantwortlich ist. Dadurch hoffe ich dass es mir gelingt die Business Logik von der Datenhaltung zu trennen.

Und es hat sich IMO gelohnt, weil dadurch viele Dinge einfacher werden, weil sie tatsächlich voll gekapselt sind. Es gibt eine Klasse, die für Datenbankkommunikation zuständig ist.

Meinst Du sowas wie eine Datenbank-Abstraktion(verwendest Du sowas? welche genau?)?

Diese wird (in einer separaten Datei, der Übersichtlichkeit wegen) erweitert um eine Klasse, die für die Session-Funktionen zuständig ist.

Was heißt "erweitert"? Doch keine Vererbungs-Beziehung, oder?

Außerdem gibts (von extern angeheiratet) eine Klasse für die Templates und eine zum Mailen.

Ich verwende hierfür jeweils Smarty. Das ist hervorragend geeignet.

Erst diese Templateorientierung macht es jetzt wirklich möglich, dass ich im Shop prinzipiell total mit virtuellen URLs operieren kann.

Was sind denn "Virtuelle URLs"? Meinst Du also keine direkte Zuordnung auf Scripte? Aber was genau hat das mit den Templates zu tun? Ich verwende auch Templates, aber die Links die das Template enthält zeigen im Prinzip immer auf ein Script, nach dem unten genannten Schema. Das Script kann dann natürlich abhängig von Parametern verschiedene Templates laden

mod_rewrite ist toll dafür.

Was macht denn Dein mod_rewrite, bei mir folgendes:

RewriteEngine On
RewriteCond %{REQUEST_URI}  !^/images|java|js|styles/.*
RewriteCond %{REQUEST_URI}  !^/main.php.*
RewriteCond %{REQUEST_URI}  !^/favicon.ico
RewriteCond %{REQUEST_URI}  !^/robots.txt

RewriteRule ^(.*)$            /main.php?uri=$1    [QSA]

Und erst dieses Operieren mit virtuellen URLs löst mich von der bisherigen Denkweise, dass eine URL gleich einem Skript ist, und das Skript dann bitteschön die Datenbank abfragen möge, den Preis (womöglich an drei verschiedenen Stellen unterschiedlich) berechnet und dann, noch direkt im Skript, die Ausgabe einer Seite vornimmt.

Hm. Also mein aktueller Ansatz ist dass das main.php Script zunächst die Konfiguration und notwendige Libs/Klassen läd(PEAR, DB, Smarty, Auth....), den Request auseinander nimmt und entsprechend aus einem Modul-Verzeichnis(oder besser nenne ich es Plugin), das angegebene Script läd. Das Script kann dann machen was es will, entweder es hat nur eine Aktion und macht dann genau das, oder es enthält einen kleinen "Controller", also einen kleinen Switch, was genau bei welchem Parameter passieren soll. Meist werden dann Klassen der Business-Logik geladen, Objekte instanziert und irgendwelche Methoden ausgeführt.

/[modulname]/[scriptname]

So in der Art arbeite ich derzeit im Prinzip auch (mag ja sein, dass im Laufe der Zeit und mit der Erfahrung sich das noch weiter in Richtung dem "richtigen" Programmieren ändert).

Was wäre denn "richtigen" Programmieren?

Allerdings regelt mod_rewrite bei mir im Vorfelde schon ein paar Kleinigkeiten. Nur direkt im Shopbereich wird die volle Shopfunktionalität benötigt. Außerhalb des Shops wird zwar per Template eine Warenkorb-Zusammenfassung gezeigt, aber das geschieht mit einem wesentlich leichteren Skript, welches nur eben die Session-Daten dort einträgt, und ansonsten nur das gewünschte Seiten-Template durchnudelt.

Hast Du dann nur ein shop.php welches die Aktionen als "action" Parameter bekommt? Und dann im Script ein Switch der das geforderte Script läd? Was ist bei Dir mit globalen Daten, also definierten Konstanten, Parsen von Config-Daten, Einbinden der DB... Klasse, wie bindest Du das auf jeder Seite ein?

So kann ich mich jeweils der Datei zuwenden, an deren Aufgabenabarbeitung ich etwas ändern möchte, oder ich nehme mir den Aufgabenverteiler vor, der anhand der angeforderten URL zum richtigen Modul weiterleitet.

Ja, aber ist das der richtige Ansatz? Inzwischen bin ich der Auffassung, dass man Funktionalitäten möglichst in Methoden packen sollte, die entsprechende APIs haben und damit _wirklich_ unabhängig vom Rest gesehen werden. Denn die Funktionalität mag in einer eigenen Datei liegen, aber wenn die einmal eingebunden ist ist das ganze im Prinzip nur noch eine große Datei. Wenn Du aber eine Methode mit gut dokumentierter API hast, dann wird das langfristig sicher große Vorteile haben, da die Implementierung wirklich weitgehend unabhängig vom Rest der Applikation gesehen werden kann.

Für mich sieht es rein logisch jedenfalls im Moment so aus, dass der Webserver im Prinzip wirklich nur datendurchleitende Funktion hat. Alle Requests kommen zentral an einem Skript an, welches seinerseits die Ablauflogik implementiert hat.

OK, also wie bei mir.

Diese Ebene greift dafür auf die einzelnen Klassen (DB, Session, Template, Mail) zu, um Mid- bis Low-Level-Operationen durchzuführen.

Meinst Du jetzt das zentrale Script? Was führt denn das für Aktionen aus? Ich dachte das machen die Scripte die hier entsprechend dem Request geladen werden?!

Das Interface ist standardisiert.

Interface wozwischen?

Die Klassen regeln dann die tatsächliche Ausführung, typischerweise durch ein paar als privat anzusehende (aber in PHP 4 ja nicht als solche deklarierbaren) Methoden, die intern aufgerufen werden.

Privaten Methoden immer ein "_" voranstellen, habe ich gelesen ;-)

Verstehe jetzt allerdings nicht genau wo Du gerade bist. Bist Du schon im eingebundenen Script, oder noch im zentralen? Ich habe Anfangs auf die Request-Parameter noch über Methoden zugegriffen, also die Parameter in einer Klasse gekapselt, aber das habe ich sehr schnell wieder verworfen, $_* Variablen sind in PHP nun mal da und daran wird sich so schnell nichts ändern habe ich beschlossen - der Einfachheit halber.

Im Grund genommen zerfällt eine größere Anwendung ja typischerweise in mehrere Schichten: Präsentation gegenüber dem User, Verarbeitung und Datenspeicherung. Wobei es natürlich noch Zwischenschichten oder Dopplungen geben kann.

Ja. Und das A und O ist dass sich nur 2 Schichten die direkt übereinander liegen unterhalten können. So nett so Dinge wie $template->assign($db->getAll('SELECT....')) auch sein mögen, gerade das darf man nicht machen.
Außerdem sollen sich die Schichten über APIs unterhalten und möglichst nichts über die Implementierung hinter der API wissen(müssen).

<img src="http://www.phppatterns.com/ezimagecatalogue/catalogue/variations/12-400x500.gif" border="0" alt="">

http://www.phppatterns.com/index.php/article/articleview/18/1/1/ (wenn es auch nicht 100%ig hinkommt, aber es geht ja ums Prinzip.)

Ach ja, was vergaß ich: Am Anfang werden natürlich auch noch irgendwelche schönen POST- oder GET-Werte einbezogen.

Wo werden die einbezogen?

Indem ich mich von der klassischen Festlegung "URL=Skript" löse, kann ich mich auch von der Gedankenfalle "ECHO ist die Präsentation" lösen. Das PHP-Skript hat dann gerade nicht mehr die Aufgabe, für jedes kleine Fitzelchen Darstellung auf dem Browser zu sorgen, sondern es hat die Aufgabe, die Template-Engine mit den Daten zu versorgen, die angezeigt werden sollen. Auf diese Weise verschmelzen Template-Engine und Browser zu einer logischen Einheit, die ich durch die Präsentationsschicht "Template-Datei" programmieren kann, ohne mich zu diesem Zeitpunkt auf die zugrundeliegende Berechnungslogik oder Datenquelle konzentrieren zu müssen.

Ja, sehe ich genau so.

Genau das Gleiche geschieht auch mit der Datenbank-Klasse. Indem ich einfach nur standardisiert die Methode "hole Artikelliste" abfrage und ein Array der gefundenen Artikel mit allen gespeicherten Eigenschaften zurückerhalte, werde ich von für Anfänger verlockenden Möglichkeiten mit direkt codeausgebenden while (mysql_fetch_array())-Schleifen ferngehalten.

Ja, das ist sehr wichtig. Aber hast Du diese Methoden in einer einzigen Datenbank-Klasse für die ganze Applikation stehen? Wieso nicht in einer Klasse pro Objekt welches Daten in der DB speichern/lesen können muss?

Ich stelle mir das ganze eigentlich eher in dieser Richtung vor: http://www.phppatterns.com/index.php/article/articleview/25/1/1/

Im Übrigen fand ich folgenden Artikel zum Thema lesenswert(und auch einige der verlinkten Seiten), auch wenn ich nicht so 100% mit übereinstimme, aber es hat mich in der Annahme bestärkt dass die komplexen Controller der Struts-Clone(wie Phrame, was in den IMO ebenfalls sehr lesenswerten, verlinkten Artikeln aus php|architect favorisiert wird) auch nicht wirklich das non-plus-ultra sind.

Viele Grüße,
Andreas