Eines der meistgenutzten Features in PHP ist es, Dateien in Skripte einzubinden. Hierzu bietet PHP eine umfangreiche Auswahl an entsprechenden Funktionen (include, include_once, require, require_once). Jede hat ihre Eigenheiten für die verschiedenen Anwendungsszenarien. Allen gemein ist jedoch das Verhalten bezüglich der Pfade, mit denen man versucht, eine bestimmte Datei einzubinden. Daher werde ich ab jetzt repräsentativ nur noch von „include“ sprechen, meine im jeweiligen Kontext aber auch alle anderen.

Das Thema ist komplexer, als man zunächst meint, daher werden verschiedene Szenarien vorgestellt, in denen ein zu kurz gedachter Ansatz schnell zu Verwirrung und Fehlern führen kann. Solchen Fehlern kann man nur vorbeugen, wenn man versteht, wie PHP die Pfadangaben verarbeitet. Die Szenarien verdeutlichen nach und nach die Lösung und beleuchten etwaige Fallstricke. Und am Ende gebe ich ein Rezept, mit dem man fast immer auf der sicheren Seite ist.
Die einfache Variante

Fangen wir mit einem einfachen Ansatz an, wie er auch in jedem Tutorial oder Lehrbuch genutzt wird:
---------------------------
Auszug aus index.php
---------------------------
include "fileToInclude.php";
Hieran ist erst einmal nichts auszusetzen. Und in einem einfachen kleinen Projekt, das optimalerweise nur aus einem Projektordner ohne Unterordner besteht, wird hier auch nie etwas schiefgehen. Man kann den Ansatz sogar erweitern und Dateien aus Unterordnern einbinden:

---------------------------
Auszug aus index.php
---------------------------
include "subfolder/fileToInclude.php";
Auch das funktioniert absolut fein. Doch gerät diese Variante schnell an die Grenzen ihrer Möglichkeiten, insbesondere wenn das Projekt komplexer wird. Zum Beispiel, wenn man aus einer Datei in einem Unterordner Dateien aus übergeordneten Ordnern einbinden möchte. Oder aus Ordnern, welche nicht direkt in derselben Ordnerhierarchie angeordnet sind.
Auch verbirgt diese Variante eine versteckte Komplexität, die zumindest für Verwirrung sorgen kann, dazu aber später mehr.
Sobald es also knifflig wird, bringt uns diese einfache Variante nicht wirklich weiter. Kommen wir also zur nächsten Variante: Absolute Pfadangaben.
Top-Down: Absolute Pfadangaben
In der Regel kennt man sein Projekt und weiß auch, wo die Dateien liegen. Da ist es doch eine gangbare Lösung, einfach für jede Datei ihren vollständigen Pfad anzugeben, oder?
Versuchen wir es, an dieser Stelle aber noch ein Disclaimer:
Da wir uns hier mit includes beschäftigen, sind mit allen Pfadangaben immer Serverpfade gemeint, also Dateipfade auf dem lokalen Dateisystem. Beim Umgang mit Webpfaden, also URL-Angaben, um z.B. von extern auf eine Ressource zugreifen zu können, gibt es ganz ähnliche, aber auch noch darüber hinaus gehende Herausforderungen. Daher soll dies ein Thema für einen eigenen Beitrag sein.
Also kommen wir zur Sache, fangen wir mit vollständigen Pfadangaben an:
---------------------------
Auszug aus index.php
---------------------------
include "C:/Programme/xampp/htdocs/meinProjekt/subfolder/fileToInclude.php";
Kann man machen und würde auch funktionieren. Wenn da nicht eine Sache wäre: Der Unterschied zwischen der lokalen Entwicklungsumgebung und der Produktivumgebung auf dem Server.
Die lokale Entwicklungsumgebung ist der eigene Computer. Hier läuft auf der lokalen Festplatte ein Webserver, der es ermöglicht, seine Arbeit direkt zu testen, ohne sie erst hochladen zu müssen. Die Webinhalte liegen hier in einem speziellen Unterordner, dem Stammverzeichnis des Webservers (z.B. htdocs).
Nun ist aber der Pfad zu eben diesem Stammverzeichnis abhängig von der jeweiligen Installation. Es wäre reiner Zufall, wenn der Pfad zum Webserver auf dem Produktiv-Server zufällig der gleiche wäre. Allein der Umstand, dass der eigene Rechner in der Regel ein Windows-PC oder ein Mac ist, während der Produktiv-Server meist auf Linux basiert, sorgt hier schon in den allermeisten Fällen für Abweichungen.
Also gut, vollständig ausgeschriebene Pfade sind keine gute Idee. Gibt es eine Alternative? Die gibt es:
---------------------------
Auszug aus index.php
---------------------------
include $_SERVER['DOCUMENT_ROOT']."/meinProjekt/subfolder/fileToInclude.php"
Diese Variante ist schon recht robust. Der Zugriff auf $_SERVER[‚DOCUMENT_ROOT‘] bewirkt, dass egal auf welcher Umgebung wir uns befinden, das aktuelle Wurzelverzeichnis angesprochen wird. Dies ist bei xampp z.B. der Ordner htdocs. Im Idealfall muss man jetzt nur noch den jeweiligen Projektordner anhängen und sich entsprechend seiner Dateistruktur entlang hangeln, um eine gewünschte Datei referenzieren zu können. Das würde in vielen Fällen auch schon genau so funktionieren.
Doch auch hier gibt es noch eine Sache zu beachten, die man bei der Benutzung dieses Ansatzes im Hinterkopf behalten sollte:
Was genau das Wurzelverzeichnis ist, hängt von der Server-Konfiguration ab, hier kann es kleine aber feine Unterschiede geben. In manchen Fällen ist es das Stammverzeichnis der Webserver-Installation. Bei Verwendung einer Shared-Hosting Lösung kann es auch ein dir zugeordnetes Unterverzeichnis sein. So weit, so gut. Und so egal. Hauptsache, was darauf folgt, bleibt gleich. Aber hier kommts:
Es kann auch sein, dass der Server so konfiguriert ist, dass als Wurzelverzeichnis tatsächlich schon der spezifische Unterordner der jeweiligen Domain ausgegeben wird. In dem Fall würde man sein Projekt nicht noch in einem Unterverzeichnis unterbringen, sondern eben direkt in dem dafür vorgesehenen Ordner, damit die Seite nach außen korrekt aufrufbar ist.
Document Root lokaler Server:
-----------------------------
C:/Programme/xampp/htdocs
Document Root Produktiv-Server:
------------------------------
C:/Programme/xampp/htdocs/MeinProjekt
Es sind auch andere Szenarien denkbar. Wichtig ist, dass man sich vor dem Veröffentlichen der Seite auf der Produktivumgebung darüber im Klaren ist und entsprechende Vorkehrungen trifft.
Üblicherweise begegnet man dem Problem damit, dass man eine eigene Konstante für das Document-Root in einer Konfigurationsdatei angibt und diese überall beim Einbinden von Dateien verwendet:
// Entwicklungsumgebung
define('ROOT', $_SERVER['DOCUMENT_ROOT']."/MeinProjekt");
// Produktivumgebung
define('ROOT', $_SERVER['DOCUMENT_ROOT']);
// Include
include ROOT . "/fileToInclude.php";
Alles, was man dann beachten muss, ist vor dem Veröffentlichen der Seite auf dem Produktivsystem die Konstante entsprechend anzupassen. Beachtet man das, hat man eine sehr solide und dank der absoluten Pfadangaben auch übersichtliche Lösung. Man muss halt nur daran denken, beim Deployment der Seite vorher die Konstante anzupassen.
Vorsicht mit /
Hier noch ein Hinweis: Vorsicht bei der Verwendung von einem vorangestellten / im include-String. Zwar wird dadurch, wie bei Webpfaden (URLs) der include-String zu einer absoluten Pfadangabe, jedoch verweist das / in dem Fall nicht auf das Wurzelverzeichnis (der Domain), wie es bei einer URL (im Idealfall) der Fall wäre. Sondern es verweist unter Umständen (je nach Serverkonfiguration) auf das Laufwerk, auf dem sich die Webserver-Installation befindet. Es ersetzt im o.g. Beispiel quasi lediglich die Angabe „C:/“. Will man in PHP das DOCUMENT_ROOT ansprechen, muss man daher die entsprechende Server-Variable verwenden.
Bottom-Up: Relative Pfadangaben mittels Steuerzeichen
Ein anderer Weg, Dateien zu referenzieren, ist mithilfe relativer Pfadangaben. Diese Variante hat den Vorteil, dass sie den Code portabler macht. Denn normalerweise muss man bei dieser Variante keine Pfad-Anpassungen beim Deployment auf den Server machen. Weil sich die relativen Pfade der Dateien, unabhängig von der Umgebung, in der sie aufgerufen werden, nicht verändern.
Man muss jedoch beachten: Relative Pfadangaben sind etwas schwierig im Handling, da es bei den normalen relativen Pfadangaben einige unerwartete Fallstricke gibt, die es zu beachten gilt. Ein weiterer Nachteil ist die etwas schlechtere Lesbarkeit.
Aber eins nach dem anderen. Gehen wir noch einmal auf den einfachen Ansatz vom Anfang zurück.
Es folgen jetzt eine Reihe von Szenarien, welche die verschiedenen Varianten Schritt für Schritt herleiten und ihre Funktion verdeutlichen sollen. Uns interessiert hier speziell, ob die Datei fileToInclude.php erfolgreich eingebunden werden kann.
Szenario 1:
index.php wird vom Client aufgerufen. Innerhalb von index.php wird die Datei script.php aus dem Unterordner eingebunden. Innerhalb der Datei script.php wird wiederum die Datei fileToInclude.php eingebunden.

--------------------
Auszug aus index.php
--------------------
include "unterordner/script.php";
---------------------
Auszug aus script.php
---------------------
include "fileToInclude.php";
Klappts? – Klappt. Soweit alles wie erwartet. Verändern wir unseren Projektaufbau etwas.
Szenario 2:
index.php wird wieder vom Client aufgerufen. Die Includes bleiben die gleichen, allerdings ist die Datei fileToInclude.php nun in den übergeordneten Projektordner verschoben worden.

--------------------
Auszug aus index.php
--------------------
include "unterordner/script.php";
---------------------
Auszug aus script.php
---------------------
include "fileToInclude.php";
Klappts? – Wieder ja. Aber warum? Wir haben doch nichts am Code verändert?! Und jetzt liegt fileToInclude.php doch im übergeordneten Verzeichnis, warum kann ich die Datei dennoch ohne eine korrigierte Pfadangabe referenzieren?
Das liegt an der Weise wie include versucht aus der Dateinangebe einen Pfad zu erstellen:
- Wird im konfigurierten include_path nachgeschaut.
- Wenn dort keine Datei mit dem Namen zu finden ist, wird im selben Verzeichnis geschaut, in dem das einbindende Skript selbst liegt. (Szenario 1)
- Wenn auch dort keine Datei mit dem Namen auffindbar ist, wird schlussendlich im aktuellen Arbeitsverzeichnis (current working directory) nachgeschaut. Das ist das Verzeichnis, in dem das Skript liegt, das gerade aktiv ausgeführt wird. Also das Skript, das vom Client angefragt wurde. (Szenario 2)
Szenario 3:
Machen wir mit der letzten Konfiguration noch ein drittes Szenario, diesmal ruft der Client aber direkt script.php auf. Die Dateien befinden sich noch in denselben Ordnern wie im vorherigen Szenario.
---------------------
Auszug aus script.php
---------------------
include "fileToInclude.php";
Klappts? – Diesmal nicht. Es kommt eine Fehlermeldung. Denn diesmal wurde script.php direkt aufgerufen, sodass „Unterordner“ das aktuelle Arbeitsverzeichnis ist, welches in dem Fall identisch ist, mit dem Verzeichnis, in dem das einbindende Skript liegt. Es wird daher gar nicht versucht, im Projektordner nach der Datei zu suchen. Und hier kommen relative Pfadangaben ins Spiel:
Szenario 4:
Der Client ruft wieder script.php auf. Die Dateien bleiben in denselben Ordnern wie in den vorherigen Szenarien. Diesmal ergänzen wir aber 3 Zeichen in der Include-Anweisung.
---------------------
Auszug aus script.php
---------------------
include "../fileToInclude.php";
Klappts? – JA! Mittels des voran gestellten ../ sorgen wir dafür, dass explizit im übergeordneten Verzeichnis nach der Datei gesucht werden soll. Würde man noch eine höhere Ebene suchen wollen, könnte man ../../ prinzipiell beliebig oft wiederholen.
Die Grenze ist lediglich der DocumentRoot, hierüber hinaus kann man keine Verzeichnisse ansteuern.
Szenario 5:
Der Client ruft wieder script.php auf. Wir erweitern den Versuchsaufbau aber etwas: Wir haben eine zweite fileToInclude.php-Datei angelegt. Der Dateiname ist bewusst der gleiche. Die Datei liegt aber im Unterordner, sodass es zu keinem Namenskonflikt im Dateisystem kommt.

---------------------
Auszug aus script.php
---------------------
include "../fileToInclude.php";
Welche Datei wird aufgerufen? – Richtig: Die aus dem Projektordner. Wir haben schließlich die Datei explizit mittels vorangestelltem ../ angesteuert.
Wenn man die Datei aus dem gleichen Ordner ansteuern möchte kann man das mittels ./ tun:
---------------------
Auszug aus script.php
---------------------
include "./fileToInclude.php";
Hier wird die Datei aus dem Unterordner eingebunden. Könnte man dazu nicht einfach das ./ auch weglassen? – Ja, das könnte man, in diesem Fall. Aber sollte im include-path eine solche Datei mit angegeben sein, würde diese dann aufgrund der Such-Reihenfolge den Vorzug bekommen. Möchte man also sicher gehen, gibt man ./ mit an (clean code).
Gibt es sonst noch was? Ja, sogar eine wichtige Sache.
Szenario 6:
Der Client ruft diesmal wieder index.php auf.

--------------------
Auszug aus index.php
--------------------
include "./unterordner/script.php";
---------------------
Auszug aus script.php
---------------------
include "../fileToInclude.php";
Die Datei index.php bindet script.php aus dem Unterverzeichnis ein, und diese wiederum versucht fileToInclude.php wieder mittels ../ aus dem übergeordneten Verzeichnis einzubinden. Könnte klappen, oder? – Tut es aber nicht. Und warum?
Es liegt am Kontext des aktuellen Aufrufs. Die Präfixe ./ und ../ beziehen sich immer auf das aktuelle Arbeitsverzeichnis, nicht auf das Verzeichnis, in dem das einbindende Skript liegt. Während include allein noch recht großzügig mit seinem Suchraum ist, ist es das mit einer explizit relativen Pfadangabe nicht mehr.
In diesem Fall ist es so, als ob wir den übergeordneten Ordner des Ordners MeinProjekt ansprechen wollten. Dieser liegt in dem Fall jedoch außerhalb des Projekts und enthält jedenfalls nicht die gesuchte Datei.
Was sagt uns das? Auch mit relativen Pfadangaben muss man aufpassen. Denn je nach Aufruf-Kontext kann es hier zu unterschiedlichen Verhalten kommen.
Hier ist es also wichtig, dass man stets im Hinterkopf behält, aus welchem Kontext eine Datei aufgerufen wird. Wird sie in einer anderen Datei eingebunden? Wird sie direkt aufgerufen? Oder wird an sie weitergeleitet? – Das alles sollte man in seiner Projekt-Konzipierung mit einbeziehen und bei den relativen Pfadangaben beachten, damit es nicht später zu unvorhergesehenem und unerwünschtem Verhalten kommt.
So, und war es das jetzt? Ich habe also die Wahl zwischen einer Variante, bei der mir bei jedem Deployment das Herz in die Hose rutscht, weil ich vergessen habe, die config-Datei an die Serverumgebung anzupassen und dadurch das Projekt gecrasht ist? Oder einer Variante, bei der gefühlt zufällig entschieden wird, ob die Datei aus dem include-Path, dem aktuellen Arbeitsverzeichnis oder dem Verzeichnis des einbindenden Skripts ausgewählt wird? Oder einer Variante, bei der je nachdem, wer oder was ein Skript gerade aufruft, das gesamte Programm crasht, oder nicht? Vielleicht doch lieber Java fürs Backend?
Natürlich habe ich das jetzt absichtlich krass überspitzt dargestellt. Solange man weiß, was man tut, und die entsprechenden Regeln, Einschränkungen und Fallstricke beachtet, funktioniert sowohl der Ansatz mit absoluten Pfaden als auch der mit relativen Pfaden sehr zuverlässig. Beide Ansätze finden in der Praxis im breiten Spektrum Anwendung. Und die einfache Methode funktioniert, wie meine Bezeichnung suggeriert, bei einfachen Projekten ebenfalls hervorragend.
Aber es gibt noch einen weiteren Weg, der die Vorteile der Robustheit von absoluten Pfaden mit den Vorteilen der Portabilität von relativen Pfaden verbindet.
Der Hybrid: Relative Pfadangaben mittels absoluter Konstanten: __DIR__
Es gibt in PHP die magische Konstante DIR. Diese enthält den absoluten Pfad zu dem Verzeichnis, in dem das Skript liegt, das diese Konstante gerade abfragt, unabhängig davon, ob das Skript selbst per include an höherer Stelle eingebunden wurde oder direkt vom Client aufgerufen wurde.
Darüber hinaus gibt es die nützliche Funktion dirname(). Diese Funktion gibt den Pfad des übergeordneten Verzeichnisses eines gegebenen Pfads aus.
Kombiniert man diese beiden, kann man das Verhalten von ./ und ../ nachbilden, jedoch, ohne dass das Verhalten vom Kontext des aktuellen Aufrufs abhängig ist.
__DIR__ entspricht dabei ./
dirname(__DIR__) entspricht ../
Es ist übrigens, wie bei ../ sogar möglich, die Aufrufe zu schachteln:
dirname(dirname(__DIR__))
Szenario 7:
Aufruf von index.php und dann script.php hintereinander. Die fileToInclude.php-Datei liegt wie im vorherigen Szenario im übergeordneten Projektordner.
--------------------
Auszug aus index.php
--------------------
include __DIR__ . "/unterordner/script.php";
---------------------
Auszug aus script.php
---------------------
include dirname(__DIR__) . "/fileToInclude.php";
Und funktioniert es? Beide Aufrufe, ja! – Referenziert man Pfade auf diese Weise, werden sie immer funktionieren, solange es sich um gültige Pfade handelt. Unabhängig davon, ob das Skript lokal ausgeführt wird oder auf einem beliebigen Server.
Conclusion
Es gibt natürlich auch andere Wege, das gewünschte Verhalten zu erzielen. Die hier gezeigten Möglichkeiten bilden eine Sammlung von Varianten ab, welche recht gut funktionieren und die in der Praxis eine weite Verbreitung haben. Es mag aber durchaus Szenarien geben, in denen die gezeigten Möglichkeiten an ihre Grenzen stoßen.
Wenn du eines aus dem Lesen dieses Beitrags mitnimmst, dann das: Das Wichtigste ist, dass du dir stets dessen bewusst bist, was du gerade tust, und unter welchen Bedingungen der Code ausgeführt werden wird, den du gerade schreibst. Andernfalls kann es passieren, dass du Tage und Wochen mit der Entwicklung einer tollen Webanwendung verbringst, die nach dem Deployment plötzlich nicht mehr funktioniert.
Kurzreferenz:
// --------------------------------------------
// Include-Methoden
// --------------------------------------------
include "file.php"; // Lädt die Datei und gibt eine Warnung aus, wenn die Datei nicht gefunden wird.
include_once "file.php"; // Lädt die Datei und gibt eine Warnung aus, wenn die Datei nicht gefunden wird. Wenn die Datei bereits geladen wurde, wird sie nicht erneut geladen.
require_once "file.php"; // Lädt die Datei und bricht das Skript ab, wenn die Datei nicht gefunden wird. Wenn die Datei bereits geladen wurde, wird sie nicht erneut geladen.
require "file.php"; // Lädt die Datei und bricht das Skript ab, wenn die Datei nicht gefunden wird.
// --------------------------------------------
// Einfache Pfade
// --------------------------------------------
include "file.php"; // Lädt die Datei entweder aus dem include_path, dem aktuellen Arbeitsverzeichnis oder dem Verzeichnis, in dem sich die aufrufende Datei befindet.
// --------------------------------------------
// Absolute Pfade
// --------------------------------------------
define("PROJECT_ROOT", $_SERVER['DOCUMENT_ROOT']."/MyProject"); // Definiert die Konstante PROJECT_ROOT als das Wurzelverzeichnis des Projekts. Kann auch in einer Konfigurationsdatei definiert werden, und vor dem Laden der Datei eingebunden werden. Beim Deployment muss der Pfad ggf. angepasst werden.
include PROJECT_ROOT."/file.php"; // Läd die Datei aus dem Projektverzeichnis.
include PROJECT_ROOT."/controller/file.php"; // Läd die Datei aus dem Controller-Verzeichnis des Projekts.
// --------------------------------------------
// Relative Pfade:
// --------------------------------------------
include "./file.php"; // Lädt die Datei aus dem aktuellen Arbeitsverzeichnis.
include "../file.php"; // Lädt die Datei aus dem übergeordneten Verzeichnis.
include "../../file.php"; // Lädt die Datei aus dem über-übergeordneten Verzeichnis.
include "../neighbourDirectory/file.php" - Lädt die Datei file.php aus einem Nachbarverzeichnis. Man wechselt also erst eine Ebene höher mit ../ und geht mit der Ordnerangabe wieder eine Ebene tiefer in einen Nachbarordner.
// --------------------------------------------
// Hybride Pfade:
// --------------------------------------------
include __DIR__."/file.php"; // Lädt die Datei aus dem Verzeichnis, in dem sich die aufrufende Datei befindet.
include dirname(__DIR__)."/file.php"; // Lädt die Datei aus dem übergeordneten Verzeichnis des Verzeichnisses, in dem sich die aufrufende Datei befindet.
include dirname(dirname(__DIR__))."/file.php"; // Lädt die Datei aus dem über-übergeordneten Verzeichnis des Verzeichnisses, in dem sich die aufrufende Datei befindet.
// --------------------------------------------
// Nützliche Funktionen
// --------------------------------------------
echo getcwd(); // Gibt das aktuelle Arbeitsverzeichnis (current working directory) aus.
echo basename($path); // Gibt den Dateinamen aus einem Pfad zurück.
echo dirname($path); // Gibt das Verzeichnis aus einem Pfad zurück.
echo realpath($path); // Gibt den absoluten Pfad einer Datei zurück.
echo file_exists($path); // Gibt true zurück, wenn die Datei existiert.
echo is_file($path); // Gibt true zurück, wenn der Pfad auf eine Datei zeigt.
echo is_dir($path); // Gibt true zurück, wenn der Pfad auf ein Verzeichnis zeigt.
$result = scandir($path); // Gibt ein Array mit den Dateien und Verzeichnissen im Verzeichnis zurück.
print_r($result); // Gibt das Array aus.