Jeder PHP-Entwickler, der sich schonmal durch den Quellcode umfangreicher, objektorientierter Projekte wühlen musste oder Einführungen zu selbigen angesehen hat, wird festgestellt haben dass mittlerweile alle mit den sogenannten Namespaces arbeiten, die mit PHP Version 5.3.0 eingeführt wurden. Weil für jedes Projekt ein eigenener Namensraum definiert werden kann, kommt es so hinterher nicht zu möglichen Namenskollisionen: Ohne eigenen Namespace dürfte jeder Klassenname nur einmal während der Laufzeit vorkommen. Mit intelligent gestalteten Namespaces ist es auch möglich, innerhalb eines Projektes weitere “Unter-”Namespaces zu vergeben. Soweit die Theorie.
Denn ich habe bisher, obwohl ich schon lange objektorientiert arbeite, die Namespaces nie richtig verstanden. Dies liegt zum zum Teil aber auch daran, dass ich noch nie die Motivation hatte, mich damit zu beschäftigen. Jetzt, wo ich an meinem eigenen Framework arbeite, wollte ich dieses nützliche Feature allerdings nicht in meinem Projekt außen vor lassen. Zugegeben, mit der Einführung von PHP Namespaces auf der offiziellen Dokumentationsseite habe ich nicht viel anfangen können, erst durch einige Forenbeiträge und Online-Lektüren konnte ich für mich selbst das “Geheimnis” der Namespaces lüften. Dieses möchte ich euch nicht vorenthalten.
Namensräume in der realen Welt
Vielleicht ist es für einige hilfreich, Namespaces anhand eines Beispiels aus der Realität zu erklären. Hierfür möchte ich einmal zu den Telefonnummern kommen. Innerhalb eines Ortes, sagen wir Bielefeld, ist es nicht nötig, eine Ortsvorwahl zu wählen: Wenn ich im Begriff bin, einen Bekannten anzurufen, der ebenfalls in Bielefeld wohnt, so ist es ausreichend, die Nummer ohne jegliche Vorwahl zu wählen. Möchte ich aber jemanden anrufen, der in München wohnt, so muss ich in Bielefeld die Münchner Vorwahl “030″ angeben, bevor ich seine Nummer in das Telefon eintippe.
Wenn der Bekannte aus München mich anrufen möchte, so muss er vor meiner eigentlichen Nummer die Bielefelder Vorwahl “0521″ eingeben. Diese Zahlenfolge, bestehend aus der Ortsvorwahl und der eigentlichen Rufnummer, ist für jemanden außerhalb Deutschlands allerdings nicht sehr hilfreich. Möchte mich jemand anrufen, der sich beispielsweise in den vereinigten Staaten befindet, so muss er dieser Nummer noch zusätzlich die Landesvorwahl voranstellen: “+49″.
Ein Glück, dass man für ein Telefongespräch innerhalb einer Ortschaft keine Ortsvorwahl wählen muss, oder für ein Telefonat in eine andere Bundesstadt keine Landesvorwahl angeben muss. Aber nur durch diese Telefonnummern-Namensräume ist es möglich, dass sich diese nicht überschneiden. Ohne diese Namensräume hätte vermutlich jeder Besitzer eines Telefonanschlusses eine neunstellige Nummer benötigt. Die Vorwahlen sind demnach also sehr praktisch, bieten uns aber noch einen weiteren Vorteil – denn anhand der Vorwahlen können wir erkennen, wo sich der andere Gesprächspartner befindet.
Wenn ich auf einer Feier eine Person kennenlerne und diese mir ihre Nummer gibt, die mit “05202-xxxx” anfängt, so weiß ich, dass die Bekanntschaft im Nachbarort Oerlinghausen wohnt.
Die Orts- und Landesvorwahlen sind also eigene Namensräume, die analog dazu auch innerhalb eines PHP-Projektes verwendet werden können.
Namensräume definieren
Jeder Telefonanschlussinhaber könnte, portiert in PHP, eine Datei sein. Jeder Entwickler sollte sich daran halten, dass pro Datei nur eine einzige Klasse gespeichert ist. Namensräume machen nur dann Sinn, wenn sie in jeder Datei definiert wurden. Wurde dies nicht gemacht, so landet die Klasse im allgemeinen Namespace, was bei späterer Benutzung nur für Verwirrung sorgt. Namespace-Angaben müssen an den Anfang einer jeden Datei – nicht irgendwo mittendrin. Das macht Sinn – oder ändert ihr eure Vorwahl während eines Telefongespräches?
Bevor ich euch Programmcode zeigen kann, zeige ich euch erstmal die Ordnerstruktur, sowie meine geplante Namespace-Struktur. Ihr werdet feststellen, dass sich bei der Namensgebung dieser beiden Dinge keine Unterschiede ergeben. Das macht es uns später einfacher, den Speicherort von Klassen wiederzufinden.

Hier lässt sich schon erkennen, wie PHP-intern verschachtelte Namespaces getrennt werden – der Backslash ist’s. Die Datei namespace-test.php, die sich im Root-Verzeichnis unseres Projektes befindet, inkludiert Dateien in den Unterordnern. So sieht sie aus:
<?php namespace Test; require_once 'fahrzeuge/PKW.php'; require_once 'fahrzeuge/LKW.php'; require_once 'fahrzeuge/ohne_motor/dreirad.php'; require_once 'flugzeuge/frachtmaschine.php'; require_once 'flugzeuge/privatjet.php'; $temp = new PKW(); ?>
Wie ihr seht, ist ganz oben definiert, um welchen Namensraum es hier überhaupt geht. Gebt den Namensraum immer ohne Hochkommata an. Danach werden statisch die fünf Dateien eingebunden (den Autoloader, der uns dies erspären würde, erkläre ich weiter unten).
Die letzte Zeile möchte ich erstmal unkommentiert lassen und euch stattdessen den Inhalt der Datei “PKW” zeigen.
Datei ‘fahrzeuge/PKW.php‘:
<?php
namespace Test\Fahrzeuge;
class PKW {
public function __construct() {
echo 'Ich könnte ein Kleinwagen sein!';
}
}
?>
Diese recht sinnfreie Klasse gibt einen kurzen Text aus, sobald sie instanziiert wurde, aber darum geht es eigentlich gar nicht. Auch hier wird ganz oben der Namensraum festgelegt. Wichtig ist hierbei, dass man den kompletten Namensraum-Pfad angibt. Da wir im untergeordneten Bereich den Namespace “Test” festgelegt haben, geben wir diesen nun auch mit an. Ich habe mir angewöhnt, den ersten Buchstaben eines Namensraumes groß zu schreiben – das ist aber theoretisch egal.
Hier die restlichen Dateien:
Datei ‘fahrzeuge/LKW.php‘:
<?php
namespace Test\Fahrzeuge;
class LKW {
public function __construct() {
echo 'Ich bin ein großer Lastkraftwagen!';
}
}
?>
Datei ‘fahrzeuge/ohne_motor/dreirad.php‘:
<?php
namespace Test\Fahrzeuge\Ohne_Motor;
class Dreirad {
public function __construct() {
echo 'Ich werde mit Muskelkraft betrieben.';
}
}
?>
Datei ‘flugzeuge/frachtmaschine.php‘:
<?php
namespace Test\Flugzeuge;
class Frachtmaschine {
public function __construct() {
echo 'Ich transportiere große Lasten in der Luft!';
}
}
?>
Datei ‘flugzeuge/privatjet.php‘:
<?php
namespace Test\Flugzeuge;
class Privatjet {
public function __construct() {
echo 'Bei reichen und wichtigen Menschen bin ich beliebt!';
}
}
?>
Namensräume benutzen
Da wir jetzt den Inhalt aller Dateien kennen, wende ich mich nun wieder der root-Datei namespace-test.php zu. Wenn wir diese Datei aufrufen, erhalten wir einen Fehler:
Fatal error: Class ‘Test\PKW’ not found in [...]\htdocs\namespace\namespace-test.php on line 10
Namespace-Neulinge fragen sich jetzt vielleicht “Warum wird die Klasse PKW nicht gefunden? Sie wurde doch inkludiert?”. Wenn man sich die Fehlermeldung genau ansieht, wird klar, warum die Klasse “PKW” nicht gefunden wurden. Genau genommen wurde nämlich versucht, Test\PKW zu instanziieren – oder anders gesagt, in dem Namespace “Test” wurde nach der Klasse “PKW” gesucht. Jetzt schaut nochmal in die Datei “PKW.php”, und schaut was wir für einen Namensraum definiert haben. Fehler erkannt?
Wir modifizieren die Datei namespace-test.php nun geringfügig:
<?php namespace Test; require_once 'fahrzeuge/PKW.php'; require_once 'fahrzeuge/LKW.php'; require_once 'fahrzeuge/ohne_motor/dreirad.php'; require_once 'flugzeuge/frachtmaschine.php'; require_once 'flugzeuge/privatjet.php'; // Beide Instanzen erzeugen das gleiche Ergebnis $test1 = new Fahrzeuge\PKW(); $test2 = new \Test\Fahrzeuge\PKW(); ?>
Als Ausgabe erhalten wir zweimal den Text “Ich könnte ein Kleinwagen sein!”. Wir haben zweimal die gleiche Klasse instanziiert, erst mit einem relativen Pfad, dann mit einem absoluten Pfad. Da wir uns im Namensraum “Test” befinden, müssen wir diesen nicht erst extra definieren (Zeile 11). Genauso gut können wir aber auch einen absoluten Pfad angeben (Zeile 12), bitte beachtet hierbei den führenden Backslash.
Hier noch ein Beispiel:
<?php namespace Test; require_once 'fahrzeuge/PKW.php'; require_once 'fahrzeuge/LKW.php'; require_once 'fahrzeuge/ohne_motor/dreirad.php'; require_once 'flugzeuge/frachtmaschine.php'; require_once 'flugzeuge/privatjet.php'; $test3 = new Flugzeuge\Privatjet(); $test4 = new \Test\Fahrzeuge\Ohne_Motor\Dreirad(); ?>
Wieder haben wir einmal einen relativen Namensraum, dann einen absoluten Namensraum angegeben. Seht ihr, wie schön wir den Speicherort der Dateien zurückverfolgen können? Dies hat aber auch einen Nachteil: Obwohl wir ordnerstrukturtechnisch gesehen nur zwei Ebenen in die Tiefe gingen, haben wir in Zeile 11 einen ganzen Haufen voller Namespaces. Das ist besonders dann sehr nervig, wenn die gleiche Klasse immer wieder eingebunden werden muss und man somit noch mehr Tipparbeit hat.
Zum Glück schafft PHP da mit dem Schlüsselwort use aber Abhilfe.
Namespace-Pfade einbinden
Mit dem use-Keyword können wir PHP dazu veranlassen, im Falle einer nicht vorhandenen Datei einen weiteren Namensraumpfad einzubeziehen, an dem gesucht werden soll.
<?php namespace Test; use \Test\Fahrzeuge\Ohne_Motor\Dreirad; require_once 'fahrzeuge/PKW.php'; require_once 'fahrzeuge/LKW.php'; require_once 'fahrzeuge/ohne_motor/dreirad.php'; require_once 'flugzeuge/frachtmaschine.php'; require_once 'flugzeuge/privatjet.php'; $test5 = new Dreirad(); ?>
In Zeile 3 wurde nun ein weiterer Namensraum angegeben. Nun passiert folgendes: In Zeile 13 wird eine Objekt der Klasse “Dreirad” erzeugt. Zunächst einmal schaut PHP im derzeit definierten Namensraum nach, ob es die Klasse “Dreirad” gibt. Das wäre dann Test\Dreirad(). Hier kann keine Klasse gefunden werden, und ohne unsere “use”-Angabe würde das Script mit einem Fehler beendet werden. Da wir in Zeile 4 aber einen weiteren möglichen Pfad angegeben haben, sieht PHP auch hier nach – und kann die Klasse “Dreirad” erfolgreich instanziieren, da sich an dem oben genannten Namespace-Pfad auch die gesuchte Klasse befindet.
Das Schlüsselwort “use” kann überall im Projekt definiert werden und muss, anders als die Namespace-Angabe selbst, nicht unbedingt am Anfang der Datei stehen. Außerdem können wir auch einen Alias für den einbezogenen Namensraum angeben, in dem wir das Schlüsselwort as benutzen:
<?php namespace Test; use \Test\Fahrzeuge\Ohne_Motor\Dreirad; require_once 'fahrzeuge/PKW.php'; require_once 'fahrzeuge/LKW.php'; require_once 'fahrzeuge/ohne_motor/dreirad.php'; require_once 'flugzeuge/frachtmaschine.php'; use \Test\Flugzeuge\Privatjet as Jet; require_once 'flugzeuge/privatjet.php'; $test5 = new Dreirad(); $test6 = new Jet(); ?>
In Zeile 16 wird die Klasse “Jet” instanziiert, die es so nicht gibt. In Zeile 11 aber gaben wir dem absoluten Namensraum \Test\Flugzeuge\Privatjet aber den Aliasnamen “Jet” und können so auch auf diesen Namen zugreifen.
Weiter gedacht
Weil die Namensräume analog zur Ordnerstruktur definiert sind, können wir einen weiteren Vorteil aus dieser Benennung ziehen – wir können dem PHP-Autoloader ganz einfach mitteilen, wo sich die Datei befindet.
Hierfür zeige ich euch mal meine vorläufe Klasse meines noch in Arbeit befindlichen Frameworks, die für das automatische Laden der Klassen bestimmt ist. Einige Zeilen habe ich für euch mit Erklärungen versehen.
<?php
namespace Shift\Core;
class ShiftLoader {
public static function register() {
spl_autoload_register(array(__CLASS__, 'load')); // Autoloader angeworfen
}
public static function load($class) {
$file = ltrim($class, '\\'); // Evntl. vorangegangenen Backslashes entfernen
$file_array = explode('\\', $file);
array_shift($file_array); // Root-Namespace entfernen
$file = implode(DIRECTORY_SEPARATOR, $file_array);
$path = dirname(__DIR__) . DIRECTORY_SEPARATOR . $file . '.php';
if(file_exists($path)) include $path; // Pfad der Klasse einbinden
else throw new \Exception ($path . " wurde nicht gefunden!");
}
}
In der letzten Zeile sieht man hier sehr schön, wie man innerhalb eines Namensraumes auf allgemeine PHP-Klassen zugreifen kann. Ich musste vor dem Klassennamen “Exception” einen Backslash angeben, um PHP darauf hinzuweisen dass ich den globalen Namespace-Raum ansprechen möchte.
Dieser Autoloader kann (vorerst) erstmal nur Klassen einbinden, die mit einem absoluten Namespace-Pfad angegeben wurden. Der Einfachheit halber möchte ich es hierbei erstmal belassen.
Fazit
Ich hoffe, mein Tutorial konnte euch die Namespaces ein bisschen näherbringen. Wenn die Namensräume der Ordnerstruktur angepasst sind, hat die Nutzung von Namespaces viele Vorteile. Außerdem seit ihr vor Kollisionen geschützt und könnt daher Klassen benutzen, die den gleichen Namen tragen.
Wenn ihr das nächste mal euer Telefon in die Hand nehmt, denkt daran, dass dieses auch nur Teil eines großen Namespace-Netzwerkes ist.
Update:
Für alle Schreibfaulen habe ich die Projektdateien als Download zur Verfügung gestellt. Download (2 KB, rar-Archiv)



Vielen Dank, sehr hilfreich!!
Heißt das, dass man den Verzeichnisbaum an den Namespace-Baum (oder auch Umgekehrt) anpassen soll?
Ja, so würde ich es empfehlen.
Schönes Tutorial,danke!
Nur ein kleines Gemecker: wenn du die Vorwahl 030 wählst, wirst du in München niemanden erreichen; 030 ist Berlin, München hat 089
Wirklich toller erklärter Beitrag zum Thema Namespace :)
Kleine Verständnis Frage: Wäre es nicht besser, wenn der Autoloader keine Exception wirft um anderen Autoloadern die Möglichkeit zu geben die Klasse zu finden?
Ja, dein Einwand ist vollkommen richtig. In einer aktuelleren Version wirft der Autoloader nur dann Exceptions, wenn sie bewusst aktiviert werden. So macht es Zend übrigens auch :)
Und jetzt will ich endlich mal dein Framework sehen ;)