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

Hallo!

Ich habe mich die letzten Tage durch einige sehr interessante Webseiten/Artikel gearbeitet, unter anderem:

http://www.phppatterns.com/
http://www.phparch.com/issue.php?mid=9
http://www.phparch.com/sample.php?mid=10
http://php.weblogs.com/ADODB
http://www.phpconference.com/downloader/downloader.php?CatID=0&NewsID=367
http://talks.php.net/
...

Kann ich nur empfehlen, unglaublich viele sehr interessante Artikel über "professionelle Webapplikationen mit PHP" wenn ich es mal so umschreiben will.
Und zwar stehe ich gerade vor dem ersten größeren "Refactoring" einer größeren eigenen PHP-Applikation wo aufgrund von Zeitdruck das Design ein wenig gelitten hat - um es mal so auszudrücken ;-)
Von der Uni her musste ich mich die letzte Zeit auch ein wenig mit Java beschäftigen, und da kommt man irgendwann zwangsläufig mit Dingen wie J2EE in Kontakt, und auch mit interessanten Frameworks für Webapplikationen wie Jakarta Struts von der Apache Foundation und anderen. Jetzt war ich immer davon ausgegangen, dass es sowas für PHP nicht gibt, aber dank der obigen Artikel habe ich gemerkt, dass es sowas sehr wohl gibt, und zwar wie Sand am Meer (Phrame, Mojavi, php.MVC um nur ein paar bekanntere zu nennen). Diese Frameworks versuchen zumeist die Konzepte von Struts in PHP zu übersetzten.

Und ich frage mich jetzt - wie sinnvoll ist das? Denn PHP ist ja nicht Java, und MVC kommt ja eigentlich von der GUI-Programmierung, was ja nicht direkt mit Webapplikationen vergleichbar ist (z.B. haben GUIs keinen Webserver der von alleine das angeforderte Script starten kann...). Nach Lesen einiger interessanter Diskussionen haben sich 2 Meinungen herauskristallisiert, die einen finden das ganz prima mit Framewoks wie Phrame zu arbeiten, die anderen sagen das ist Quatsch, man brauche keinen Controller und kein Action-Mapping, dafür hätten wir ja den Webserver...

Ich mache zur Zeit eine Art Mischmasch, ich verwende ein Script welches alle Libs und Einstellungen läd, welches per Mod-Rewrite bei jedem Request geladen wird und entsprechend dem eigentlichen Request dann ein Modul(Verzeichnis) und dort ein Script läd, ein Request sieht etwas so aus:

/[modulname]/[scriptname]

und alles weiter kommt dahinter als Parameter. (Wobei ich auf mod_rewrite aufgrund der Größe des Moduls lieber verzichten würde...)

Das Script was dann geladen wird weiß was zu tun ist und läd dann notwendige Klassen und "macht dann irgendwas" damit.
Was ist Eure Meinung dazu? Hat es einen Sinn wie bei Struts oder Phrame zentral alle "Actions", also alle Requests die möglich sein sollen zentral zu definieren? Ich finde das sehr unpraktisch, vor allem auch weil ich eine eigene Plugin-Architektur habe, wo Plugins kpl. in einem eigenen Ordner liegen und nicht in allen möglichen Verzeichnissen der Applikation Änderungen vornehmen sollen. Man könnte zwar auch per php.ini per auto-prepend(oder wie das heißt) ein Script  vor jedem Request laden, welches dann die Konfigurationen... durchführt, also dass über den Webserver immer echte Pfade aufgerufen werden, nur ist das dann etwas schwierig wenn die ganzen Plugins mit allen Unterverzeichnissen innerhalb es doc-root liegen, das mag ich z.B. nicht so.

Wie seht Ihr das, ist ein MVC-Design in einer PHP-Webapplikation sinnvoller?

Und noch eine Frage, ich verwende zwar eine Datenbank-Abstraktion, aber man hört/liest oft, man solle noch eine DAO(Data Access Object) - Schicht zwischen Datenbank-Abstraktion und der eigentlichen Business-Logik haben. Was haltet hier hiervon?

Mal am Beispiel eines Produktkataloges, sagen wir mal ich habe eien Tabelle "products", die dann die Grunddaten der Produkte enthält, z.B. HerstellerID, Preis und Titel. Dann haben wir noch spezielle Tabellen, sagen wir mal für "computer", die dann produktspezifische Daten enthält.

Jetzt sagen wir mal ich habe ein HTML-Formular welches die Daten eines Computers enthält, sowohl die aus der Tabelle products als auch aus computer. Dieses Formular schicke ich jetzt ab.

Bei mir erzeugt das dann so einen Request: /products/edit?prod_id=123&typ=2&price=22,99...

Das führt dazu dass das "edit.php" Script aus dem Modul-Verzeichnis "products" geladen wird (include).
Dies hat dann einen Switch und weiß dass bei typ=2 die Klasse "Computer" geladen wird, die von "Products" erbt.

Das Script edit.php macht dann also sowas:
include('Cpmputer.class.php');
$computer = new Computer($_REQUEST['prod_id']);

und dann

$computer->setPrice($_REQUEST['price']);
$computer->setCPU($_REQUEST['cpu']);
...

und am Ende

$computer->storeData();

Diese Methode muss ich dann überall einbauen, da sich das immer unterscheidet, und die sieht dann so aus:

function storeData() {
  global $db;
  $sql = "UPDATE
            products
          SET
            price=$this->price,
            ...
          WHERE
            prod_id=$this->id;
  $db->query($sql);
  $sql = "UPDATE
            computer
          SET
            cpu=$this->cpu,
            ...
          WHERE
            prod_id=$this->id";

}

So wird das ganze dann persistiert (ist jetzt natürlich etwas vereinfacht, ohne Validierung...).

Meine Frage ist jetzt, macht Ihr das vergleichbar, oder habt Ihr tatsächlich noch eine DAO-Schicht dazwischen, wie es J2EE ja z.B. durch Beans oder EJBs vorsieht?

Manchmal wird ja auch eine extra Klasse für jedes Objekt gebaut, die für das Speichern zuständig ist, also wäre die Methode storeData() nicht in "Computer" integriert sondern z.B. in ComputarDataStorage, wo  sich diese Methode dann die Daten über getPrice()... Methoden dann die Daten holt und in die DB schreibt. Findet Ihr das sinnvoll?
Genauso beim Lesen von Daten, würdet Ihr das auch von der eigentlichen "Business Logik" Klasse, also "Computer" abkapseln und hier jeweils eine eigen Klasse schreiben die die Daten aus der DB holt und in die Klasse "Computer" schreibt?

Viele Grüße
Andreas

  1. Hallo Andreas,

    Ich kann nicht behaupten, dass ich allzuviel von Deinem Posting verstehe , aber trotzdem vielen Dank fuer die Links, das sieht ja sehr spannend aus.

    Dieter

    1. Hallo Dieter!

      Ich kann nicht behaupten, dass ich allzuviel von Deinem Posting verstehe , aber trotzdem vielen Dank fuer die Links, das sieht ja sehr spannend aus.

      Was verstehst Du nicht? Kommst Du nicht aus der "Java-Welt"? MVC(Model-View-Controller) kommt glaube ich ursprünglich von Smalltalk, und ist heute in Java-Applikationen wohl ein sehr verbreitet Applikations-Design, auch als Model 2 bekannt.

      Eine IMHO wirklich sehr gelungene Einfpührung vor allem für PHP findest Du in dem Artikel "An introduction to the Model-View-Controller Pattern", in der Gratisausgabe von php|architect (May-2003) http://www.phparch.com/issue.php?mid=9. Das wird in dem anderen Gratis-Artikel den ich verlinkt habe fortgesetzt, und auf der phppattern.com Seite findest Du noch einige Artikel dazu, bezogen dessen Umsetzung in PHP und Diskussionen über den Sinn eines Controllers(das C aus MVC) in PHP Webapplikationen. Aber ich habe nach wirklich tagelangem lesen (wenn man einmal weiß wonach man suchen muss findet man _Unmengen_ an Informationen) für mich immer noch nicht so ganz genau geklärt ob das denn nun wirklich Sinn macht oder nicht. Der Vorteil ist wohl, dass man sich dadurch eine wirklich saubere Architekur aufzwingt, aber für den Preis von weniger Flexibilität und mehr Overhead. Das Problem ist nur, dass man als "Anfänger" dazu neigt die Flexibilität zu mißbrauchen. Daher interessieren mich Eure Meinungen zum Thema.
      Es steht außer Frage das man Logik und Präsentation trennen soll, aber MVC geht ja noch weiter, die Frage ist ob das sein muss, oder besser gesagt, wie implementiert Ihr einen Controller, denn irgendwo ist sowas durchaus notwendig, nur ist eben fraglich wie geanu der funktioniert, also ob man sich etwa nur auf den Websertver verlässt, der die Script direkt startet, oder ob man ein komplexes "Action-Mapping" verwendet, was bedeutet dass man vorher jede Aktion die in der Anwendung von außen ausgeführt werden kann in einer Konfigurations-Datei beschreibt, und genau festlegt was genau bei der Aktion passieren soll. Und es ist noch fraglich wie man dann die Akition ausführt, einfach ein Script das dann Klassen läd und eine Methode ausführt, oder ob das ganze aus dem Controller heraus passiert, also komplett objektorientiert.

      Also im Augenbblick bin ich eher der Meinung das man so einen komplexen Controller nicht benötigt, aber man lernt sehr viel wenn man sich mit diesem "Design Pattern" beschäftigt, wie mit den vielen anderen unter http://www.phppatterns.com/index.php/article/archive/1/

      Das ist die erste vernünftige Quelle im vorherigen Posting, wo es mal darum geht nicht zu erklären wie etwas funktioniert(Referenz), sondern wie man etwas "vernünftig" implementiert. In PHP ist das alles AFAIK recht neu, in anderen Sprachen wie Java gibst darüber Bücher ohne Ende, wobei es hier ja weniger um den Code und dessen Implementierung in einer bestimmten Sprache geht, sondern eigentlich um die abstrakte Lösung eines Programmierproblems. Nur empfinde ich es schon als hilfreich wenn das ganze unter dem Gesichtspunkt der Implementierung in PHP gesehen wird. Denn man sollte nicht Java 1:1 in PHP übersetzen, da hier unterschiedliche Voraussetzungen gelten.

      Viele Grüße,
      Andreas

  2. Moin!

    Ich will das Posting mal mit einer Geschichte beginnen. :)

    Ich habe gerade mal wieder in den Quelltext eines ungefähr ein Jahr alten PHP-Formmailers reingeschaut, den ich geschrieben hatte. Das Skript ist wirklich nicht schlecht, es hat eine einfache Template-Engine, ist gesichert gegen Spam-Attacken (mailt als nicht wild in der Gegend umher, sondern nur an definierte Adressen), garantiert auch, dass keine Formularfelder verloren gehen - und wenn alle Stricke reißen, schickt es eben einfach nur einen Dump des Formulars weg.

    Aber: Das Skript basiert komplett auf Funktionen, die direkt in der einzigen Datei enthalten sind, es ist irgendwie unübersichtlich und gefällt mir rein ästhetisch jetzt nicht mehr. Aber es macht genau, was es soll.

    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. 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. Diese wird (in einer separaten Datei, der Übersichtlichkeit wegen) erweitert um eine Klasse, die für die Session-Funktionen zuständig ist. Außerdem gibts (von extern angeheiratet) eine Klasse für die Templates und eine zum Mailen.

    Erst diese Templateorientierung macht es jetzt wirklich möglich, dass ich im Shop prinzipiell total mit virtuellen URLs operieren kann. mod_rewrite ist toll dafür. 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.

    Ich mache zur Zeit eine Art Mischmasch, ich verwende ein Script welches alle Libs und Einstellungen läd, welches per Mod-Rewrite bei jedem Request geladen wird und entsprechend dem eigentlichen Request dann ein Modul(Verzeichnis) und dort ein Script läd, ein Request sieht etwas so aus:

    /[modulname]/[scriptname]

    und alles weiter kommt dahinter als Parameter. (Wobei ich auf mod_rewrite aufgrund der Größe des Moduls lieber verzichten würde...)

    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). 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.

    Das Shop-Skript selbst ist wiederum schön aufgeteilt in Unterskripte, die zwar alle per include eingebunden sind, aber in ihrer Gesamtheit den Aufgabenverteiler unübersichtlich machen würden.

    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.

    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.

    Diese Ebene greift dafür auf die einzelnen Klassen (DB, Session, Template, Mail) zu, um Mid- bis Low-Level-Operationen durchzuführen. Das Interface ist standardisiert. 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.

    Das Script was dann geladen wird weiß was zu tun ist und läd dann notwendige Klassen und "macht dann irgendwas" damit.

    Das ist in meinen Augen sinnvoll.

    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.

    Bei der typischen Anfänger-PHP-Programmierung öffnet jedes Skript einzeln die Datenbankverbindung, um etwas zu speichern oder zu lesen, rödelt dann mit der CPU rum, um was auszurechnen, und gibt schließlich unter Aufwendung vieler ECHOs oder PRINTs ein Ergebnis bekannt. Ach ja, was vergaß ich: Am Anfang werden natürlich auch noch irgendwelche schönen POST- oder GET-Werte einbezogen.

    Das Problem: Die Präsentationsschicht hört nicht nach dem ECHO auf, sondern im Gegenteil beginnt dort erst, denn nach dem ECHO erfolgt die Auslieferung an den Webbrowser - und _der_ sorgt für die Darstellung und "Präsentation".

    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.

    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.

    Im Gegenteil kann ich, weil es ja nur ein Array gibt, auf dieselbe Weise eben einmal eine Artikelliste einer Artikelkategorie holen, und ein anderes Mal eine Artikelliste, nach der ein Suchbegriff suchen soll.

    Und ich kann andererseits in der DB-Klasse beispielsweise die Artikelsuche vom simplen FULLTEXT-Index umstellen auf einen Full-Table-Scan "LIKE %suchwort%", oder auch komplexe Indexing-Mechanismen einbauen. Resultat ist immer eine Artikelliste, die ich ausgebe - ohne irgendeine Änderung an Code aus anderen Schichten.

    Man könnte zwar auch per php.ini per auto-prepend(oder wie das heißt) ein Script  vor jedem Request laden, welches dann die Konfigurationen... durchführt, also dass über den Webserver immer echte Pfade aufgerufen werden, nur ist das dann etwas schwierig wenn die ganzen Plugins mit allen Unterverzeichnissen innerhalb es doc-root liegen, das mag ich z.B. nicht so.

    Ja, sowas könnte man machen. Würde vielleicht sogar gut funktionieren. Dennoch muß ja bei so einer Konstruktion für jedes per URL aufgerufene Skript irgendetwas auf dem Server vorhanden sein - sei es das tatsächliche Skript, oder irgendein symbolischer Link woandershin, oder eine Serverkonfiguration, um es so umzubiegen.

    Da habe ich das Umbiegen doch irgendwie gerne selbst in der Hand. :)

    - Sven Rautenberg

    --
    "Beim Stuff für's Web gibts kein Material, was sonst das Zeugs ist, aus dem die Sachen sind."
    (fastix®, 13. Oktober 2003, 02:26 Uhr -> </archiv/2003/10/60137/#m338340>)
    1. 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

      1. Moin!

        Was heißt bei Dir "konsequent"?

        Das heißt bei mir, es so konsequent anzuwenden, wie ich es für sinnvoll halte. :)

        Meinst Du richtig  mit OO Analyse, Entwurf, schön alles mit UML in Objekten/Klassen modelliert?

        Um Gottes Willen, es geht um einen Webshop, keine Doktorarbeit. :) Ich habe mir natürlich reichlich Gedanken gemacht, die Erfahrungen aus einem Webshopprojekt einer anderen Firma, was gründlich mißraten war, mit einbezogen und aufgrund guter Erfahrungen mit einer XML-Importklasse (war mal ein RSS-Leser) diesmal eben auf Klassen gesetzt.

        Oder für das was Du früher mit Funktionen gemacht hast? [...] und ich dachte OK, dann ist ja alles prima...

        Keine Ahnung, ob man das vergleichen kann. Natürlich geht es immer noch besser oder anders. Ich habe relativ wenige, dafür große Objekte. Die Feinaufdröselung bis hin zur Ansicht, dass es ja eigentlich gar keine klassischen Variablen mehr gibt, sondern alles Objekte sind - die Zahl "2" ist beispielsweise ein Objekt mit der Eigenschaft "2" (und sonst nichts), geht mir dann doch noch etwas zu weit. Möglich, dass ich mit meinen Klassen jetzt zwar schon einen Schritt weiter bin, aber immer noch Meilen gehen könnte.

        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.

        Siehst du, so komplex ist der Shop bei weitem nicht. Ich habe zwar 394 Zeilen CSS ;) und insgesamt 14 programmaktive Dateien mit insgesamt grob 50 KB Code, aber das ist ja weit weg von "komplex".

        Der Umfang des Shops war absehbar, die Aufgabe im weitesten Sinne "definiert". Ich denke, dafür ist die gefundene Lösung angemessen.

        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?)?

        Ich verwende keine gesonderte Datenbank-Abstraktion. Das leistet in gewissem Sinne meine DB-Klasse.

        Ich sehe das so: Jede Datenbank hat ihren eigenen SQL-Dialekt. Und irgendwann in der Abstraktionsreihe wird es dazu kommen, dass die gewünschte Anforderung in SQL übersetzt werden soll.

        Wenn ich nun also Abstraktion betreibe, die immer funktionieren soll, dann muß ich mich funktionsmäßig auf den kleinsten gemeinsamen Nenner einigen, der geht. ODBC geht meines Wissens diesen Weg.

        Alternativ kann ich für gewisse Datenbanken fehlende Funktionalität natürlich in Code emulieren. Mircosofts MSSQL kann kein "LIMIT anzahl,offset", sondern nur "TOP anzahl"? Ok, dann wird der Offset zur Anzahl draufgerechnet und intern die ersten $offset Ergebnisse weggeworfen. Blöd für die Datenbank, aber gut für die Abstraktion. :)

        Ausgehend von dieser Überlegung, und in Kenntnis der Tatsache, dass als DB ohnehin MySQL zum Einsatz kommen sollte, habe ich mich darauf beschränkt, sämtliche Datenbankzugriffe eben in der DB-Klasse abzuhandeln. Sollte jemals eine andere DB zum Zuge kommen, würden sich die relevanten Änderungen nur genau dort abspielen.

        Das ist in meinen Augen hinreichend gut gekapselt für diese Aufgabe.

        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?

        Doch. Ich hatte im ersten Ansatz festgestellt, dass die Session-Klasse DB-Zugriffe benötigt, beispielsweise, um den Preis von Produkten zu erfahren, die in den Warenkorb gelegt werden. Meine erste Lösung dazu war, eine Instanz der DB-Klasse und eine Instanz der Session-Klasse zu haben und die DB-Instanz jeder Session-Methode als Parameter mitzugeben. Das fand ich dann aber tierisch umständlich, und so include ich die DB-Klasse in der Session-Klasse, erweitere die DB-Klasse um Session-Methoden und benutze dann nur die Session-Instanz im Shop. Ja, mag etwas böse erscheinen, fand ich aber gut so.

        Außerdem eröffnet mir diese Trennung eben auch Dinge wie einen XML-Datenbankimport, der logischerweise auf die DB-Klasse zugreift, aber keine Session-Funktionalität benötigt.

        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.

        Ich verwende vlibTemplate. Das funktioniert auch hervorragend.

        Was sind denn "Virtuelle URLs"? Meinst Du also keine direkte Zuordnung auf Scripte? Aber was genau hat das mit den Templates zu tun?

        Mit virtuellen URLs meine ich das, was durch

        RewriteRule ^/shop/.+.html$ /backoffice/shop.php

        entsteht. Für sämtliche .html-Adressen im Shop ist genau ein Skript zuständig, aber ohne, dass dieses irgendwie auffällt oder nach außen sichtbar wird.

        Sämtliche Formular-POSTs und -GETs gehen nicht an dieses Skript, sondern (nach dem Prinzip "Affenformular") immer an sich selbst. Jedenfalls grob gesehen. Im zentralen Skript entscheide ich basierend auf der angeforderten URL, was geschehen soll. Da gibt es im Prinzip drei Möglichkeiten: Startseite anzeigen, Kategorieliste anzeigen, oder Details anzeigen.

        Sicher, der Unterschied zu deiner Vorgehensweise ist marginal: Ob ich nun "/shop/zentrale.php?action=add&artikel=123" aufrufe, oder "/shop/warenkorb.html?artikel=123", ist vom Effekt her sicher gleich. Mir ging es aber auch um vernünftige URLs, ich wollte keine langen URL-Parameter haben, damit Suchmaschinen sich auch freuen.

        Außerdem erreiche ich durch die virtuellen URLs noch eine Stufe mehr Abstraktion. Viele URLs werden nämlich auf ein simples Template-Befüll-Skript geleitet, welches nichts weiter zu tun hat, als die in der URL genannte Datei als Template zu parsen (und damit dann doch eher un-virtuelle URLs zu verwenden.) Da ich mich beim Template-Schreiben somit voll auf die für den Benutzer sinnvolle Verlinkung konzentrieren kann und unabhängig davon mit Rewriting die für die verlinkten Seiten passende Businesslogik dranhänge, habe ich einige Probleme nicht. Beispielsweise werden sich die URLs des Shops nie mehr ändern müssen, egal, welche Shopsoftware eingesetzt wird.

        Ich könnte beispielsweise, wenn ich die Performance zur ständigen dynamischen Generierung der Seiten nicht mehr aufbringen kann, einfach statische Seiten generieren, an die passende Stelle legen und dort vom Apache ausliefern lassen. Ich bin nicht von irgendwelchen Skriptnamen abhängig, die hartcodiert in den Templates drinstehen. Und da zwischen "Darstellung im Browser" und "Aufbereitung des darzustellenden HTML-Codes" laut deiner Grafik ja auch eine Schichtgrenze liegt, deine Businesslogik aber nicht an die Schicht "Darstellung im Browser" grenzt, sollte dort eben auch eine Trennung vorgenommen werden.

        Wie erwähnt: Die RewriteRule ist nicht die einzige, die zum Einsatz kommt, das geht etwas nach Bereichen getrennt. Der Apache spielt also als Schicht auch eine Rolle (wenngleich die Schicht dünn und unscheinbar ist).

        Hm. Also mein aktueller Ansatz ist dass das main.php Script zunächst [...]

        Schätzungsweise gehen wir dann sehr ähnlich vor.

        Was wäre denn "richtiges" Programmieren?

        Das, was Profis und Gelehrte dafür halten. ;)

        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?

        Siehe oben. Auch wenn es nicht so aussieht, GET- und POST-Parameter kommen mit obiger RewriteRule beim Skript an. URL-Parameter werden von mod_rewrite sowieso gesondert betrachtet.

        Da ich auf jeder Seite des Auftritts die Kurzanzeige des Warenkorbs benötige, brauche ich auf jeder Seite die Session-Klasse, damit ich die Session-Daten abfragen kann.

        Wirklich zentrale Konfigurationsdaten, die das Shop-Skript interessieren würden, habe ich nicht. Die Datenbankklasse bindet ein kurzes Include ein, damit der DB-Zugang bekannt wird. Ansonsten war es das.

        Im Zweifel würde ich für zentrale Konfigurationsdaten tatsächlich noch eine Extra-Klasse basteln. Denn solche Daten wollen mit einem Admin-Interface dann auch irgendwie verändert werden - da kann man dann zusätzlich zur "get_conf()"-Methode gleich noch eine "set_conf()"-Methode implementieren und das Zeugs irgendwo hinspeichern.

        Spannend wäre in diesem Zusammenhang die Möglichkeit, solch eine Klasse als Bestandteil der Session abzuspeichern und wiederherstellen zu lassen. :)

        Inzwischen bin ich der Auffassung, dass man Funktionalitäten möglichst in Methoden packen sollte [...]

        Bei meinem aktuellen Ansatz macht es nicht viel Sinn, aus den einzelnen Anweisungen, was im Falle des Aufrufs einzelner Seitentypen zu tun ist, eine Klasse mit API zu machen. shop.php ist in diesem Sinne eine "Klasse". An dieser Stelle werden die Nachbarschichten verknüpft - einmal in Richtung Präsentationsschicht (Templates), und dann in Richtung Session/DB. shop.php selbst ist die Businesslogik, die man in meinen Augen nicht trennen kann.

        Und die Aufteilung in unterschiedliche Dateien, die logisch ein Skript bilden (jedenfalls nicht in unterschiedliche Klassen zerfallen), hilft bei der Übersicht.

        Ja, kann man sicher alles anders machen, aber ich lerne ja noch immer was dazu. :)

        So nett so Dinge wie $template->assign($db->getAll('SELECT....')) auch sein mögen, gerade das darf man nicht machen.

        Ok, SQL-Befehle haben in der Businesslogik nichts verloren, aber Dinge wie $template->assign($db->getList($category)) macht man.

        Richtig böse wäre, wenn sich die Template-Engine auf Anweisung der Businesslogik unter Umgehung der DAO mithilfe der DB-Klasse die Liste direkt aus der DB holen würde.

        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?

        Man kann sich darüber natürlich streiten, was sinnvoll ist, und was nicht. Wenn man möglichst universell programmiert, ist man gezwungen, alle Möglichkeiten, die vorkommen könnten, mit einzubeziehen. Und weil man sich nichts verbauen will, dröselt man seine Objekte dann eben möglichst fein auf, vererbt und referenziert - im Grunde genommen ersetzt man aber das Konstrukt "Programmiersprache, die alles erlaubt" durch das Konstrukt "Objektgebilde, das alles erlaubt".

        Aber nur deshalb, weil das eine oder andere "alles erlaubt", habe ich ja noch keinen funktionierenden Code. :) Irgendwann muß ich immer den Schritt machen, mich von all der schönen Univeralität zu lösen und endlich konkret werden.

        Jetzt kann ich zu diesem Zweck natürlich erstmal einen riesigen Overhead basteln, in dem jede Datenbankabfrageergebniszeile als Objekt dargestellt wird, um dann mit viel drumherum und mehreren Schichten von Objekten endlich mal auszudrücken, dass die abgefragte Liste der Artikel endlich mal ausgegeben werden soll.

        Ich kann aber genausogut auch die Abkürzung nehmen und für die Businesslogik eine Methode in der Session-Klasse nutzen, die mir ein Array aus der DB abfragt und intern einfach nur den dafür notwendigen Query an die Query-Methode aus der DB-Klasse leitet und das Ergebnisarray zurückliefert.

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

        Das klingt zwar als theoretische Abhandlung für ein "wie 'man' es macht" ganz toll, aber ich muß ja auch irgendwann mal fertig werden. :) Für mich ist solch eine Objekteritis noch etwas zu unübersichtlich.

        - Sven Rautenberg

        --
        "Beim Stuff für's Web gibts kein Material, was sonst das Zeugs ist, aus dem die Sachen sind." (fastix®, 13. Oktober 2003, 02:26 Uhr -> /archiv/2003/10/60137/#m338340)
        1. Hallo!

          Meinst Du richtig  mit OO Analyse, Entwurf, schön alles mit UML in Objekten/Klassen modelliert? Um Gottes Willen, es geht um einen Webshop, keine Doktorarbeit. :)

          Gut bei kleineren Projekten vielleicht "unnötiger Overhead", aber das ist schon gut, wenn man sich vorher diese Gedanken macht bekommt man eigentlich leichter an ein gut durchdachtes, modulares Design.

          Ich habe zwar 394 Zeilen CSS ;) ...

          Man sollte nur keine "Business-Logik" in CSS auslagern ;-)

          Ich verwende keine gesonderte Datenbank-Abstraktion. Das leistet in gewissem Sinne meine DB-Klasse.

          Ja, meist braucht man das auch nicht, ich verwende zur Zeit PEAR::DB, aber ich überlege noch ob es vielleicht Sinn macht ADODB oder PEAR::MDB zu verwenden, ich habe das zum Glück direkt so angelegt dass der Wechsel möglich wäre, wenn das auch nicht alles so glatt laufen dürfte, aber möglich wäre es.

          Ich sehe das so: Jede Datenbank hat ihren eigenen SQL-Dialekt.

          Den versuche ich zu vermeiden - soweit es geht, und beschränkle mich da lieber und mache mehr mit PHP. Denn bei mir gehört die Möglichkeit mit verschiedenen RDBMS zu arbeiten mit zum "must-have".

          Und irgendwann in der Abstraktionsreihe wird es dazu kommen, dass die gewünschte Anforderung in SQL übersetzt werden soll.

          Es gibt ja viele fertige, wie die von  mir genannten, sogar als PHP-Modul "dbx" (von wegen PHP hätte keine DB-Abstraktion, sowohl DBX als auch PEAR gehören zum Standardumfang).

          Alternativ kann ich für gewisse Datenbanken fehlende Funktionalität natürlich in Code emulieren. Mircosofts MSSQL kann kein "LIMIT anzahl,offset", sondern nur "TOP anzahl"? OK, dann wird der Offset zur Anzahl draufgerechnet und intern die ersten $offset Ergebnisse weggeworfen. Blöd für die Datenbank, aber gut für die Abstraktion. :)

          Limits können viele Abstraktionsschichten emulieren, ebenso auto-increment und vieles andere. Problematischer wird es mit einigen "krasseren" Unterschieden wie JOIN, das gibts wohl nicht in älteren Oracle Dialekten. Und das ist ein großes Problem, daher muss ich an manchen Stellen selber zusätzliech Querys schreiben, Datenbank-Abhängig.

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

          Doch. Ich hatte im ersten Ansatz festgestellt, dass die Session-Klasse DB-Zugriffe benötigt, beispielsweise, um den Preis von Produkten zu erfahren, die in den Warenkorb gelegt werden. Meine erste Lösung dazu war, eine Instanz der DB-Klasse und eine Instanz der Session-Klasse zu haben und die DB-Instanz jeder Session-Methode als Parameter mitzugeben. Das fand ich dann aber tierisch umständlich, und so include ich die DB-Klasse in der Session-Klasse, erweitere die DB-Klasse um Session-Methoden und benutze dann nur die Session-Instanz im Shop. Ja, mag etwas böse erscheinen, fand ich aber gut so.

          Hm, also das machen einige Leute so, aber ich finde das hat dann noch wenig mit OOP zu tun. Dadurch dass Du die DB-Klasse direkt verwendest, ist dessen Implementierung nicht mehr unabhängig von der Session-Klasse. Das mag in Deinem Fall auch ziemlich egal sein, bei mit nicht. Aber wo besteht das Problem entweder im Konstruktor der Session-Klasse das DB-Objekt in die Session-Klasse zu übernehmen, entweder per $this->db = $_GLOBALS['DB']; oder als Parameter an den Konstruktor übergeben?

          Ich verwende zur Zeit global $db in den Methoden die DB-Zugriff brauchen. Andere Frage - wozu hast Du eine Session-Klasse? Was macht die?

          | Außerdem eröffnet mir diese Trennung eben auch Dinge wie einen XML-Datenbankimport, der logischerweise auf die DB-Klasse zugreift, aber keine Session-Funktionalität benötigt.

          Das man es trennen soll ist klar, aber nicht durch Vererbung 2er vollkommen verschiedener Objekte nur der Bequemlichkeit halber wieder verbinden ;-)

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

          Ich habe letztens irgendwo eine Sammlung der Template-Engines für PHP gesehen, waren weit über 50, danach habe ich nicht mehr geguckt ;-)

          Was sind denn "Virtuelle URLs"? Meinst Du also keine direkte Zuordnung auf Scripte? Aber was genau hat das mit den Templates zu tun? Mit virtuellen URLs meine ich das, was durch RewriteRule ^/shop/.+.html$ /backoffice/shop.php

          entsteht. Für sämtliche .html-Adressen im Shop ist genau ein Skript zuständig, aber ohne, dass dieses irgendwie auffällt oder nach außen sichtbar wird.

          Und den Namen benutzt Du nicht als Parameter in Deinen Scripten? Oder suchst Du den in PHP nochmal aus dem Request-String?

          Sämtliche Formular-POSTs und -GETs gehen nicht an dieses Skript, sondern (nach dem Prinzip "Affenformular") immer an sich selbst.

          Aber Du brauchst doch Parameter oder Formulare in Deinem Shop, oder nicht?

          Wie erwähnt: Die RewriteRule ist nicht die einzige, die zum Einsatz kommt, das geht etwas nach Bereichen getrennt. Der Apache spielt also als Schicht auch eine Rolle (wenngleich die Schicht dünn und unscheinbar ist).

          Bei mir geht alles in ein Script, welches alle notwendigen Daten läd, und anhand des originalen Requests genau weiß was zu tun ist, also es wird immer ein Script mit dem angegebene Namen aus dem angegebenen Modul-Verzeichnis geladen. Dieses Script enthält einen kleinen Controller der entsprechend den Parametern weitere Aktionen durchführt.

          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?

          Siehe oben. Auch wenn es nicht so aussieht, GET- und POST-Parameter kommen mit obiger RewriteRule beim Skript an. URL-Parameter werden von mod_rewrite sowieso gesondert betrachtet.

          Wieso machst Du so viel mit den Rewrite-Rules? Diese Logik habe ich in meinem main-php, bzw. das verwendet noch gar keine Parameter, die werden erst in dem von main.php geladenen Script ausgewertet.

          Wirklich zentrale Konfigurationsdaten, die das Shop-Skript interessieren würden, habe ich nicht.

          Nicht? Aber man hat doch immer so Dinge wie root-path, root-url, DB-Userdaten, Einstellungen für Template-Engine, Debug-Modus...

          Die Datenbankklasse bindet ein kurzes Include ein, damit der DB-Zugang bekannt wird. Ansonsten war es das.

          in jedem Script wo benötigt? ich weiß z.B. dass ich die DB eigentlich immer brauche, daher binde ich sowas gobal ein.

          Im Zweifel würde ich für zentrale Konfigurationsdaten tatsächlich noch eine Extra-Klasse basteln.

          Habe ich auch gemacht.

          Spannend wäre in diesem Zusammenhang die Möglichkeit, solch eine Klasse als Bestandteil der Session abzuspeichern und wiederherstellen zu lassen. :)

          Ja, aber was bringt das? Ich weiß jetzt keine genauen werte, aber das Parsen und Instanzieren aller "Basis-Klassen" in main.php dauert nur wenige 1/100 Sekunden, vermutlich durch Verwendung eines Opcode Caches, der dessen Opcode eh im Ram hält. Das ist sicher noch schneller als aus dem /tmp Files. Wobei ich gerade ein wenig damit experimentiere Session-Daten im Shared Memory zu halten (mm-Erweiterung), hat nur den Haken dass die Daten darin nicht gelockt werden, das heißt theoretisch können Inkonsistenzen entstehen, ob und wie sich das praktisch auswirkt habe ich aber noch  nicht so ganz raus.

          Inzwischen bin ich der Auffassung, dass man Funktionalitäten möglichst in Methoden packen sollte [...]

          Bei meinem aktuellen Ansatz macht es nicht viel Sinn, aus den einzelnen Anweisungen, was im Falle des Aufrufs einzelner Seitentypen zu tun ist, eine Klasse mit API zu machen. shop.php ist in diesem Sinne eine "Klasse". An dieser Stelle werden die Nachbarschichten verknüpft - einmal in Richtung Präsentationsschicht (Templates), und dann in Richtung Session/DB. shop.php selbst ist die Businesslogik, die man in meinen Augen nicht trennen kann.

          Naja, ich Trenne z.B. die Datenbank-Zugriffe von den einzelnen Objekte n durch DAO-Klassen speziell für jedes Objekt der Business-Logik. Templates rufe ich eigentlich "proredural" auf, das heißt das Script was in main.php eingebunden ist macht irgendwas mit den Methoden der Objekte, und gibt an Ende über das Template-Objekt die Daten aus. Das könnt eman sicher auch noch kapseln, aber da sehe ich noch keinen Vorteil drin, wenn gleich das auch viele Leute so machen.

          Und die Aufteilung in unterschiedliche Dateien, die logisch ein Skript bilden (jedenfalls nicht in unterschiedliche Klassen zerfallen), hilft bei der Übersicht.

          Das stimmt. Da die Aktionen normalerweise in einer eingebundenen Klasse stehen habe ich im Script eigentlich nur noch die Ablaufkontrolle stehen.

          So nett so Dinge wie $template->assign($db->getAll('SELECT....')) auch sein mögen, gerade das darf man nicht machen. Ok, SQL-Befehle haben in der Businesslogik nichts verloren, aber Dinge wie $template->assign($db->getList($category)) macht man.

          $db ist bei mir ja die Abstraktions-Klasse, die kann nur mit SQL umgehen. Die wird von den DAO-KLassen angesprochen. Aber bei mir würde das anders aussehen:

          DB ist die Abstraktionsklasse(PEAR::DB...) Katalog ist die Katalog-Klasse KatalogDAO ist die Klasse die für den Zugriff auf die Datenbank für die Katalog-Klasse zuständig ist, diese verwendet DB

          $katalog = KatalogDAO::loadData(); // loadData() holt Daten aus DB für $id und schreibt die per set...() Methoden in ein neues Katalog-Objekt und gibt eine Referenz zurück

          $template->assign($katalog->getList()); // sowas würde ich dann machen, getList ist hier eine Methode von Katalog und gibt die Liste als Array aus

          Richtig böse wäre, wenn sich die Template-Engine auf Anweisung der Businesslogik unter Umgehung der DAO mithilfe der DB-Klasse die Liste direkt aus der DB holen würde.

          Ja, PHP erfordert Disziplin ;-)

          Aber nur deshalb, weil das eine oder andere "alles erlaubt", habe ich ja noch keinen funktionierenden Code. :) Irgendwann muß ich immer den Schritt machen, mich von all der schönen Univeralität zu lösen und endlich konkret werden.

          Klar, aber man sollte immer die Modularität im Hinterkopf haben - sofern das Projekt das erfordert. Die Objekte müssen möglichst  gutgekapselt sein, damit Änderungen an einer Stelle möglichst wenige Auswirkungen auf andere Teile der Software haben, denn was das bedeutet habe ich am eigenen Leibe erfahren ;-) Am Anfang ist das kein  Problem, aber irgendwann wird es immer schwieriger sich zu erinnern oder aus dem Code rauszulesen wo die gemachte Änderung denn jetzt überall Einfluss hat. Möglicherweise man vergisst eine Stelle und dann passiert Monate später ein komischer Fehler den sich keiner erklären kann...

          Jetzt kann ich zu diesem Zweck natürlich erstmal einen riesigen Overhead basteln, in dem jede Datenbankabfrageergebniszeile als Objekt dargestellt wird, um dann mit viel drumherum und mehreren Schichten von Objekten endlich mal auszudrücken, dass die abgefragte Liste der Artikel endlich mal ausgegeben werden soll.

          Ja, aber Du verwendest ja auch nicht sowas wie

          $res = mysql_query('SELECT * FROM katalog'); foreach($row=mysql_fetch_array($res))   echo "<li>$row['prod_name'] ($row['prod_id'])</li>";

          So würde man auch ne Menge Code vermeiden, und man hätte direkt seine Liste. Ist halt eine Frage wie weit man das treiben will. Ich denke je komplexer die Software und desto mehr Leute dran arbeiten desto mehr Konventionen, Kapselungen, Schnittstellen... braucht man.

          Ich kann aber genauso gut auch die Abkürzung nehmen und für die Businesslogik eine Methode in der Session-Klasse nutzen, die mir ein Array aus der DB abfragt und intern einfach nur den dafür notwendigen Query an die Query-Methode aus der DB-Klasse leitet und das Ergebnisarray zurückliefert.

          Wie gesagt - Disziplin ist das Zauberwort ;-)

          Viele Grüße Andreas

          1. Moin!

            Ich kürze mal ein paar Nebenschauplätze weg. :)

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

            Doch. [...] Ja, mag etwas böse erscheinen, fand ich aber gut so.

            Hm, also das machen einige Leute so, aber ich finde das hat dann noch wenig mit OOP zu tun. Dadurch dass Du die DB-Klasse direkt verwendest, ist dessen Implementierung nicht mehr unabhängig von der Session-Klasse. Das mag in Deinem Fall auch ziemlich egal sein, bei mit nicht. Aber wo besteht das Problem entweder im Konstruktor der Session-Klasse das DB-Objekt in die Session-Klasse zu übernehmen, entweder per $this->db = $_GLOBALS['DB']; oder als Parameter an den Konstruktor übergeben?

            Natürlich ist die Implementierung der DB-Klasse unabhängig von der sie erweiternden Session-Klasse. Das ist ja der Sinn einer Klassenerweiterung: Enthaltene Methoden der Ursprungsklasse durch neue Methoden zu ergänzen bzw. (das tue ich nicht) existierende Methoden zu verändern.

            Ich habe eben festgestellt, dass ich:
            a) genau eine Instanz der Session-Klasse benötige,
            b) innerhalb der Session ohnehin Datenbankzugriff haben muß und
            c) um sauber zu programmieren immer die separate Datenbankinstanz an die Session-Instanz übergeben müßte.

            Wenn ich also die DB-Klasse nur deswegen instantiieren muß, damit die Session-Klasse was davon hat, dann kann ich die beiden auch in einer Klasse zusammenfassen. Die Methoden der DB-Klasse stehen mir dann immer noch zur Verfügung, und die Methoden der Session-Klassen können sauber auf die DB-Methoden zugreifen, weil es interne Methoden sind. Ich spare mir das Gehampel, im Shop eine DB-Instanz und eine Session-Instanz zu halten und der Session-Instanz ständig die einzige vorhandene DB-Instanz übergeben zu müssen.

            Dein Ansatz, über einen $_GLOBALS-Zugriff auf die DB-Instanz zuzugreifen, würde ich dagegen als superböse bezeichnen. Würde ich das machen, würde ich in der Session-Klasse festlegen, wie ich im Hauptprogramm die DB-Instanz benennen müßte. _Das_ ist nun wirklich alles andere als "unabhängig programmiert".

            Ich verwende zur Zeit global $db in den Methoden die DB-Zugriff brauchen.

            $_GLOBALS und "global $var" sind böse. Übergib eine Referenz - das mußt du dann aber (weshalb es ja auch nervt) entweder einmal machen: $session->set_db($db); (das speichert dann die Referenz intern - geht sowas überhaupt?) oder bei jedem Methodenaufruf immer.

            Andere Frage - wozu hast Du eine Session-Klasse? Was macht die?

            Die macht so lustige Dinge wie die Verwaltung des Warenkorbs. Es gibt beispielsweise diese Methoden:
            set_in_basket($artikelnr, $anzahl)
            delete_from_basket($artikelnr)
            add_to_basket($artikelnr, $anzahl)
            remove_from_basket($artikelnr, $anzahl)
            get_basket()
            clear_basket()
            get_num_article($artikelnr)
            get_num_articles()
            get_sum_articles()

            Damit kann ich wunderbar regeln, was in einem Warenkorb typischerweise so anfällt. Da die Methoden sich ggf. gegenseitig aufrufen, ist die Singularität auch gewährleistet. Beispiel:
            function remove_from_basket($artikelnr,$anzahl)
            {
              $this->set_in_basket($artikelnr,$this->get_num_article($artikelnr)-$anzahl);
            }

            remove_from_basket zieht vom vorhandenen Warenkorb $anzahl von $artikelnr ab. Wäre also zu gebrauchen für "plus/minus"-Buttons.

            Jetzt wird man sich wundern, wo denn da die Abfrage ist, falls man bei einem Artikel zwei abziehen will. Die Abfrage ist in set_in_basket enthalten. Dort wird geprüft, ob mindestens ein Artikel in den Warenkorb gepackt wird - andernfalls wird der Artikel gelöscht (mit delete_from_basket). Auf diese Weise  verhindere ich auch, dass Benutzer per direkter Eingabe "-200" oder "ganzviele" Artikel einkaufen können. Die Parameterprüfung wird dort gemacht, wo die tatsächliche Arbeit anfällt. Parameter durchreichen hingegen ist ungefährlich.

            | Außerdem eröffnet mir diese Trennung eben auch Dinge wie einen XML-Datenbankimport, der logischerweise auf die DB-Klasse zugreift, aber keine Session-Funktionalität benötigt.

            Das man es trennen soll ist klar, aber nicht durch Vererbung 2er vollkommen verschiedener Objekte nur der Bequemlichkeit halber wieder verbinden ;-)

            Doch. Es sind nicht zwei vollkommen verschiedene Objekte. Sie benötigen einander. Ich muß sie irgendwie verbinden. Meine Methode finde ich gut. Dass im Hintergrund eben z.B. ganz banale Arrays werkeln, und keine tollen Objekte, reicht für meine Bedürfnisse vollkommen aus. Möglich, dass ich exakt an dieser Stelle bei einem anderen Projekt dann doch den Schritt hin zu Objekten gehe.

            Ich habe letztens irgendwo eine Sammlung der Template-Engines für PHP gesehen, waren weit über 50, danach habe ich nicht mehr geguckt ;-)

            Ich habe auch mal eine eigene "Template-Engine" geschrieben - naja, eher nur eine Template-Funktion für einen Formmailer. Sind also dann weit über 51 Stück. ;)

            RewriteRule ^/shop/.+.html$ /backoffice/shop.php
            Und den Namen benutzt Du nicht als Parameter in Deinen Scripten? Oder suchst Du den in PHP nochmal aus dem Request-String?

            Doch. mod_rewrite setzt dankenswerterweise die original angeforderte URL in $_SERVER['SCRIPT_URL'], das werte ich aus. Denn irgendwie will ich schon gerne wissen, ob nun eine Artikeldetailansicht gewünscht ist, oder der Warenkorb. :)

            Sämtliche Formular-POSTs und -GETs gehen nicht an dieses Skript, sondern (nach dem Prinzip "Affenformular") immer an sich selbst.

            Aber Du brauchst doch Parameter oder Formulare in Deinem Shop, oder nicht?

            Ja klar. Habe ich ja auch. Nur werden Requests, die irgendwelche Aktionen aufgrund von gesendeten Formularen bewirken, grundsätzlich mit einem Redirect beantwortet, um der Formularreload-Doppelsendungs-Problematik zu entgehen.

            Wieso machst Du so viel mit den Rewrite-Rules? Diese Logik habe ich in meinem main-php, bzw. das verwendet noch gar keine Parameter, die werden erst in dem von main.php geladenen Script ausgewertet.

            Keine Ahnung, warum ich das wirklich mache. Es ist halt an dieser Stelle die Möglichkeit, dass man sich serverseitig viel Parsearbeit sparen kann, wenn man anhand des Requests nur das Skript aktiviert, was tatsächlich arbeiten muß. Für den Shop selbst ist das egal, klar. Da muß immer das shop.php ran und die Aufgaben verteilen. Aber es gibt ja noch andere Seiten und Aufgaben, die nicht so komplex sind.

            Außerdem arbeite ich gerne mit mod_rewrite, um beispielsweise "broken image"-Links zu verhindern. Ein Artikel kann drei Bilder haben: Thumbnail, Großansicht und noch eine "Datentabelle". Die meisten Artikel haben nur die zwei Bilder. Also schreibe ich für nicht auffindbare Datentabellen die URL um auf ein 1-Pixel-transparent-GIF (und schreibe keine Größen ins <img>). Für nicht auffindbare Großansichten und Thumbnails hingegen kommen zwei "Bild nicht gefunden"-Bilder zum Browser. Auf diese Weise spare ich mir die redundante Speicherung eines Bildlinks, dessen Vorhandensein ich in zweifacher Hinsicht prüfen müßte, in der Datenbank (der ohnehin nur Pfad + Artikelnummer + Zusatz + ".jpg" wäre) und lagere im Prinzip uninteressante Funktionalität eben an mod_rewrite aus.

            Die zweifache Überprüfung bedeutet: Einerseits kann in der DB "nichts" gespeichert sein, wenn kein Bild vorhanden ist, andererseits kann das Bild real nicht vorhanden sein, obwohl es in der Datenbank drinsteht - und schließlich kann es trotz negativer Aussage der Datenbank doch vorhanden sein. Das alles zu prüfen, während ich die Bildverknüpfung <img> ausgebe, erschien mir etwas zu aufwendig. Die Bildungsregel für den Bildnamen ist jetzt eben im Template abgelegt (die Artikelnummer wird an die passende Stelle des Links eingesetzt), und den Rest regeln der Webserver. Das paßt auch vollkommen in das Schichtenmodell, denn der Webserver ist eben für die Versorgung des Browsers mit Benutzeroberfläche zuständig. Gewisse Entscheidungen trifft er eben selbst - dazu gehört die Abfrage, ob ein Bild vorhanden ist, oder ob Ersatz angezeigt werden soll - und gewisse andere Dinge werden an die nächste Schicht (das Shop-Skript) weitergeleitet.

            Jetzt kann ich zu diesem Zweck natürlich erstmal einen riesigen Overhead basteln, in dem jede Datenbankabfrageergebniszeile als Objekt dargestellt wird, um dann mit viel drumherum und mehreren Schichten von Objekten endlich mal auszudrücken, dass die abgefragte Liste der Artikel endlich mal ausgegeben werden soll.

            Ja, aber Du verwendest ja auch nicht sowas wie

            $res = mysql_query('SELECT * FROM katalog');
            foreach($row=mysql_fetch_array($res))
              echo "<li>$row['prod_name'] ($row['prod_id'])</li>";

            Nein. Ich kriege von $session->get_articles($kategorie) ein Array aller Artikeldaten und schiebe das der Template-Engine rüber. Ist also alles etwas abstrakter, macht aber im Prinzip nichts anderes.

            So würde man auch ne Menge Code vermeiden, und man hätte direkt seine Liste. Ist halt eine Frage wie weit man das treiben will. Ich denke je komplexer die Software und desto mehr Leute dran arbeiten desto mehr Konventionen, Kapselungen, Schnittstellen... braucht man.

            Das unterschreibe ich sofort. Der Shop ist Einzelarbeit. Koordination mit anderen Programmierern wäre da wahrscheinlich ... hm, nennen wir es "interessant" geworden.

            - Sven Rautenberg

            --
            "Beim Stuff für's Web gibts kein Material, was sonst das Zeugs ist, aus dem die Sachen sind."
            (fastix®, 13. Oktober 2003, 02:26 Uhr -> </archiv/2003/10/60137/#m338340>)