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)