Unser OXID Autoloader

Jeder von uns Programmierern ist eigentlich bestrebt, wenig zu wiederholen und viel wiederzuverwenden. Wir überlegen uns meistens möglichst schöne Klassenstrukturen in denen wir unsere Logiken so kapseln, dass wir sie hoffentlich wiederverwenden können. In letzter Zeit springe ich zwischen einigen OXID Todos und stehe gefühlt jedes Mal vor dem Schritt bestehende Strukturen zu kombinieren oder Strukturen auf der grünen Wiese neu zu pflanzen. Und jedes Mal ist der OXID-Autoloader auch wieder ein Thema.

In “älteren” Strukturen, musste man sich meist “selbst” darum kümmern wie Strukturen geladen werden und hat dementsprechend die Quellcodes händisch nach bestem Wissen und Gewissen mit Includes versehen. Die Ausgabe 3.2012 des PHP-Magazins hat dafür z.B. Phing genannt, wobei manche Dinge trotz Deklaration noch händisch inkludiert werden müssen.
 Zitat der Phing-Doku:
 Always include/require all the classes needed for this task in full written notation. Furthermore you should always include phing/Task.php at the very top of your include block. Then include all other required system or proprietary classes.
Diese händischen Includes sind aber alles harte Aufrufe und schränken die Wiederverwendbarkeit etwas ein indem sie wahrscheinlich saubere Quellcodes “verschmutzen”. Und jedes Mal macht man sich Gedanken um die Verfügbarkeit von Logiken. Steigt man in ein Projekt neu ein, erhöht dies auch die Lernkurve denn man weiß nicht auf Anhieb ob und wo man mal eben eine Klassenkonstante aufrufen kann.
Die PHP-Community hat dies erkannt und steuert mit einem Autoloader seit PHP5 nach. Zusätzlich dazu kann mit der SPL der PHP-Autoloader nützlich erweitert werden, indem der einzelne Standard-Autoloader __autoload() durch einen ganzen Stapel von Autoloadern erweitert werden kann. Ein Autoloader wird beim Aufrufen von bisher nicht inkludierten Klassen und Interfaces automatisch mit dem entsprechenden voll qualifizierten Interface- oder Klassennamen aufgerufen und erlaubt dem Entwickler so, fehlende Logiken erst on Demand nachzuladen.
Ein weiterer Vorteil eines Autoloaders ist meiner Meinung nach auch, dass autoloadbare Strukturen meistens selbsterklärender sind.
Autoloadbare Strukturen zeigen sich heutzutage eigentlich in allen großen Frameworks. Im Zend Framework werden zum Beispiel alle Klassen und Interfaces  mit dem Präfix Zend als oberster Namespace eingeleitet. Der Klassennamen wird dann automatisch in den Ordnernnamen umgemünzt, indem man vom Zend-Ordner ausgehend alle Namespace-Trenner nutzt um damit Ordner und Dateien zu identifizieren.
Oft wird ein Software-Core wie der des Zend Frameworks in großen Projekten nicht direkt genutzt sondern an bestimmten Stellen erweitert.
Um dann Namenskonflikte zwischen Originallogik und Erweiterung zu vermeiden, hat es sich als Best Practice erwiesen, die Erweiterungen in einen eigenen Namespace, bzw. Ordner im Autoloader-Kontext, zu kapseln.
Magento als Kind vom Zend Framework kann hierfür als Beispiel dienen.

Im Core des OXID Shopsystems sind solche Strukturen vorhanden. Geht es dann aber an die tiefe Modulentwicklung, fehlt es an manchen Details.

Mittlerweile denkt man bei OXID an Version 4.6, wenn nicht sogar Version 5, aber die grobe Ordnerstruktur für Logiken hat sich API-safe schon länger nicht mehr verändert. Seit mindestens Version 2 von OXID lässt sich fast jede Core-Klasse über “oxNew” und entsprechenden Strukturen im “modules” Ordner erweitern (Mehr dazu bei OXID selbst).  “oxNew” sorgt für eine lose Kopplung des eigentlichen “new” Aufrufs und erlaubt über das Backend für fast jede Core-Klassen eine Vererbungshierachie zu bestimmen. Genau diese Struktur kann heutzutage von einem Autoloader beeinflusst werden.
Die entsprechende Moduleinstellung wird seit Jahren von OXID so ausgewertet, dass der entsprechende Entrag im “modules”-Ordner gesucht wird. Im Backend kann für jede Datei ein Pfad und ein Klassenname eingetragen werden. Der Klassenname sollte aber gemäß Core-Logik auch dem Dateinamen entsprechen. Mit OXID 4 ist zwar ein Autoloader eingeführt worden, ist aber vom jeweiligen Integrationspartner noch nicht wirklich direkt beeinflussbar gewesen, denn es wurde ausschließlich der Standard-Autoloader deklariert.
Man musste also seit Jahren Redundanzen in saubere Kapselungen einführen.
Zum Einen hat man zur sauberen Kapselungen selbstverständlich die Dateien in Ordner gepackt. Man hatte aber – und musste natürlich sogar –  auch die Klassen so zu benennen, dass sie einzigartig waren.
Hier tritt bereits die erste Redundanz auf, muss dann diese Redundanz aber auch noch zusätzlich im Backend von OXID spiegeln.
Zitat von OXID:
There we enter in the modules form field „oxarticle => ourmodule/extendedarticlerating“ and save that entry. Now the module is registered and loaded.
Mit OXID 4.3.0 hat man sich hier deutlich geöffnet und den erweiterbaren SPL Stack eingeführt. Ab jetzt war es über die functions.php möglich einfach eigene Autoloader einzuführen und so die oben beschriebene Redundanz aufzulösen.
Folgenden Autoloader, den ich immer für meine Freizeit-Projekte und nun für meine Firma WBL-Konzept verwende, möchte ich euch unter der MIT Lizenz öffentlich freigeben.

Alternativ könnt Ihr ihn mit Unittests auch hier runterladen.

Unser Autoloader sucht ausgehend von “modules” Ordner im Zend Framework Stil die Interface- und Klassendateien, indem er den PHP-Namespace-Trenner “\” oder den Unterstrich “_” nutzt um eine Ordnerhierarchie aufzubauen.
Damit dieser Modulautoloader die OXID-Core-Logik nicht beeinflusst und wir somit möglichst Upate-Safe weil API-sicher bleiben, versucht er Klassen nur zu laden, wenn ihm mitgeteilt wird, für welche Namespaces er gilt.
Der Autoloader geht von einer Standarddateiendung “.php” aus. Jedoch hat mein Agenturalltag gezeigt, dass trotz harmonisierter Strukturen eigene, spezielle Stilmittel beibehalten werden. So findet man zum Beispiel Ordnerstrukturen, bei denen Klassendateien mit “.class.php” oder Interfaces mit “iface.php” enden. Um dies abzudecken erlaubt der Setter, dass der Autoloader auch andere Dateiendungen beachten kann.
Hierbei ist zu beachten,  dass die Endungen die im Array vorne stehen, auch als erstes bearbeitet werden, denn die Iteration über die Dateiendungen erfolgt mit einer foreach-Schleife. Intern hätte man dies mit einem glob-Aufruf vielleicht eleganter lösen können, aber meine Benchmarks haben gezeigt, dass dies keinen Unterschied macht.

Eigene Klassen außerhalb der Modulchain

Geht es an eigene Klassen die mit “oxNew” geladen werden sollen, scheitert dieser Autoloader. “oxNew” wandelt mit strtolower die Klassennamen intern zu Kleinbuchstaben um und deaktiviert somit das direkte Mapping von Klassennamen zu Dateinamen. Jeder folgende Autoloader-Aufruf erhält dann nur noch kleingeschriebene Klassennamen.

Unser Autoloader versucht dies auszugleichen, indem er “wblNew” bietet. Ein einfacher Wrapper für “oxNew”.

“aModuleFiles” bei OXID 4.6.0

Mit OXID 4.6.0 ist ein weiterer Hook in den Autoloader eingebunden worden. Deklariert man in seiner metadata.php eines Moduls ein “files”-Array, werden diese Dateien vom Autoloader beachtet.

 Ich möchte diese Technik nicht einsetzen, da sie meiner Ansicht nach eine weitere Redundanz darstellt, aber eines meiner nächsten Postings wird sicher sein, wie sich dieser OXID-Autoloader im OXID 4.6.0 Kontext verhält. Ich empfehle in der neuen Moduleinstellung jetzt einmal auf  “nein” zu klicken ;). Dazu aber später mehr.

Ausblick auf nächstes Posting(oxblocks, OXID 4.6.0)

Bis OXID 4.6.0 gab es eigentlich nur eine weitere Logik die wirklich abhängig von dem Modulordner war. Und das ist das Feature der “oxblocks”.  Über die Datenbanktabelle “oxtplblocks” konnten Templates überlagert werden, indem man einen Speicherort und ein Modul eines anderen Templates angegeben hat. Die einfachste Möglichkeit war dann, den entsprechenden Modulordner als Ganzes anzugeben. Nun hat sich das Verhalten in Abhängigkeit zur neuen OXID-Modulverwaltung aber etwas geändert. Dazu wie gesagt aber ein anderes Mal mehr.

UPDATE 06.05.12

Unser Autoloader nach dem Upate auf OXID 4.6.0

Kommentare sind geschlossen.