mitmario.dev

Namensräume

PHP Sandbox 4 Min Lesezeit 4 BeispieleLektion 5 von 6

In Lektion 9.5 sind Namensräume schon einmal aufgetaucht, weil Composer sie zum Autoladen braucht. Dort haben zwei Zeilen gereicht. Hier steht, was es damit wirklich auf sich hat.

Zwei Klassen dürfen denselben Namen haben

Zwei Klassen, die beide Notiz heissen
<?php

require __DIR__ . "/modell.php";
require __DIR__ . "/import.php";

// Ohne use geht es auch, dann steht der volle Name da.
$eine = new \App\Modell\Notiz("Milch kaufen");
$andere = new \App\Import\Notiz("  Milch kaufen  ");

echo get_class($eine), "\n";
echo get_class($andere), "\n";
echo "Titel: ", $eine->titel, "\n";

Ohne Namensräume gibt es im ganzen Programm genau eine Klasse Notiz. Wer eine zweite einbindet, bekommt beim Einlesen Cannot redeclare class Notiz samt Angabe, wo die erste steht, und das Programm läuft gar nicht erst an. In deinem eigenen Code kannst du dem ausweichen. Sobald ein Paket dabei ist, kannst du es nicht mehr: Du entscheidest ja nicht, wie fremde Klassen heißen.

Ein Namensraum löst das, indem er den Namen verlängert. Steht am Anfang einer Datei namespace App\Modell;, heißt die Klasse Notiz darin mit vollem Namen App\Modell\Notiz. Der Rückstrich trennt die Teile, so wie der Schrägstrich Ordner trennt.

Die Zeile muss die erste Anweisung der Datei sein, direkt hinter <?php, und sie gilt für alles darunter. Eine einzige Ausnahme lässt PHP zu: declare(strict_types=1) aus Lektion 6.2 darf davor stehen, und ab Abschnitt 17 wirst du die beiden Zeilen genau so übereinander sehen.

use kürzt ab, und lädt nichts

Kurze Namen mit use, ein zweiter mit as
<?php

require __DIR__ . "/modell.php";
require __DIR__ . "/import.php";

// use laedt nichts. Es sagt nur: wenn hier unten Notiz steht,
// meine ich diese. Zweimal derselbe kurze Name geht deshalb
// nicht, und dafuer gibt es as.
use App\Modell\Notiz;
use App\Import\Notiz as ImportNotiz;

$eine = new Notiz("Milch kaufen");
$andere = new ImportNotiz("  Milch kaufen  ");

echo get_class($eine), "\n";
echo get_class($andere), "\n";

// Der volle Name steht auch drin, wenn man ihn braucht.
echo Notiz::class, "\n";
echo ImportNotiz::class, "\n";

Volle Namen sind unlesbar, deshalb gibt es use. Die Zeile use App\Modell\Notiz; sagt: Wenn hier unten Notiz steht, meine ich diese.

Zwei Dinge sind daran wichtig. Erstens lädt use nichts. Es ist keine Abkürzung für require, sondern nur eine Umbenennung für diese eine Datei. Wer die Datei nicht eingebunden hat, bekommt trotz use ein Class not found.

Zweitens geht derselbe kurze Name nur einmal. Zwei use-Zeilen, die beide auf Notiz enden, sind ein Fehler beim Einlesen (Cannot use App\Import\Notiz as Notiz because the name is already in use), und genau dafür gibt es as: use App\Import\Notiz as ImportNotiz; vergibt einen zweiten Namen, der nur in dieser Datei gilt.

Der Zusatz ::class gibt dir zu jeder Klasse ihren vollen Namen als Zeichenkette zurück. Praktisch für Protokolle und immer richtig, auch wenn du oben mit use abgekürzt hast. Der Reiter Debug hält es genauso: Dort steht eine=App\Modell\Notiz {titel: …} neben andere=App\Import\Notiz {zeile: …}, und der kurze Name aus der use-Zeile taucht in der Liste nirgends auf.

Der Rückstrich, über den jeder einmal stolpert

Im Namensraum ist Exception nicht mehr Exception
<?php

namespace App\Dienst;

// Eingebaute Klassen liegen im globalen Raum. In einer Datei mit
// Namensraum sucht PHP sie deshalb zuerst nebenan.
try {
    throw new InvalidArgumentException("Leere Zeile");
} catch (\Error $fehler) {
    echo "Gesucht wurde: ", $fehler->getMessage(), "\n";
}

// Mit einem Rueckstrich davor ist klar, wo sie steht.
try {
    throw new \InvalidArgumentException("Leere Zeile");
} catch (\InvalidArgumentException $fehler) {
    echo "Gefangen: ", $fehler->getMessage(), "\n";
}

// Bei Funktionen ist es anders: PHP faellt von selbst auf global
// zurueck, deshalb braucht strlen() keinen Rueckstrich.
echo "Laenge: ", strlen("Milch"), "\n";

Eingebaute Klassen wie Exception, InvalidArgumentException, DateTimeImmutable oder PDO liegen im globalen Namensraum, also in gar keinem. In einer Datei mit namespace App\Dienst; sucht PHP new InvalidArgumentException(...) deshalb zuerst als App\Dienst\InvalidArgumentException, findet nichts und bricht ab.

Der führende Rückstrich sagt „von ganz oben”: new \InvalidArgumentException(...) findet die richtige. Alternativ schreibst du einmal use InvalidArgumentException; an den Dateianfang, dann reicht der kurze Name wieder.

Bei Funktionen ist es anders. strlen(), count() und trim() funktionieren in jedem Namensraum ohne Rückstrich, weil PHP dort von selbst auf den globalen Raum zurückfällt. Nur bei Klassen tut es das nicht. Diese Ungleichheit ist der Grund, warum der Fehler so überrascht: Der halbe Code funktioniert weiter wie gewohnt. Im Terminal passt der Beweis in eine Zeile: php -r 'namespace App; echo strlen("Milch"), "\n"; new Exception("x");' schreibt erst die 5 hin und bricht dann mit Class "App\Exception" not found ab.

Der Namensraum ist der Ordner

Vom Namen zum Dateipfad
<?php

// Dieselbe Rechnung wie in Lektion 9.5, hier nur von der anderen
// Seite betrachtet: der Namensraum ist der Ordner.
spl_autoload_register(function (string $klasse): void {
    $rest = substr($klasse, strlen("App\\"));
    $datei = __DIR__ . "/src/" . str_replace("\\", "/", $rest) . ".php";

    echo "  gesucht in: src/", str_replace("\\", "/", $rest), ".php\n";

    if (is_file($datei)) {
        require $datei;
    }
});

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

echo "Klasse: ", $notiz::class, "\n";
echo "Titel:  ", $notiz->titel, "\n";

Technisch hat ein Namensraum mit Ordnern nichts zu tun. Du dürftest alle Klassen in eine Datei schreiben und ihnen zwanzig verschiedene Namensräume geben.

In der Praxis macht das niemand, und der Grund ist das Autoloading aus Lektion 9.5: PSR-4 verabredet, dass der Namensraum dem Ordner entspricht und der Klassenname dem Dateinamen. Erst dadurch kann ein Autolader aus App\Modell\Notiz den Pfad src/Modell/Notiz.php ausrechnen, ohne irgendeine Liste zu führen.

Im Reiter Debug siehst du die Rechnung in drei Schritten: erst klasse mit dem vollen Namen, dann rest ohne das Präfix, dann datei mit dem fertigen Pfad, und die Rückstriche werden dabei zu Schrägstrichen. Dass es die Datei gibt, sagt dir find src -type f im Terminal.

Halt dich also an die Regel, auch wenn PHP sie nicht erzwingt. Sie ist die Bedingung dafür, dass du nie wieder eine require-Zeile schreiben musst.

Was ein Namensraum nicht ist

Er ist keine Kapselung. private und public regeln, wer etwas anfassen darf; ein Namensraum regelt nur, wie etwas heißt. Von außen kommt man an jede Klasse in jedem Namensraum heran, wenn man ihren vollen Namen hinschreibt.

Und er ist kein Modul: Es gibt nichts, was ein Namensraum lädt, exportiert oder verbirgt. Er ist ein Nachname, nicht mehr, und genau deshalb ist er so leicht zu benutzen, sobald man den Rückstrich einmal verstanden hat.

Zum Mitnehmen

Ab drei Dateien fangen Namen an, sich in die Quere zu kommen. Ein Namensraum ist so etwas wie ein Nachname für deine Klassen. Er hat eine Nebenwirkung, über die jeder einmal stolpert, und deshalb steht in fremdem Code plötzlich ein Rückstrich vor Exception.

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 4 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, auf dem Server geprüft

    Öffnet sich mit dem Basis Konto.