mitmario.dev

Eigener Code mit Autoloading

PHP Sandbox 4 Min Lesezeit 3 BeispieleLektion 5 von 6

Bis hierher hattest du eine Datei, und wenn du eine zweite brauchtest, hast du sie mit require eingebunden. Bei fünf Dateien geht das noch. Bei fünfzig nicht mehr, und spätestens dann bindest du irgendwo dieselbe Datei zweimal ein und bekommst „Cannot redeclare”.

Namensräume, so weit man sie hier braucht

Ein Namensraum ist ein Vorname für deine Klassen. Steht am Anfang einer Datei die Zeile namespace App\Modell;, dann heißt die Klasse Notiz in dieser Datei mit vollem Namen App\Modell\Notiz. Der Rückstrich trennt dabei die Teile, so wie der Schrägstrich Ordner trennt.

Das löst ein Problem, das du im Kurs bisher nicht hattest, in einem Projekt mit Paketen aber sofort bekommst: Zwei Klassen dürfen Notiz heißen, deine und die aus einem Paket, solange ihre Namensräume verschieden sind. Ohne Namensräume gäbe es eine Notiz im ganzen Programm, und wer sie zuerst einbindet, gewinnt.

Mit use App\Modell\Notiz; oben in einer Datei sagst du: Wenn ich hier unten Notiz schreibe, meine ich diese. Alles Weitere zu Namensräumen steht in Lektion 13.5. Für diese Lektion reichen die zwei Zeilen.

Autoloading ist eine Zeichenkettenersetzung

Ein Autolader von Hand
<?php

// spl_autoload_register meldet eine Funktion an, die PHP genau dann
// fragt, wenn es eine Klasse noch nicht kennt. Mehr steckt hinter
// Autoloading nicht.

spl_autoload_register(function (string $klasse): void {
    $praefix = "App\\";

    if (!str_starts_with($klasse, $praefix)) {
        return;
    }

    $rest = substr($klasse, strlen($praefix));
    $datei = "src/" . str_replace("\\", "/", $rest) . ".php";

    echo "  gefragt nach: ", $klasse, "\n";
    echo "  gesucht in:   ", $datei, "\n";

    if (is_file(__DIR__ . "/" . $datei)) {
        require __DIR__ . "/" . $datei;
    }
});

echo "Vor der ersten Benutzung ist noch nichts geladen.\n";

$notiz = new App\Modell\Notiz();

echo "Geladen: ", get_debug_type($notiz), "\n";
echo "Inhalt:  ", $notiz->text, "\n";

Hier steht kein Composer, sondern ein Autolader von Hand, und ohne die zwei echo-Zeilen, die es nur zum Zusehen gibt, sind es vierzehn Zeilen. spl_autoload_register() meldet eine Funktion an, und PHP ruft sie genau dann auf, wenn eine Klasse gebraucht wird, die es noch nicht kennt. Die Funktion bekommt den vollen Klassennamen und hat eine einzige Aufgabe: die passende Datei einbinden.

Der Weg vom Namen zum Pfad ist die ganze Regel:

  1. Fängt der Klassenname mit App\ an? Wenn nicht, ist diese Funktion nicht zuständig.
  2. Präfix abschneiden, aus jedem Rückstrich einen Schrägstrich machen, .php anhängen.
  3. Vorn den Ordner davor, der zum Präfix gehört.

Aus App\Modell\Notiz wird so src/Modell/Notiz.php. Genau das ist PSR-4, und das steckt hinter dem Kürzel: eine Absprache darüber, wie ein Klassenname in einen Dateipfad übersetzt wird. Sie ist so einfach, weil sie einfach sein muss: Alle Pakete halten sich daran, und ein einziger Autolader kann deshalb für alle zuständig sein.

Sieh dir die Reihenfolge in der Ausgabe an. Die erste Zeile steht da, bevor der Autolader überhaupt gefragt wurde. Er wird erst beim new gerufen, und nur dann. Wer zwanzig Klassen im Projekt hat und in einem Aufruf zwei davon benutzt, lädt zwei Dateien.

Im Reiter Debug siehst du diesen Sprung als Weg durch die Datei: Nach der Zeile mit new geht es nach oben in die Funktion, klasse steht mit dem vollen Namen da, dann kommen rest und datei dazu, und nach dem require geht es unten mit der Zeile Geladen: weiter. Das ist die ganze Übersetzung, Schritt für Schritt, an dem Namen, den PHP gerade gefragt hat.

Und warum die Groß- und Kleinschreibung zählt

Der Dateiname zählt
<?php

spl_autoload_register(function (string $klasse): void {
    $praefix = "App\\";

    if (!str_starts_with($klasse, $praefix)) {
        return;
    }

    $rest = substr($klasse, strlen($praefix));
    $datei = "src/" . str_replace("\\", "/", $rest) . ".php";

    echo "  gesucht in: ", $datei, "\n";

    if (is_file(__DIR__ . "/" . $datei)) {
        require __DIR__ . "/" . $datei;
    }
});

$notiz = new App\Modell\Notiz();

echo "Hierher kommt das Programm nicht mehr.\n";

Dieselbe Klasse, dieselbe Regel, nur heißt die Datei notiz.php statt Notiz.php. Der Autolader sucht nach src/Modell/Notiz.php, findet nichts, und PHP bricht mit Uncaught Error: Class "App\Modell\Notiz" not found ab; php index.php; echo $? im Terminal zeigt die 255 darunter. Dass es wirklich nur am Namen liegt, beweist ein einziger Befehl: mv src/Modell/notiz.php src/Modell/Notiz.php, dann noch einmal php index.php, und das Programm läuft bis zur letzten Zeile durch. Der nächste Prüflauf legt die Datei wieder klein geschrieben hin, so wie sie im Editor steht.

Auf einem Windows-Rechner läuft dasselbe Projekt, weil dort notiz.php und Notiz.php dieselbe Datei sind. Auf einem Linux-Server nicht, und das ist der Klassiker unter den Fehlern, die erst beim Hochladen auftauchen. Die Regel dagegen ist einfach: Die Datei heißt genau wie die Klasse darin, und der Ordner genau wie der Namensraum.

Dieselbe Arbeit, von Composer erledigt

Dasselbe von Composer
<?php

// Eine einzige Zeile, und danach ist beides da: das fremde Paket
// und der eigene Code aus src/.
require __DIR__ . "/vendor/autoload.php";

$notiz = new App\Modell\Notiz("Milch kaufen");

echo "Klasse:  ", get_debug_type($notiz), "\n";
echo "Text:    ", $notiz->text, "\n";
echo "Kennung: ", $notiz->kennung, "\n";

In der Praxis schreibt niemand den Autolader von Hand. Man trägt die Regel in die composer.json ein, in einen Block autoload, darin einen Block psr-4, und darin steht die Zuordnung selbst: links das Präfix "App\\", rechts der Ordner "src/". Im Beispiel oben kannst du sie in der composer.json nachlesen.

Die zwei Rückstriche hinter App sind eine Eigenheit von JSON, nicht von PHP: Ein Rückstrich leitet dort Sonderzeichen ein, ein echter muss deshalb verdoppelt werden. Gemeint ist einer.

Danach lädt vendor/autoload.php beides: die Pakete und deinen eigenen Code. Im Beispiel benutzt die eigene Klasse App\Modell\Notiz das fremde ramsey/uuid, und in index.php steht trotzdem nur eine einzige require-Zeile.

Wenn du den Block nachträglich einträgst, muss Composer die Autoload-Dateien einmal neu erzeugen. Der Befehl dafür heißt composer dump-autoload. In diesem Kurs fährt ihn die Plattform bei jedem Lauf selbst, gleich nachdem sie das Paket ausgepackt hat, deshalb musst du ihn hier nie tippen. Kannst du aber, er braucht kein Netz: Er meldet Generating autoload files, und cat vendor/composer/autoload_psr4.php zeigt danach, was dabei entstanden ist. Ganz unten steht deine Zuordnung, 'App\\' => array($baseDir . '/src'), über ihr die drei Zeilen der Pakete. Nimm den Block aus der composer.json heraus und tipp php index.php: Class "App\Modell\Notiz" not found, dieselbe Meldung wie beim falschen Dateinamen, diesmal weil Composer den Ordner src/ gar nicht kennt. Das Paket daneben lädt weiter, nur dein eigener Code nicht mehr.

Die Klassen im Beispiel kennst du aus Lektion 6.7, neu sind hier nur die zwei Zeilen darüber: der Namensraum und die Einfuhr. Worauf es ankommt, ist die Frage, ob der Pfad zum Namen passt. In Abschnitt 17 lädt dein eigenes src/ genau so, ohne eine einzige require-Zeile, und dort ist es dann dein Code statt fremder.

Zum Mitnehmen

Autoloading ist keine Erfindung für Pakete. Es funktioniert für deinen eigenen Code genauso, und sobald es eingerichtet ist, hörst du auf, require-Zeilen zu schreiben.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.

Was in dieser Lektion steckt

  • Artikel mit 3 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, auf dem Server geprüft

    Öffnet sich mit dem Basis Konto.