Archiv der Kategorie PHP

Best practices in papayaCMS 5 – oder: Sinnvolle Aufteilung der Modulklassen.

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

Ineffiziente Strukturierung der Klassen

Ineffiziente Strukturierung der Klassen

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.

Beispiel mit Kompetenzen: Delegation

Beispiel mit Kompetenzen: Delegation

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.

Komplexes Beispiel mit Oberklassen für Module

Komplexes Beispiel mit Oberklassen für Module

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

Database Exporter: Database Structure and Naming Analyzer

Mit dem Database Exporter 1.0 kann man die Struktur und die Namensgebung seiner Datenbank (mitsamt aller Tabellen und Feldern) analysieren.

Dabei reicht es, die Zugangsdaten (Host, Benutzername, Password) anzugeben, und man kann eine komplette Datenbank (hier: DATABASENAME) in ein Verzeichnis (hier: xml) zu exportieren.

$dbexporter->exportDbStructureToXML('DATABASENAME', 'xml');

Während des Schemaexports wird auch die Struktur analysiert und nach einigen Gesichtspunkten bewertet:

  • Fremdschlüsselabhängigkeiten werden automatisch gefunden, wenn Fremdschlüssel einmalig heißen; d.h. beispielsweise in der Tabelle postings heißt es user_id und nicht poster_id, wenn damit eigentlich auf users.user_id verwiesen wird (id wie in ROR wird nicht verwendet)
  • gefundene Abhängigkeiten werden zudem mit ihren Typen verglichen, wobei davon ausgegangen wird, dass der Typ im Hauptschlüssel (Primary Key) der richtige ist

PHP/zlib: Wie ermittelt man die unkomprimierte Dateigröße einer .gz-Datei?

Problem: Mit $fh = gzopen($fileName, 'r'); erhält man einen Filepointer auf die Datei, aber für gzread() benötigt man neben dem Filepointer auch eine Leseinheit (und in den meisten Fällen dürfte das der Inhalt sein). Allerdings kommen wir mit filesize($fileName) nicht weiter, denn damit erhalten wir nur die Größe der komprimierten Datei zurück.

Antwort: Nach einem Beitrag von zaotong auf php.net/gzread wird die Originaldatengröße in den letzten 4 Bytes der Datei gespeichert; alles was man machen muss, ist also die letzten 4 Bytes auslesen und als Buffergröße nutzen.

Snippet:

$fileName = 'compressed.gz';

// normal öffnen, windows-binärmode
$fh = fopen($fileName, "rb");
// springe zum 4. vorletzen byte
fseek($fh, -4, SEEK_END);
//lese die nächsten 4 bytes (also die letzten)
$readBuf = fread($fh, 4);
// binärdaten lesen, ist integer (4 Byte)
$gzSize = end(unpack("V", $readBuf));
// pointer wieder schliesen
fclose($fh); 

// nun entkomprimieren, windows-binärmode
$fh = gzopen($fileName, "rb");
// nun können wir angeben, wieviel wirkliche daten wir lesen wollen
$content = gzread($fh, $gzSize);
// und brav wieder schließen
gzclose($fh);