Vor wenigen Tagen wurde papayaCMS 5.0 veröffentlicht. Da eventuell der ein oder andere mit dem System nun herumexperimentiert, wird man als Entwickler auch schnell Module entwickeln wollen. Vor allem bei größeren Modulen (bestehend aus vielen Ausgabe-Klassen) empfiehlt sich eine weit granulärere Aufteilung als zwingend vorgeschrieben ist.
Ich erspare mir die Erklärung der Basispapayasystems, dafür möge man bitte die entsprechenden Dokumentionen (PDF) konsultieren. Nur so viel sei gesagt: Um die Daten von der Datenbank auf den Bildschirm zu bekommen, braucht man grundsätzlich nur eine Ausgabeklasse (Seite, Box, usw.) und ein entsprechendes XSL-Template. Aber, aber..
Prinzipiell gilt: MVC-Kenner werden einen sehr leichten Einstieg haben, die anderen sollten bei Fragen zunächst diese Thematik verinnerlichen.
Damit eine Klasse Zugriff auf die Datenbank hat, muss es von base_db (sys_base_db.php) abgeleitet werden. Vor allem bei kleinen Seitenmodulen passiert es häufig, dass der Entwickler die Kurzversion nimmt: Generalisierung der Datenbankklasse, 1-2 Queries und XML-Ausgabe. Spätestens, wenn man die gleiche Funktionalität – etwa “last blog post titles” – nicht nur als Seiten- sondern auch als Boxmodul benötigt, entsteht doppelte Code. Doppelter und redundanter Code. Schlecht. Ein guter Ansatz, um die Zuständigkeiten zu prüfen und zu ermitteln, ob Operationen sich nicht generalisieren und delegieren lassen können. Und ja, wir kommen dem Ziel MVC damit praktisch sehr nahe.
Alleine aus diesem praktische Grunde (und selbstverständlich ist die Verwendung von Delegation grundsätzlich in Modulsystem zu befürworten) empfiehlt sich der Einsatz einer Datenbasisklasse. Diese Klasse erweitert die o.g. Datenbankklasse, die Ausgabemodule instanziieren nur noch diese Basisklasse. Und für unsere Freunde der notorischen Delegationsverweigerer: Mit “Seitenklasse erweitert Datenbankklasse” kommt ihr nicht weiter… und das gilt selbstverständlich auch für alle anderen Arten von Modulen. Tatsächlich ist hier Delegation unbedingt notwendig.
Ein weiterer Vorteil ist die bessere Wartbarkeit und Übersichtlichkeit (vgl. MVC). Selbstständlich werden auch schreibene Operationen in dieser Klasse implementiert. Der Einsatz von Interfaces oder mehrerer Datenbasisklassen steht jedem frei. Man sollte aber nicht mit Kanonen auf Spatzen schießen, ein komplexes System wird durch mehr Komplexität nicht wirklich einfacher.
Die weitere Aufgabe des Ausgabemoduls (vgl. Controller) ist natürlich auch die Ausgabe.. ja, kein Witz 😉 Auch hier gibt es eine ähnliche Konstellation wie bei den Datenbankabfragen, aber in der umgekehrten Sicht: Es gibt in größeren System häufig gleiche oder ähnliche Ausgaben. Auch hier kann die Faulheit einen schnell dazu verleiten, den XML-Code im Controller selbst zu schreiben – ist ja praktisch. Aber auch hier gilt: Spätestens wenn man selber [beim Refactoring] anfängt, und klasseninterne Helpermethoden zur Sub-XML-Generierung zu bauen oder gleichen XML-Code an mehr als 1-2 Stellen verwendet.. richtig: Delegation! Hier empfiehlt sich der Einsatz einer Ausgabebasisklasse. Jene Klasse hat im Wesentlichen nur eine Aufgabe: Für einen oder mehrere Parameter (idR eine Datenarraystruktur) ein geschäftsklassenabhängiges XML zu bauen. Es gibt weder eine Verbindung zur Datenbank noch zu anderen Ausgabemodulen.
Ebenfalls für diese Klassen gilt: Eine weitere strukturelle Aufteilung der Ausgabebasisklassen kann, muss aber nicht vorteilhaft sein.
Selbstverständlich lassen sich beide Klasse auch in Nicht-Ausgabemodulen verwenden, etwa einem Connectormodul. Das ist vor allem für Module interessant, die bestimmte Daten und/oder XML (aus welchen Gründen auch immer) weitergeben müssen.
Tipp 1: Vor allem bei mehreren Seiten- oder Boxmodulen kann es durchaus sinnvoll sein, eine gemeinsame Oberklasse (base_myplugin_content) zu erstellen. Hier lassen sich auch direkt die notwendigen Basisklassen korrekt instanziieren.
Tipp 2: Die Verwendung des Singleton-Pattern steht einem frei: In der Regel hat es aber wenig Sinn, die Datenbasisklasse mehrfach zu erstellen (kann vorkommen, wenn Boxen in Seiten des gleichen Modules auftauchen). Daher: getInstance().
In der Zusammenfassung:
- Trennung der Zuständigkeiten und Verwendung von Delegation, aber: Generalisierung geht auch
- Verwendung von Datenbasisklassen, bspw. base_myplugin_data extends base_db
- Verwendung von Ausgabebasisklassen, bspw. base_myplugin_output extends base_object
- delegierte Verwendung der o.g. Klassen in Seiten- und Boxmodulen