Christian Seiler: Automatisches Nachladen von Klassen mit PHP

PHP bietet seit der Version 5 die Möglichkeit, Klassen automatisch bei Bedarf nachzuladen.

Einfaches Laden über __autoload

Klassen können in PHP seit Version 5 dynamisch nachgeladen werden. Darunter ist zu verstehen, dass PHP eine benutzerdefinierte Funktion aufruft, falls eine bis dahin unbekannte Klasse angefordert wird. Beispiel:

$a = new UnbekannteKlasse ();

UnbekannteKlasse sei in dem Moment noch nicht definiert. Im Normalfall wird PHP automatisch eine Fehlermeldung "Klasse nicht gefunden" ausgeben und sich beenden. Definiert man jedoch eine Funktion namens __autoload (sie beginnt mit zwei Unterstrichen), so wird zuerst diese aufgerufen. Ihr wird genau ein Parameter übergeben: Der Name der Klasse, die nicht gefunden wurde und nachgeladen werden soll. Die Funktion kann nun an Hand des Klassennamens versuchen, die passende PHP-Datei einzubinden (per include), damit die Klasse nach Beendigung der Funktion definiert ist. Sollte die Funktion keine Datei zu der angegebenen Klasse finden, d.h. die Klasse existiert nach Aufruf der Funktion immer noch nicht, gibt PHP die bekannte Fehlermeldung aus.

Ein einfaches Beispiel für eine derartige Funktion wäre:

function __autoload ($klasse) {
  // die bösesten zeichen in klassennamen mal sicherheitshalber verbieten
  if (strpos ($klasse, '.') !== false || strpos ($klasse, '/') !== false
      || strpos ($klasse, '\\') !== false || strpos ($klasse, ':') !== false) {
    return;
  }
  if (file_exists ($klasse.'.php')) {
    include_once $klasse.'.php';
  }
}

Die Funktion versucht nun eine Datei namens Klassenname.php zu finden und diese dann einzubinden, falls sie existiert. Einzige Besonderheit hier ist die Überprüfung auf schädliche Zeichen: Im Normalfall sind diese Zeichen in Klassennamen in PHP sowieso nicht erlaubt, allerdings reicht call_user_func diese Zeichen unter Umständen direkt an __autoload durch. Damit wäre es unter Umständen möglich, dass fremde Inhalte per include eingebunden werden können. Dies wird durch diese Abfrage verhindert.

Verwendet man __autoload, so kann man auf lange Include-Listen am Anfang von PHP-Dateien verzichten, man muss nur noch die Datei einbinden, die __autoload definiert.

Der spl_autoload_register-Mechanismus

Zusätzlich bietet PHP im Rahmen der SPL (Standard PHP Library) die Funktion spl_autoload_register an. Diese ermöglicht es, den normalen Nachlademechanismus zu ersetzen. Das heißt: Sobald spl_autoload_register einmal aufgerufen wurde, ist der normale Nachlademechanismus außer Kraft gesetzt.

Die Funktion spl_autoload_register akzeptiert einen Parameter: Den Namen einer Funktion, die versuchen soll, eine Klasse automatisch nachzuladen. Diese wird dann an Stelle von __autoload verwendet. Falls der Parameter nicht angegeben wird, wird die Funktion spl_autoload angenommen, die versucht, die Klasse kleingeschrieben als Dateinamen mit .php oder .inc als Endung einzubinden.

Dies kann aus zwei Gründen interessant sein:

  1. Man möchte keine eigene Funktion programmieren, weil einem die Funktionalität, die spl_autoload bietet, ausreicht. In dem Fall reicht es einfach, folgenen Aufruf zu tätigen:

    spl_autoload_register ();

  2. Man möchte mehrere Funktionen nutzen, die Klassen automatisch nachladen sollen. Dann kann man einfach spl_autoload_register mehrfach aufrufen und die dort angegebenen Funktionen werden der Reihe nach aufgerufen, bis die Klasse gefunden wurde:

    spl_autoload_register ('nachlade_funktion_1');
    spl_autoload_register ('nachlade_funktion_2');

Hier sei noch angemerkt, dass ein Aufruf von spl_autoload_register ('__autoload'); dafür sorgt, dass wieder die klassische __autoload-Methode aufgerufen wird, auch wenn zum Beispiel noch andere Funktionen zusätzlich definiert sind.

Anwendung in Klassenbibliotheken

Dies ist für Autoren von Klassenbibliotheken, die aus mehreren Klassen bestehen, nun besonders interessant. Vorher mussten entweder die Entwickler einer Klassenbibliothek selbst dafür sorgen, dass jede Datei die korrekten anderen Dateien einbund (wobei sie dort dann auf Dinge wie den include_path achten mussten) oder sie wälzten die Verantwortung auf die Nutzer der Klassenbibliothek ab.

Nun kann eine Klassenbibliothek nämlich eine Funktion anbieten, die alle relevanten Klassen dieser Bibliothek automatisch nachlädt. Jemand, der die Klassenbibliothek (oder Teile davon) nutzen will, muss nur noch die Datei einbinden, die diese Funktion enthält und sie dann entweder in seiner eigenen __autoload-Funktion aufrufen oder per spl_autoload_register zu den registrierten Nachladefunktionen hinzufügen.

Folgendes Beispiel soll für eine Klassenbibliothek gelten, deren Präfix SELFHTML ist, d.h. jede Klasse fängt mit diesen acht Zeichen an. Im Hauptverzeichnis der Klassenbibliothek liegt nun eine Datei Autoloader.php, die folgenden Inhalt besitzt:

class SELFHTMLAutoloader {
  private static $basisPfad = null;
  public static function autoload ($klasse) {
    if (self::$basisPfad === null) self::$basisPfad = dirname (__FILE__);
    if (substr ($klasse, 0, 8) !== "SELFHTML") return;
    if (strpos ($klasse, '.') !== false || strpos ($klasse, '/') !== false
        || strpos ($klasse, '\\') !== false || strpos ($klasse, ':') !== false) {
      return;
    }
    $teile = preg_split ('/(?<=.)(?=\p{Lu}\P{Lu})|(?<=\P{Lu})(?=\p{Lu})/U', substr ($klasse, 8));
    $pfad = self::$basisPfad . DIRECTORY_SEPARATOR .
      join (DIRECTORY_SEPARATOR, $teile) . '.php';
    if (!file_exists ($pfad)) return;
    include_once $pfad;
  }
}

Der Code macht nun folgendes:

  1. Zuerst wird der Basis-Pfad der Klassenbibliothek ausfindig gemacht. Da die Datei Autoloader.php direkt darin liegt, ist der Basis-Pfad natürlich das Verzeichnis, in dem sich diese Datei befindet, also dirname (__FILE__). Der Pfad wird zwischengespeichert, um bei weiteren Aufrufen Zeit zu sparen.
  2. Nun wird überprüft, ob die Klasse zur Klassenbibliothek gehört, in diesem Fall wird dies daran erkannt, ob die Klasse mit SELFHTML anfängt.
  3. Schließlich werden hier die gleichen Überprüfungen auf schädliche Zeichen durchgeführt, wie oben.
  4. Nun wird der Name der Klasse aufgeteilt in mehrere Elemente. Hier wird angenommen, dass die Klasse in CamelCase vorliegt, das heißt, dass in dem Klassennamen Wörter aneinandergereiht werden, deren erster Buchstabe immer großgeschrieben wird. Als Beispiel: TestKlasse. Der Ausdruck würde dies in zwei Teile "Test" und "Klasse" aufteilen.
  5. Die Teile werden nun wieder zusammengefügt mit Hilfe der Konstante DIRECTORY_SEPARATOR, das ist der Verzeichnistrenner unter dem jeweiligen Betriebssystem, unter Windows also \, unter Linux oder Mac OS X /. Zudem wird die Endung .php angehängt und der vorher bestimmte Basispfad berücksichtigt. Aus SELFHTMLTestKlasse wird also /Pfad/zur/Klassenbibliothek/Test/Klasse.php.
  6. Falls diese Datei existiert, wird diese eingebunden.

Selbstverständlich ist die genaue Art, wie hier die Klasse nachgeladen wird, nur ein Beispiel. Falls die Autoren der Klassenbibliothek andere Konventionen als CamelCase bevorzugen, kann eine anders geschriebene Funktion dies natürlich berücksichtigen.

Will nun ein anderer Entwickler die Klassenbibliothek verwenden, so kann er einfach folgenden Code nutzen:

require_once '/Pfad/zur/Klassenbibliothek/Autoloader.php';
spl_autoload_register (array ('SELFHTMLAutoloader', 'autoload'));

Selbstverständlich wäre auch möglich, in einer eigenen __autoload-Funktion diese Funktion aufzurufen:

require_once '/Pfad/zur/Klassenbibliothek/Autoloader.php';
function __autoload ($klasse) {
  // ...
  SELFHTMLAutoloader::autoload ($klasse);
  // ...
}

Fazit

Mit dem automatischen Nachladen von Klassen ermöglicht es PHP 5 Programmierern, sehr lange Listen von include-Anweisungen zu vermeiden. Zudem wird das Einbinden von Klassenbibliotheken, die sich an das hier vorgestellte Schema halten, stark vereinfacht: Es müssen weder Laufzeitkonstanten gesetzt werden noch muss der include_path angepasst werden.

  1. Danke für den Artikel! Auf diese Mechanismen werde ich bei meinen nächsten Projekten zurückgreifen. Damit kann man sich einiges einfacher machen.

  2. Der große Haken daran ist die schlampige bis dilletantische Umsetzung von PHP an sich und PHP-Projekten im besonderen. Der gesamte autoload-Mechanismus wird ganz schnell zu einem Schuss ins Knie wenn man mehrere verschiedene PHP-Applikationen in einem Environment integrieren muss. Das fängt dann schon damit an, dass man drei PHP-Apps hat welche __autoload() benutzen. Und dass dann meist mit der Implikation, dass "meine App" ja die einzige auf der Welt ist. Und schon hat man das Problem wie man die verschiedenen Implementierungen von Autoload integriert, ohne krude Hacks zu machen.

    An und für sich ein nettes Feature, aber im Kontext einer so lumpigen Sprache wie PHP und der größtenteils noch lumpiger geschriebenen Anwendungen ist bei größeren Projekten eher davon abzuraten.

    Im übrigen ist es ein Performancegewinn (PHP ist auch recht langsam...), wenn man exakt immer nur an der Stelle ein require_once() macht, wo man die Klasse auch braucht. Im übrigen sollte man Klassen nicht mit include/require einbinden, da man sonst recht schnell Redeclaration-Erros bekommt.

    Ach ja: Ich muss im grinsen, wenn mri Leute erklären dass PHP jetzt enterprise-application-fähig geworden ist...

  3. Das fängt dann schon damit an, dass man drei PHP-Apps hat welche __autoload() benutzen. Und dass dann meist mit der Implikation, dass "meine App" ja die einzige auf der Welt ist.

    Deswegen beschrieb ich ja, wie man multiple Autoloader realisiert (per spl_autoload_register und wie man damit Klassenbibliotheken problemlos parallel betreiben kann.

    Dass das einen nicht davor schützt, dass Leute schlechten Code schreiben und es dadurch zu mehrfach definierten __autoload kommt, ändert nichts daran, dass Autoloading zumindest prinzipiell eine sinnvolle Lösung ist.

  4. "Dass das einen nicht davor schützt, dass Leute schlechten Code schreiben und es dadurch zu mehrfach definierten __autoload kommt, ändert nichts daran, dass Autoloading zumindest prinzipiell eine sinnvolle Lösung ist."

    Genau das liegt aber nun mal in dem mangelhaften Design der Sprache begründet. Und da muss ich halt dann eben sagen: Ja, ganz tolles Feature. Aber bevor ihr sowas in PHP einbaut wäre es toller wenn man man erst mal die generellen Design-Bugs der Sprachen aufräumen würde.

    Wie gesagt, schon tolles Feature. Aber haut mich halt echt nicht vom Hocker. Also mal ernsthaft: Autoloading in Verbindung mit Factories konnte man davor auch schon elegant lösen.

  5. Ich glaube Du missverstehst mich. Ich bin weder Entwickler von PHP noch habe ich hier vor, PHP-Apologie zu betreiben. Ich wollte lediglich eine brauchbare Methode vorstellen, wie in PHP Klassenbibliotheken entwickelt und eingebunden werden können, ohne dass man sich verrenken muss. Nicht mehr und nicht weniger.

    Du meinst nun, von Autoloading ist generell abzuraten, weil man sich bei Nichtbeachtung meiner Vorschläge damit selbst ins Knie schießen kann. Nunja, dass man Autoloading auch falsch einsetzen kann, bestreite ich ja nicht. Das trifft aber auf jedes Feature jeder Programmiersprache zu. Und egal, was man von PHP halten mag, es ist aktuell nunmal so wie es ist und in diesem Kontext ist Autoloading in der Form, wie ich es vorschlug, ein extrem nützliches Mittel, um sich als Programmierer das Leben leichter zu machen. Und das wollte ich hier darstellen.

  6. "Ich bin weder Entwickler von PHP..."

    Na ja, dann ist dass das Hauptproblem ;-)

  7. Ich finde den Beitrag sehr hilfreich. Danke!

  8. Hier ist eines gute Artikel ueber Exceptions mit __autoload() und namespaces: Exceptions in __autoload()

  9. Sehr schöner Artikel. Diese Funktionalität kannte ich bisher garnicht, bei meinem nächsten Projekt wird sie aber sicherlich zum Einsatz kommen :)