Bei einem größeren Projekt entstehen höhere Anforderungen an die Organisation und Dateistruktur der Sourcen. Diese Anforderungen gelten sowohl für die Modul- als auch die Theme-Entwicklung. Dazu muss man sich in jedem Projekt Gedanken machen,
- wie man den Source-Code innerhalb eines Moduls oder Themes auf verschiedene Ordner und Dateien verteilt,
- wie man Teile des Source-Codes über mehrere Module hinweg gemeinsam nutzen kann und
- wie man Ordner-, Datei-, Klassen- und Funktionsnamen so wählt, dass der Source-Code am Ende gut lesbar ist.
Drupal bietet hierfür verschiedene gestufte Ansätze, die je nach Komplexität des Projekts mehr oder weniger in Frage kommen. Insbesondere empfiehlt es sich, bei jedem nicht-trivialen Projekt das Autoloading zu nutzen.
Im folgenden werden die Möglichkeiten der Sourcecode-Strukturierung in Drupal kurz diskutiert, um dann ausführlicher auf das PSR-4 Autoloading einzugehen, das für Drupal 8 den aktuellen Standard darstellt und für das es in Drupal 7 das Modul X Autoload gibt.
Einfache Möglichkeiten der Organisation
Die einfachste Möglichkeit, Sourcecode aus der .module-Datei zu verlagern ist die Verwendung von require_once(...) oder include_once(...) in der .module-Datei.
// @file mymodule.module ... $modulpath = dirname(__FILE__); require_once("$modulpath/inc/lib.inc"); require_once("../otherModule/inc/xyz.inc"); ...
Damit lassen sich, wie im Snippet angedeutet, durch Ausnutzen der Möglichkeiten relativer Pfade (../../abc) durchaus Beziehungen zu Sourcen in anderen Modulen herstellen.
In Drupal gibt es außerdem die Möglichkeit, Sourcen mit der Funktion module_load_include zu laden. Ich hatte in diesem Beitrag aber auf Probleme mit der Funktion hingewiesen, so dass sich ihr Einsatz nicht ermpfiehlt.
Drupal bietet für Klassen die Möglichkeit, diese in der .info-Datei per files[]-Direktive zu deklarieren.
name = mymodule description = Module for something core = 7.x package = whr-cool php = 5.3 version = 7.x-0.1(alpha) files[] = inc/classes/myfirst.class.inc files[] = inc/classes/mysecond.class.inc files[] = ../othermodule/inc/classes/othermodulefirst.class.inc
Auch damit ist die Bezugnahme auf Sourcen (Klassen) in anderen Modulen durch relative Pfadangaben grundsätzlich möglich. Der funktionale Source-Code würde weiterhin mit require_once ausgelagert und alle Klassen in der .info-Datei deklariert.
Ein grundsätzliches Problem bei dieser Vorgehensweise ist, dass man ab einer gewissen Komplexität des Projekts eine undurchschaubare Vielfalt von Pfadbeziehungen zu managen hat und außerdem mit eindeutigen Klassennamen arbeiten muss. So muss man Modul-übergreifend auf eindeutige Klassennamen achten.
Für Probleme dieser Art wurde in PHP das Autoloading geschaffen.
Verwendung von einfachem Autoloading
Damit es in Drupal keine Konflikte mit dem internen Autoloading gibt, verwendet man dort nicht __autoload() sondern spl_autoload_register() (s. auch Tipp in der PHP-Dokumentation).
Die Implementierung in einem Modul (in mymodule.module) oder einem Theme (in template.php) kann dann z.B. so aussehen:
// Quelle: http://www.codesynthesis.co.uk/tutorials/autoloading-classes-in-drupal-7 spl_autoload_register('autoload'); function autoload($class) { if(file_exists('/path/' . $class . '.php')) { include('/path/' . $class . '.php'); } }
Es können auch mehrere spl_autoload_register-Aufrufe hintereinander oder an verschiedenen Stellen erfolgen.
Damit lässt sich schon eine komplexere Organisation von Sourcecode aufbauen. Dabei muss man allerdings Drupal-spezifische Hierarchien nachbilden und seinen eigenen Standard entwickeln und wird sicherlich nicht immer sofort eine befriedigende Best Practice finden.
Nun gibt es ambitionierte Entwickler, die sich schon viele Gedanken zu dem Thema gemacht haben, insbesondere im Hinblick auf die zukünftige Version Drupal 8. Dort entschied man sich zunächst für den Standard PSR-0 um dann später auf den Standard PSR-4 zu wechseln. Es empfiehlt sich - nicht zuletzt um sich schon einmal an diesen für Drupal 8 wichtigen Standard zu gewöhnen - die eigene Organisation von Ordnern und Sourcecode so weit wie möglich daran anzulehnen.
Einsatz von Autoloading nach PSR-4
Für das Autoloading nach PSR-4 wurde für Drupal 7 das Modul X Autoload entwickelt.
Ein wesentlicher Unterschied zum Standard PSR-0 ist der, dass die Sourcen unterhalb des Modulordners src abgelegt werden und nicht mehr im Ordner lib. Wer also noch Beispiele im Netz zur Verwendung von X Autoload findet, die Sourcen im Ordner lib organisieren, sollte sich bewußt sein, dass sich der Beitrag in dem Fall auf einen im Grunde veralteten Standard bezieht. Gleichwohl lassen sich mit X Autoload wohl beide Standards realisieren, so dass auch die veralteten Beispiele funktionieren. Ich habe das aber nicht überprüft. Im folgenden wird nur der Standard PSR-4 weiter verfolgt.
Eine typische Dateistruktur nach PSR-4 für Drupal kann z.B. folgendermaßen aussehen:
/modules/mymodule/mymodule.info.yml /modules/mymodule/src/MyModuleClass.php /modules/mymodule/src/Entity/MyEntity.php # Plugin /modules/mymodule/src/Tests/MyEntityTest.php # Simpletest /modules/mymodule/tests/src/Entity/MyEntityTest.php # PHPUnit
PSR-4 - Namespaces werden in Drupal 8 aufgelöst, wie in diesem Beitrag beschrieben.
Möchte ich also meine Klassen für mein Drupal 7 Projekt nach PSR-4-Standard organisieren gehe ich folgendermaßen vor.
- Ich installiere das Modul X Autoload und nehme in meinem mymodule.info-File dependencies[] = xautoload auf.
- Ich lege eine Datei MyEntity.php für meine Klasse MyEntity im Ordner /modules/mymodule/src/Entity/ an.
- In meiner Datei MyEntity.php deklariere ich oben den Namespace:
namespace Drupal\mymodule\Entity; // include 'x1234.php'; // const xy = 77; class MyEntity { // ... }
- Diese Klasse kann ich jetzt überall, also auch in anderen Modulen und deren Sourcen verwenden, indem ich auf den jeweiligen Namespace verweise. Beispielsweise:
use Drupal\mymodule\Entity\MyEntity as MyEntity; ... $MyEntity = new MyEntity // or directly without 'use' // $MyEntity = new Drupal\mymodule\Entity\MyEntity;
Fazit
Vorteile des Einsatzes des Moduls X Autoload sind:
- Eine PSR-4-Standard-konforme und Drupal 8 ähnliche Organisation der Projekt-Ressourcen,
- die konfliktfreie Modul-übergreifende Zentralisierung von Source-Code und
- die Verwendung einfacher gut lesbarer Klassen- und Dateinamen.
Last but not least gibt es noch ein interessantes Feature, auf das in diesem Zusammenhang etwas off topic ebenfalls hingewiesen werden soll:
- Für die Files[]-Deklaration in der mymodule.info-Datei können auch Wildcards verwendet werden, z.B. files[] = src/*.php. Komplexere Formen sind möglich. Hierzu sei aber auf die Dokumentation verwiesen.