Nachdem ihr bereits wisst wie ihr Klassen definiert und sie in verschiedenen Namensräumen unterbringt, beschäftigt sich dieser Artikel damit die Klassen möglichst effizient bekannt zu machen – das heißt ohne jedes mal ein require
im Code zu benutzen. Das Konzept dahinter heißt Autoloading.
Das Beispiel
Im Rahmen dieses Beitrags werde ich folgende Klasse verwenden.
1 2 3 4 5 6 |
namespace Example; class Person { public $name; } |
Sie befindet sich in der Datei Person.php
im Ordner Example
.
Wozu benötige ich Autoloading?
Bevor wir eine Klasse in PHP verwenden können, muss sie definiert werden. Dies findet in einer separaten Datei statt und diese Datei muss von uns somit eingebunden werden. Bisher benötigen wir dazu jedes mal ein require
.
1 2 3 |
require_once __DIR__.'/Example/Person.php'; $p = new \Example\Person(); |
Das wird insbesondere dann schwierig, wenn unsere Klassen wiederum andere Klassen verwenden. Schreiben wir überall ein require
nehmen wir uns sehr viel Flexibilität für spätere Änderungen.
Wir könnten natürlich global (zum Beispiel in der index.php) eine Liste aller Klassen hinterlegen. Das wäre allerdings sehr ineffizient, weil jetzt bei jedem Programmaufruf, alle Dateien geladen werden müssen, obwohl wir nur einen Bruchteil davon benutzen werden.
Noch schlimmer wird es, wenn wir Bibliotheken von Drittanbietern verwenden wollen. Da kennen wir die Ordnerstruktur überhaupt nicht.
Dieses Problem können wir sehr elegant mit Autoloading lösen.
Verfügbare Funktionen
PHP bietet zwei Möglichkeiten mit Autoloading umzugehen. Zum einen __autoload()
und zum anderen spl_autoload_register()
.
__autoload()
Wie andere magische Methoden auch, wird sie von uns definiert und PHP erkennt anhand des Namens, was wir damit bezwecken wollen. Sie wird immer dann aufgerufen, wenn wir noch unbekannte Klassen, Interfaces oder Traits verwenden. Als Parameter erhalten wir den Namen.
1 2 3 4 5 6 7 8 9 |
function __autoload($name) { $fileName = __DIR__.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $name).'.php'; if (file_exists($fileName)) { require_once $fileName; } } $p = new \Example\Person(); |
An dieser Stelle werden wir jetzt dafür belohnt, dass wir uns bei der Benennung unserer Namensräume und Klassen an ein einheitliches Schema gehalten haben. Wir müssen lediglich die Trennzeichen für Namensräume durch das Trennzeichen für Verzeichnisse ersetzen und ein .php
ergänzen.
Wie alle Funktionen können wir auch __autoload()
nur ein einziges Mal definieren. Wir können also in unserem Programm nur diesen einen Autoloader haben. Das wird spätestens dann zum Problem, wenn wir Bibliotheken von Drittanbietern verwenden. Wir müssten deren Ordnerstruktur in unseren Autoloader aufnehmen.
Aufgrund dieser Nachteile wird von der Verwendung abgeraten!
spl_autoload_register()
Im Gegensatz zu __autoload()
ist spl_autoload_register()
eine PHP-Funktion, die wir nur verwenden und nicht definieren. Eine Bibliothek kann sie also unabhängig von uns aufrufen und einen Autoloader registrieren. Genauso können wir jetzt für jedes unserer Module einen eigenen Autoloader anmelden.
Sie benötigt als Parameter ein callable
, also eine Funktion, die zunächst genauso aussieht wie unsere __autoload()
oben.
1 2 3 4 5 6 7 8 9 |
spl_autoload_register(function ($name) { $fileName = __DIR__.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $name).'.php'; if (file_exists($fileName)) { require_once $fileName; } }); $p = new \Example\Person(); |
Stößt PHP jetzt auf eine unbekannte Klasse werden alle registrierten Autoloader der Reihe nach durchlaufen, bis einer die notwendige Datei einbindet, oder keine weiteren Autoloader mehr vorhanden sind.
Daher ist es äußerst wichtig, dass sich jede so registrierte Funktion nur um „ihre“ Klassen kümmert und für den Rest keine Fehler erzeugt. Die Prüfung auf file_exists
hilft uns dabei. So ist sicher gestellt, dass spätere Autoloader ebenfalls einen Versuch bekommen und PHP nicht aufgrund der fehlenden Datei einen Fatal Error wirft.
Autoloader Klasse
Moderne Frameworks bringen gleich ganze Klassen mit, die als Autoloader dienen. Dies wird dadurch ermöglicht, dass spl_autoload_register()
als Parameter nicht nur eine Funktion sondern auch ein Array aus Objekt und Funktion akzeptiert.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Loader { public function handle($name) { $fileName = __DIR__.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $name).'.php'; if (file_exists($fileName)) { require_once $fileName; } } } $loader = new Loader(); spl_autoload_register([$loader, 'handle']); $p = new \Example\Person(); |
Aber Achtung: Unsere Autoloader Klasse kann sich nicht selbst laden, wenn wir sie in einer eigenen Datei definieren. Diese müssen wir also vor ihrer Verwendung über require
bekannt machen.
Beispiel einer Autoloader Klasse
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
class Loader { private $namespaces = []; /** * Erlaubt dem Anwender in einem Rutsch für mehrere Namensräume Verzeichnisse zu definieren. * * @param array $definition [Namensraum => Verzeichnis] * * @return $this */ public function registerNamespaces(array $definition) { $this->namespaces = $definition; return $this; } /** * Vereinfacht für den Anwender, die Registrierung des Autoloaders. */ public function register() { spl_autoload_register([$this, 'handle']); } /** * Das eigentliche Autoloading. * * @param $name */ public function handle($name) { $parts = explode('\\', $name); // Den obersten Namensraum der Klasse prüfen if (isset($this->namespaces[$parts[0]])) { // ok wir sind zuständig // Zielpfad $path = $this->namespaces[$parts[0]]; // den obersten Namensraum entfernen array_shift($parts); $fileName = $path.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts).'.php'; if (file_exists($fileName)) { require_once $fileName; } } } } |
Wir bieten dem Anwender die Möglichkeit für verschiedene Namensräume unterschiedliche Ordner zu definieren. Durch das Fluent Interface lassen sich Autoloader jetzt schön lesbar anmelden.
1 2 3 4 5 6 |
(new Loader())->registerNamespaces([ 'Example' => __DIR__.DIRECTORY_SEPARATOR.'Example', 'Bundle' => __DIR__.DIRECTORY_SEPARATOR.'app'.DIRECTORY_SEPARATOR.'bundles', ])->register(); $p = new \Example\Person(); |
Mit dieser Klasse haben wir einen PSR-0 Autoloader für den zwingend Namensräume verwendet werden müssen. Als kleine Übungsaufgabe solltet ihr ihn PSR-4 kompatibel machen, denn das ist der Standard, der heute verwendet werden sollte.