Abschnitt 13 · Lektion 5
Namensräume
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
<?php
namespace App\Modell;
class Notiz
{
public function __construct(public readonly string $titel)
{
}
} <?php
namespace App\Import;
// Derselbe kurze Name, anderer Namensraum. Ohne die zwei Zeilen
// oben waere das ein Abbruch beim Einlesen: Cannot redeclare
// class Notiz.
class Notiz
{
public function __construct(public readonly string $zeile)
{
}
} <?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
<?php
namespace App\Modell;
class Notiz
{
public function __construct(public readonly string $titel)
{
}
} <?php
namespace App\Import;
class Notiz
{
public function __construct(public readonly string $zeile)
{
}
} <?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
<?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
<?php
namespace App\Modell;
class Notiz
{
public function __construct(public readonly string $titel)
{
}
} <?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, kostenlosZu 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.