Du verwendest einen Internet Explorer in einer Version kleiner gleich 8. Dieser Browser wird nicht unterstützt. Bitte aktualisiere mindestens auf Version 9.
Patrick Saar - Artikel

Artikel

Patrick Saar

Autoloading mit PHP

Einführung zum Autoloading von Klassen in PHP mit einer Implementierung einer Autoloader-Klasse nach PSR-4 Standard.
Autoloading mit PHP

Wer in PHP bereits mittlere bis größere Projekte umgesetzt hat, kennt das Problem der Einbindung von Code aus anderen PHP-Dateien. Vor allem bei der objektorientierten Programmierung werden in der Regel eine PHP-Datei pro Klasse oder pro Interface erstellt. Um die Funktionalität der Klasse oder die Schnittstellenbeschreibung des Interfaces in anderen PHP-Dateien nun nutzen zu können, müssen die PHP-Dateien, die die Klassen- / Interface-Definitionen enthalten, bekannt gemacht werden.

Nehmen wir im Folgenden eine einfache Klassenstruktur mit einer Basisklasse Vehicle und einer Klasse Car, die von der Basisklasse erbt, an.

Vehicle.php

<?php
class Vehicle {
    public function drive() {
        echo "Drive!";
    }
}

Car.php

<?php
class Car extends Vehicle {
}

Die Ordnerstruktur sieht so aus.

- Classes
-- Model
--- Car.php
--- Vehicle.php
- index.php

In einer weiteren Datei index.php wollen wir nun ein Objekt $car erstellen und eine Methode des Objekts aufrufen.

<?php
$car = new Car();
$car->drive();

Würden wir die Datei index.php nun so im Browser ausführen, käme es zu einem Fatal Error: Class 'Car' not found in index.php on line 2, da die Klasse Car nicht bekannt ist. Wir müssen den Code der Datei Car.php in index.php einbinden. PHP bietet uns dafür diese drei Möglichkeiten:

  1. Einbindung mittels der PHP-Funktionen require() oder include()
  2. Verwendung der magischen PHP-Funktion __autoload
  3. Implementierung eines Autoloaders mithilfe der PHP-Funktion spl_autoload_register()

Einbindung von Code mit require() oder include()

Um die Klassendefinitionen nutzen zu können, binden wir in index.php die Dateien Vehicle.php und Car.php per PHP-Funktion require() ein. Die index.php sieht nun so aus.

<?php
require('Classes/Model/Vehicle.php');
require('Classes/Model/Car.php');

$car = new Car();
$car->drive();

Wichtig ist dabei die Reihenfolge. Da die Klasse Car von Vehicle erbt, muss zuerst Vehicle.php eingebunden werden, denn sonst erhalten wir wieder einen Fatal Error: Class 'Vehicle' not found in Classes/Model/Car.php on line 2. Das zeigt uns schon, dass die Einbindung mittels require() oder include() fehleranfällig ist. Um die Problematik der Abhängigkeiten zu vermeiden, könnten wir auch Vehicle.php ausschließlich im Quellcode von Car.php einbinden. Doch mit wachsender Anzahl an Klassen wird diese Art der Einbindung einerseits unübersichtlich und andererseits wird unser Quellcode ziemlich aufgebläht. Das dachten sich wahrscheinlich auch die Entwickler von PHP und haben in PHP 5 eine magische Funktion __autoload() eingeführt. Da __autoload() seit PHP 7.2 aus gutem Grund als deprecated gekennzeichnet wurde, werde ich nur kurz darauf eingehen.

Die magische PHP-Funktion __autoload()

Von php.net stammt folgendes Zitat:

"Tipp Obwohl die __autoload() Funktion ebenso für das automatische Laden von Klassen und Schnittstellen genutzt werden kann, ist es zu bevorzugen die spl_autoload_register() Funktion zu verwenden. Dies ist so, weil sie eine flexiblere Alternative darstellt (die es ermöglicht eine beliebige Anzahl von Autoladern in der Anwendung zu spezifizieren, wie etwa bei Fremdbibliotheken). Aus diesem Grund wird von der Verwendung von __autoload() abgeraten und diese Funktion könnte zukünftig als veraltet gekennzeichnet werden."

Quelle: http://php.net/manual/de/language.oop5.autoload.php

Im nächsten Abschnitt zeige ich nun wie man eine Autoloader-Klasse mit spl_autoload_register() implementieren kann und was es mit dem im Abstract genannten PSR-4 Standard auf sich hat.

Eine eigene Autoloader-Klasse nach PSR-4 Standard

In PHP 5.3 wurden Namespaces (zu deutsch: Namensräume) eingeführt. Sie dienen einerseits zur besseren Organisation von Projekten mit vielen Klassen und/oder Fremdbibliotheken, und andererseits kann man mithilfe von Namespaces wunderbar einen Autoloader implementieren. Da es in diesem Artikel um den Autoloader geht, werde ich nicht tiefer auf Namespaces eingehen. Wer mehr erfahren möchte, kann sich unter http://php.net/manual/de/language.namespaces.rationale.php weiter einlesen.

Erweitern wir also unsere zwei Klassen um den Namespace Vendor\Model. Unser Quellcode sieht dann so aus.

Vehicle.php

<?php
namespace Vendor\Model;

class Vehicle {
    public function drive() {
        echo "Drive!";
    }
}

Car.php

<?php
namespace Vendor\Model;

class Car extends Vehicle {   
}

Wir verwenden als ersten Teil den String Vendor in unserem Namespace, da dies vom PSR-4 Standard so verlangt wird. Vendor ist in unserem kleinen Beispiel nur ein Platzhalter und sollte in realen Projekten durch den Namen des Anbieters ersetzt werden. Schauen wir uns kurz den PSR-4 Standard genauer an. Der PSR-4 Standard gibt folgendes Schema des Namespaces vor:

\<VendorName>(\<SubNamespaceNames>)+\<ClassName>

Wie gesagt beginnt der Namespace mit dem Vendor Name, gefolgt von mindestens einem bis beliebig vielen Sub-Namespaces, die der weiteren Strukturierung dienen, in unserem Fall Model, und dem Klassennamen, den wir aber nur bei der Initialisierung eines neuen Objekts angeben müssen.

Was hat das nun alles mit unserer Autoloader-Klasse zu tun? Wir können mithilfe der Namespaces einen Bezug zwischen dem Speicherort der PHP-Datei und dem vollqualifizierenden Klassennamen herstellen. Nehmen wir als Beispiel Car.php. Der Speicherort lautet:

/Classes/Model/Car.php

Und der vollqualifizierende Klassenname ist:

\Vendor\Model\Car

Wie man sieht sind der Pfad des Speicherortes und der Klassenname sehr ähnlich. Einzig das Pfadsegment Classes und das Namespace-Präfix Vendor passen nicht zusammen. Der Namespace-Präfix wird ja aber vom PSR-4 Standard gefordert. Wir können daher unseren erste (rudimentäre) Autoloader-Funktion mit spl_autoload_register() implementieren.

<?php
spl_autoload_register(function($class) {
	if (!preg_match('/^' . preg_quote('Vendor') . '/', $class)) {
		// Don't register a class without our namespace's prefix
		return;
	}
	
	$relativeClass = substr($class, strlen('Vendor'));

	$file = 'Classes' . str_replace('\\', DIRECTORY_SEPARATOR, $relativeClass) . '.php';

	if (file_exists($file)) {
		require_once($file);
	}
});

In Zeile 3 überprüfen wir zuerst, ob die zu ladende Klasse unseren Namespace-Präfix Vendor trägt. Falls nicht, verlassen wir die Funktion, ohne die weiteren Schritte auszuführen. Das hat den Vorteil, dass wir Autoloadern von Fremdbibliotheken nicht in die Quere kommen. Wir wollen nämlich nur unsere Klassen laden. Als Nächstes entfernen wir aus dem Klassennamen im String $class den Vendor, da dieser wie bereits gesagt, nicht in unserem Speicherort vorkommt und ergänzen in Zeile 10 das Pfadsegment Classes. Sollten wir PHP unter Windows laufen lassen, so stimmen nun Pfad und Namespace überein. Unter Linux beispielsweise müssen aber noch alle Backlashes (\) im Klassennamen durch Slashes (/) ersetzt werden. Die PHP-Konstante DIRECTORY_SEPARATOR enthält den entsprechenden Pfad-Separator des Betriebssystems. Schließlich wird noch das Datei-Format .php angehängt und wir haben aus dem vollqualifizierenden Klassennamen den Speicherort der PHP-Datei berechnet, welche wir in Zeile 13 einbinden, falls die Datei überhaupt existiert. Zur Einbindung verwenden wir require_once(), um den Code nur einmalig einzubinden.

Wir haben im vorherigen Abschnitt gesehen, dass wir den Vendor Name entfernen und das sogenannte Base Directory Classes hinzufügen mussten. Hier unterscheidet sich der PSR-4 Standard von seinem Vorgänger dem PSR-0 Standard. Im PSR-0 Standard hätte folgender Klassenname zu folgendem Speicherort geführt:

\Vendor\Model\Car -> /Vendor/Model/Car.php

Es gibt im PSR-0 Standard also weder ein Base Directory, noch wird das Namespace-Präfix entfernt. Man kann sagen, dass im PSR-0 Standard die Beziehung zwischen Speicherort und Namespace absolut ist, wohingegen sie beim PSR-4 Standard relativ ist. Weitere Infos zum PSR-4 Standard gibt es unter https://www.php-fig.org/psr/psr-4/.

Schlussendlich wollen wir das Base Directory und den Vendor Name nicht hart codieren. Deshalb erweitern wir den Code um eine Klasse, die diese Informationen in Attributen vorhält.

<?php
class Autoloader {
    /**
     * @type string
     */
    protected $namespacePrefix = '';

    /**
     * @type string
     */
    protected $baseDir = '';

    /**
     * Sets the namespace's prefix.
     * Only classes with this namespace will be autoloaded.
     * 
     * @param string $prefix
     * @return \Autoloader
     */
    public function setNamespacePrefix($prefix) {
        $this->namespacePrefix = $prefix;
        return $this;
    }

    /**
     * Sets the base directory, where we can find our php files.
     * 
     * @param string $dir
     * @return \Autoloader
     */
    public function setBaseDir($dir) {
        $this->baseDir = $dir;
        return $this;
    }

    /**
     * Register our autoloader.
     * 
     * @return void
     */
    public function register() {
        spl_autoload_register(function($class) {
            if (!preg_match('/^' . preg_quote($this->namespacePrefix) . '/', $class)) {
                // Don't register a class without our namespace's prefix
                return;
            }
            
            $relativeClass = substr($class, strlen($this->namespacePrefix));

            $file = $this->baseDir . str_replace('\\', DIRECTORY_SEPARATOR, $relativeClass) . '.php';

            if (file_exists($file)) {
                require_once($file);
            }
        });
    }
}

Fertig ist unsere Autoloader-Klasse. Nun müssen wir in unserer index.php nur noch den Autoloader einbinden und aufrufen. Alle weiteren Klassen werden dann automatisch geladen.

<?php
require_once('autoload.php');

$loader = new Autoloader();
$loader->setNamespacePrefix('Vendor')
       ->setBaseDir('Classes')
       ->register();

$car = new \Vendor\Model\Car();
$car->drive();

Das war ein Einblick in die Grundprinzipen des Autoloading mit PHP und den PSR-4 Standard. Wer einen Schritt weitergehen will, sollte sich mit der Paketverwaltung Composer vertraut machen. Weitere Infos findet Ihr unter https://getcomposer.org.

Diese Seite verwendet Cookies um die beste Nutzerfreundlichkeit zu bieten. Falls Du auf der Seite weitersurfst, stimmst Du der Cookie-Nutzung zu.
Details Ok