Eigentlich keine neue Komponente, aber trotzdem eine Erwähnung wert: Zend_Loader_Autoloader [1][2] ist eine Unterkomponente von Zend_Loader und ersetzt dessen Autoloading-Mechanismus. Die alte Zend_Loader-Klasse bleibt weiterhin bestehen und kann auch verwendet werden. Was neu ist am Autoloader, das schaue ich mir jetzt mal an
Solange nicht anders erwähnt oder notwendig, verzichte ich für die Übersichtlichkeit bei den Klassen auf das führende Zend_Loader.
Wozu?
Wozu eigentlich Autoloading? Entgegen älterer Aussagen von mir, kann Autoloading (mal abgesehen vom übersichtlicheren Code) auch einen Performance-Vorteil bieten. Erstellt man mittels new ein Objekt, wird immer zunächst die Klasse gesucht, was auch notwendig ist, denn daraus soll schließlich das Objekt erstellt werden. Existiert die Klasse bereits im Speicher, wird direkt das Objekt erstellt, existiert sie nicht, tritt der Autoloading-Mechanis in Kraft. Nun kann man sagen, dass dieser ein Overhead darstellt, schließlich handelt es sich um (oft nicht-trivialer) PHP-Code, allerdings wird dieser immer nur maximal 1 mal pro Klasse ausgeführt. Sorgt man per require_once immer dafür, dass die Klasse zur Instanzierung auch existiert, wird zwar der PHP-Code nicht aufgerufen, allerdings muss intern jedes mal geprüft werden, ob die Datei bereits geladen wurde. Das kann bei vielen kleinen Dateien recht große Auswirkungen haben. Man kann natürlich versuchen die Klassen immer nur zur ersten Verwendung explizit zu laden, was aber eher fehlerträchtig, als sinnvoll ist und hier mal ignoriert wird
.
Und wozu ein neuer Autoloader? Der alte Mechanismus funktioniert super für Klassen und Bibliotheken/Frameworks, die sich an die Zend-Konvention zur Benennung von Klassen und (Klassen-)Dateien halten, das wars dann aber auch schon. Selbst die Klassen der Module (zum Beispiel die Models oder Formulare) lassen sich damit nicht besonders gut automatisch nachladen. Bei fremden Bibliotheken kommt hinzu, dass diese auch eigene Autoloader mitbringen können, bei denen sich die Entwickler etwas gedacht haben. Nutzt man hierfür einfach den SPL-Autoload-Stack, muss man sich für eine Reihenfolge entscheiden, was nicht unbedingt immer zur korrekten Lösung führt.
Autoloader
Der größte Unterschied zum alten Mechanismus ist, dass der Neue Prefix-basiert arbeitet und Autoload-Anfragen delegieren kann. Er bekommt dazu die Prefix, um die er sich kümmern soll (zum Beispiel Zend_ oder My_Library_), und (falls nötig) den (zusätzlichen) Mechanismus, der dazu benötigt wird, um die Dateien der Klassen, die mit den Prefix beginnen, zu laden. Somit ist es möglich, dass er alleine im SPL-Stack steht und die Anfragen weiter gibt, andererseits können weiter Loader im SPL-Stack stehen, für die sich Zend_Loader_Autoloader eben nicht verantwortlich fühlt.
Für die Mechanismen gibt es mehrere Möglichkeiten. Zum einen kann per registerNamespace() ein Prefix definiert werden, für den sich der hauseigene Loader kümmern soll/kann. Alternativ, falls nicht der interne Mechanismus verwendet werden soll, kann mittels pushAutoloader() (bzw unshiftAutoloader() zum Einfügen an den Anfang des Stacks) eigene Mechanismen ergänzt werden. Der erste Parameter ist dabei entweder ein im PHP-Sinne Callback, oder eine Klasse, die das Interface Zend_Loader_Autoloader_Interface implementiert. Ein Beispiel dafür ist die Klasse Zend_Loader_Autoloader_Resource, zu der ich noch komme.
Wichtig ist noch, dass Autoloader das Singleton-Pattern implementiert und bei Aufruf von getInstance() sich selbst (oder eine erweiternde Klasse) im SPL-Stack registriert.
$l = Zend_Loader_Autoloader::getInstance();
$l->registerNamespace('My_Library');
$l->pushAutoloader (array('My_Autoload_Class','autoload'), 'My_Library');
$l->pushAutoloader (new My_Autoloader(), 'My_Library');
Die erste Variante ist vorallen für Zend-konforme Bibliotheken interessant, allen voran voraussichtlich die eigene, die zweite Variante für fremde Bibliotheken, die eigene Loader mitbringen, wie zum Beispiel Doctrine oder eZcomponents, die dritte Variante letzten Endes für Klassen(sammlungen), die keinen eigenen Loader besitzen, sich aber auch nicht an die Zend-Konvention halten, den dazugehörigen Loader muss man sich dann aller Vorraussicht nach aber selbst schreiben. Die Angabe des Namensraumes ist bei den letzten beiden Varianten optional, dann gilt der Loader für alle Klassen, die nicht bereits vorher durch einen Loader geladen werden konnten. Loader mit passenden Prefix haben grundsätzlich Vorrang.
Letzten Endes gibt es noch die Methode setFallbackAutoloader(), mir der man den Autoloader anweisen kann auf jede Klasse, die er nicht zuordnen oder laden konnte, den internen Loader anzuwenden. Dies entspricht in etwa dem Verhalten vom Zend_Loader-Autoloader mit dem Unterschied, dass zunächst alle (passenden) Loader angewendet werden; “Fallback” eben.
Autoloader_Resource
Autoloader_Resource ist eine Implementierung von Autoloader_Interface, die das oben bereits angesprochene Problem angeht, dass Modul-Klassen nicht einfach so durch den Autoloader geladen werden können. Grund ist die Abweichung von den Zend-Konventionen. Als Beispiel nehmen wir ein Formular in application/modules/mymodule/forms/Myform.php, was soweit zwar nicht fix vorgegeben ist, durch den vorgegebenen Aufbau der Kontroller aber Quasi-Standard. Der (ab jetzt) verwendete Klassenbezeichner Mymodule_Form_Myform weicht allerdings davon ab, wenn man Autoloader_Resource aber nicht erweitern oder selber neu schreiben möchte, kann man sich auch daran gewöhnen.
Autoloader_Resource betrachtet nun 3 Teile. Da wären einmal das Modul (hier Mymodule), welches fest (samt Basispfad, hier application/modules/mymodue) am Objekt gebunden ist, dann die Komponente samt “Zwischenpfad” (hier Form mit forms/), die als Paar dem Objekt bekannt gemacht werden, und letzten Endes der Name. Wird über diesen Loader versucht eine Klasse zu laden, wird zunächst das einleitende Modul-Prefix verglichen und abgebrochen, wenn es nicht übereinstimmt. Danach wird geprüft, ob für die Komponente ein “Zwischenpfad” existiert und wenn ja, wird daraus samt Eigenname eine komplette Pfadangabe erstellt und versucht die Datei zu laden.
Praktisch sieht es recht übersichtlich aus
$l = new Zend_Loader_Autoloader_Resource(array (
'basePath' => 'application/modules/mymodule',
'namespace' => 'Mymodule'
));
$l->addResourceType ('forms', 'forms/', 'Form');
Der erste Parameter von addResourceType() ist dabei ein Typ-Bezeichner, der vorallen intern verwendet wird. Eine Registrierung des Loaders bei Zend_Loader_Autoloader ist nicht notwendig, denn er er meldet sich selbst bereits im Konstruktor per pushAutoloader() an.
Eine Besonderheit ergibt sich noch, wenn man Zend_Application benutzt. Dort findet sich die Klasse Zend_Application_Module_Autoloader, welche Zend_Loader_Autoloader_Resource erweitert und einige gängige Resource-Typen bereits vordefiniert.
Model_DbTable => models/DbTable/ Form => forms/ Model => models/ Plugin => plugins/ api => apis/
Nutzt man diese Klasse, muss man also nur noch Modul-Prefix und Modul-Basispfad im Konstruktur angeben. Nutzt man zudem Zend_Application_Resource_Modules und definiert eine Bootstrap-Klasse für das Modul, dann kann man sich sogar das sparen. Man kann die Bootstrap selbst noch dazu nutzen, um eigene Resource-Typen zu definieren, weil das bisher (?) nicht nativ zur Verfügung steht.
protected function _initCustomResourceTypes () {
$rl = $this->getResourceLoader();
$rl->addResourceType('mytype','MyType','my/path/');
}
Mehr zu Zend_Application findet sich auch hier in der Vorschau zu Zend_Application
[1] Proposal: http://framework.zend.com/wiki/display/ZFPROP/Zend_Loader_Autoloader+-+Ralph+Schindler
[2] Manual: http://framework.zend.com/manual/en/zend.loader.html
[...] in eigener Sache: Ich habe zwar schon einen Beitrag über die neue Autoloader-Komponente vom Zend-Framework, allerdings scheint noch Bedarf zu sein, wie ich im Forum erkannte. Deshalb habe [...]