Nach einer kurzen Weihnachtspause möchte ich mit dem letzten Posting für dieses Jahr kurz das Thema „Autoloading in PHP“ behandeln.

Wir stellen uns dazu folgende Situation vor: Wir haben eine große Sammlung an verschiedenen Dateien mit PHP Klassen die uns bei unserer täglichen Arbeit unterstützt und unsere Web-Applikation am laufen hält.

Wenn nun die verschiedenen Klassen im Quelltext der Applikation benutzt werden sollen, müssen wir die einzelnen Dateien inkludieren – Sei es mit Hilfe von require oder include.

require und include sind im großen und ganzen identisch – Der einzige Unterschied besteht darin das require einen Compiler-Fehler verursacht wenn die angegebene Datei nicht gefunden werden konnte, include hingegen nur eine Warnung.

Was würdet ihr aber tun wenn verschiedene Klassen voneinander abhängig sind?
Würdet ihr im Quelltexten immer wieder mit include und/oder require die benötigten Dateien einbinden? An sich ist das natürlich richtig, doch für diese Fälle solltet ihr lieber include_once bzw. require_once benutzen – Dadurch werden bereits inkludierte Dateien kein zweites mal eingebunden und euer Webserver wird etwas geschont!

Also NICHT sowas schreiben:

include 'datei_a.php';
include 'datei_b.php';

// ...

include 'datei_a.php';

Sondern lieber sowas hier:

include_once 'datei_a.php';
include_once 'datei_b.php';

// ...

include_once 'datei_a.php';

Doch auch diese Lösung ist nicht besonders zeitgemäß. Wir sind schließlich Web-Entwickler und möchten eine einfache aber dennoch performante und möglichst automatisierte Lösung haben. Genau dafür gibt es den sogenannten „Autoloader“ – bzw. den Befehl spl_autoload_register (Ab PHP 5.1.2).

Der Autoloader tut folgendes: Jedes mal wenn eine neue Klasse, die noch nicht im Quelltext vorkam, instanziiert wird, wird der Autoloader die ihm übergebene Funktion aufrufen. In dieser Funktion könnt ihr nun eine Logik einbauen die z.B. anhand eines Musters eure Klassen einbindet!

Das ganze klingt etwas kompliziert – Deshalb ein kurzes und einfach gehaltenes Beispiel:

function autoloader($class)
{
    include_once 'classes/my_' . strtolower($class) . '.php';
}

spl_autoload_register('autoloader');

$my_class = new Demo();
$my_second_class = new Super_Demo();

Diese Funktion würde nun, ausgehend vom Standort der aktuellen Datei, folgende Dateien inkludieren:

classes/my_demo.php
classes/my_super_demo.php

Das Beispiel beschreibt einen funktionierenden Autoloader – Allerdings würde es Fehler hageln, sobald die gewünschte Klasse nicht gefunden werden konnte. Außerdem müssten nun alle Dateien innerhalb von „classes“ liegen. Folgender Code ist etwas sicherer und erlaubt mehrere Ordner-Ebenen:

function autoloader($class)
{
    try
    {
        // Macht aus dem Klassennamen einen Pfad.
        $file = str_replace('_', '/', strtolower($class)) . '.php';

        if (file_exists($file))
        {
            // Lädt die Datei der Klasse.
            require $file;

            // Klasse wurde gefunden
            return true;
        }

        // Die Klasse konnte im Dateisystem nicht gefunden werden.
        return false;
    }
    catch (Exception $e)
    {
        echo $e->getMessage();
        die;
    }
}

Leicht angepasster Code aus dem Kohana Framework.

In dieser Funktion wird geprüft ob die gesuchte Datei existiert, es wird die Exception die im Fehlerfall geworfen würde abgefangen und alle Unterstriche im Klassennamen werden in Slashes umgewandelt – Das hat zur Folge das eine Klasse mit Namen Demo_Class_One im folgenden Ordner gesucht werden würde:

demo/class/one.php

Viele Leute finden keinen Gefallen daran das jeder Unterstrich zu einem Slash umgewandelt wird, denn das hat zur Folge das wir sehr viele Unterordner haben und es schnell unübersichtlich wird – Doch das können wir mit einem kleinen Trick lösen: Wir können den Autoloader so programmieren das zwar einfache Unterstriche zu Slashes umgewandelt werden, doppelte Unterstriche jedoch als eine Art „Trenner“ benutzt werden. Dadurch könnten wir unsere Klassen weiterhin logisch trennen, sind aber nicht dazu gezwungen für jeden Unterstrich eine neue Ordner-Ebene zu erstellen.
Instanziieren wir Beispielsweise nun diese Klasse: Database_Sql_Select__Query_Builder

Würde in diesem Pfad gesucht werden:

database/sql/select/query_builder.php

Der Autoloader müsste dazu entsprechend so aussehen:

function autoloader($class)
{
    try
    {
        // Macht aus dem Klassennamen einen Pfad.
        list($dir, $file) = explode('__', strtolower($class));
        $file = str_replace('_', '/', $dir) . '/' . $file . '.php';

        if (file_exists($file))
        {
            // Lädt die Datei der Klasse.
            require $file;

            // Klasse wurde gefunden
            return true;
        }

        // Die Klasse konnte im Dateisystem nicht gefunden werden.
        return false;
    }
    catch (Exception $e)
    {
        echo $e->getMessage();
        die;
    }
}

Und schon brauchen wir in unserem Quelltext keine einzige Datei mehr händisch einbinden um die darin enthaltene Klasse zu nutzen! Natürlich war das aber noch nicht alles – Wir können auch mehrere Autoloader definieren! Das ist nützlich wenn mehrere Fremd-Bibliotheken benutzt werden welche eigene Autoloader mitbringen.

Definiert man nun mehrere Autoloader hintereinander und instanziiert anschließend eine Klasse, so würden die Autoloader nach der Reihe durchlaufen werden bis die Klasse gefunden wurde. Hier gilt „wer zuerst kommt, malt zuerst“ – Soll heißen es würde erst die Funktion des ersten registrierten Autoloaders durchlaufen werden, danach die Zweite, danach die Dritte, …

Autoloader lassen sich übrigens mit Hilfe von spl_autoload_unregister wieder entfernen.

Die meisten Frameworks setzen Autoloader ein damit nur die Dateien inkludiert werden, die auch tatsächlich benötigt werden. Das trägt wesentlich zur performance bei – Stellt euch mal vor was passieren würde wenn große Frameworks wie Beispielsweise „symfony“ oder das „Zend Framework“ keine Autoloader nutzen würden!
Schnell würden pro Request hunderte (wenn nicht tausende) Dateien eingebunden werden – Das zehrt natürlich am Arbeitsspeicher, der Laufzeit und somit auch an den Nerven des Benutzers.

Eine Sache möchte ich noch kurz anmerken: Die Beispiele oben sind relativ schlicht und generisch gehalten, doch es können für bestimmte Klassen natürlich auch vordefinierte Ordner durchsucht werden – Das ganze könnte in etwa so aussehen:

function autoloader($class)
{
    try
    {
        $class = strtolower($class);

        // Macht aus dem Klassennamen einen Pfad.
        if (strpos($class, 'my_') === 0)
        {
            $file = 'classes/my_classes/' . $class . '.php';
        }
        else if (strpos($class, 'helper_') === 0)
        {
            $file = 'classes/helper/' . $class . '.php';
        }
        else if (strpos($class, 'database_'))
        {
            $file = 'database_classes/' . $class . '.class.php';
        }
        else
        {
            $file = 'classes/misc/' . $class . '.php';
        }

        if (file_exists($file))
        {
            // Lädt die Datei der Klasse.
            require $file;

            // Klasse wurde gefunden.
            return true;
        }

        // Die Klasse konnte im Dateisystem nicht gefunden werden.
        return false;
    }
    catch (Exception $e)
    {
        echo $e->getMessage();
        die;
    }
}

Wir möchten nun diese Klassen instanziieren:
My_Demo
My_New_Demo
Helper_Toolkit
Helper_Mail
Database_Instance
Demo_Class

Der Autoloader würde folgende Dateien einbinden:

classes/my_classes/my_demo.php
classes/my_classes/my_new_demo.php
classes/helper/helper_toolkit.php
classes/helper/helper_mail.php
database_classes/database_instance.class.php
classes/misc/demo_class.php

Der Phantasie sind also keine Grenzen gesetzt – Bedenkt nur, das große und komplexe Funktionen entsprechend mehr Zeit zum durchlaufen benötigen als kleine schlichte. Ich denke mit dieser kleinen Einleitung in die Welt der Autoloader seid ihr nun selbst in der Lage einen funktionstüchtigen Autoloader für eure Web-Applikation zu entwickeln!

Mit diesen Worten möchte ich mich hier für das Jahr 2011 abmelden und euch, lieben Lesern, einen guten Rutsch in das Jahr 2012 wünschen! Am 03.Januar werde ich mit einem neuen Posting für euch aufwarten.

Wie immer sind Kommentare, Anmerkungen und weitere Tipps gerne gesehen!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.