Marc Ermshaus’ avatar

Marc Ermshaus

Linkblog

PHP-Includes: Niemals ohne __DIR__

Published on 30 Sep 2012. Tagged with php.

Includes sind ein Grundbaustein von PHP-Anwendungen, ohne den eine sinnvolle Gliederung des Codes kaum möglich ist. Selbst moderne Frameworks mit Autoloading kommen nicht ohne mindestens einen entsprechenden Befehl aus, ĂŒber den weitere Skript-Ressourcen eingebunden werden (include, include_once, require, require_once). Hinter Include-Konstrukten steckt aber auch ein gewisses Maß an FunktionalitĂ€t, das nicht immer völlig trivial zu durchschauen ist und das verschiedene Vorgehensweisen bei der Anwendung zulĂ€sst.

Include-Anweisungen sind Sprachkonstrukte. Sie lassen im Unterschied zu Funktionen nur ein einziges „Argument“ zu, den Pfad der einzubindenden Ressource. SĂ€mtliche Informationen, die fĂŒr den Include-Vorgang relevant sind, mĂŒssen somit entweder in diesem Pfad enthalten sein oder mĂŒssen global fĂŒr die Umgebung konfiguriert werden (beispielsweise ĂŒber die Zusammensetzung des Include-Pfads). Diese globalen Konfigurationen können zwar zumeist von konkreten Anwendungen ĂŒberschrieben werden. Bei Libraries oder allgemein bei Software, die ein Anwender selbst auf einem Zielsystem installiert, ist es aber nicht immer unproblematisch, auf korrekte Voreinstellungen zu setzen.

Ablauf von Includes

Die PHP-Dokumentation beschreibt, was bei einem Include passiert:

Files are included based on the file path given or, if none is given, the include_path specified. If the file isn’t found in the include_path, include will finally check in the calling script's own directory and the current working directory before failing. [
]

If a path is defined — whether absolute [
] or relative to the current directory [
] — the include_path will be ignored altogether. For example, if a filename begins with ../, the parser will look in the parent directory to find the requested file.

Einige Definitionen dazu:

absoluter Pfad
Ein absoluter Pfad ist ein Pfad, der mit / oder einem Laufwerksbuchstaben (Windows) beginnt, beispielsweise /includes/test.php oder c:/includes/test.php.
relativer Pfad
Ein relativer Pfad ist ein Pfad, der mit ./ oder ../ beginnt, beispielsweise ./includes/test.php oder ../includes/test.php.
Dateipfad
Ein Dateipfad (file path) ist hier ein absoluter oder relativer Pfad.
virtueller Pfad
Ein virtueller Pfad ist hier in Abgrenzung dazu ein Pfad, der kein Dateipfad ist, der also nicht absolut oder relativ ist. Ein Beispiel ist der Pfad includes/test.php.
Arbeitsverzeichnis
Das Arbeitsverzeichnis (working directory) ist ĂŒblicherweise das Verzeichnis, in dem die erste aufgerufene PHP-Datei liegt. Das Verzeichnis Ă€ndert sich auch dann nicht, wenn diese Datei weitere Dateien aus Unterverzeichnissen inkludiert.
Skriptverzeicnis
Das Skriptverzeichnis ist aus Sicht einer konkreten PHP-Datei das Verzeichnis, in dem sich diese Datei befindet. FĂŒr eine inkludierte Datei ist es das Verzeichnis, aus dem die Datei inkludiert wird. Das Skriptverzeichnis ist Ă€quivalent zum Wert der magischen Konstanten __DIR__.
Include-Pfad
Der Include-Pfad ist eine global konfigurierbare und ansprechbare Liste von Verzeichnissen, von denen ausgehend PHP nach Entsprechungen fĂŒr virtuelle Pfade sucht (Definition in der PHP-Dokumentation).

PHP beziehungsweise das Dateisystem benötigt fĂŒr die DurchfĂŒhrung eines Includes letztlich immer einen absoluten Pfad. Nur damit lĂ€sst sich eine Datei im Dateisystem zweifelsfrei lokalisieren. Andere Arten von Pfadangaben mĂŒssen im Zuge des Include-Vorgangs in absolute Pfadangaben umgewandelt werden.

FĂŒr Dateipfade ist diese Umwandlung leicht, denn die Angaben sind bereits so spezifisch, dass der Include-Pfad ignoriert werden kan. Absolute Dateipfade sind bereits absolute Pfade, relative Dateipfade mĂŒssen lediglich ausgehend vom Arbeitsverzeichnis aufgelöst werden.

FĂŒr virtuelle Pfade ist die Umwandlung komplexer.

  1. Zuerst wird nacheinander jedes Verzeichnis des globalen Include-Pfads nach einer Entsprechung durchsucht.
  2. Wird die nachgefragte Datei dort nicht gefunden, wird daraufhin das Skriptverzeichnis durchsucht. Das ist nicht das Arbeitsverzeichnis, sondern dasjenige Verzeichnis, in dem sich die Datei befindet, die den Include-Befehl enthÀlt.
  3. Wird auch dort nichts gefunden, wird zuletzt noch ausgehend vom Arbeitsverzeichnis gesucht.

Bemerkenswert ist dabei, dass der Include-Pfad zumeist bereits einen Eintrag . enthĂ€lt, der auf das Arbeitsverzeichnis verweist. In der Standardkonfiguration von PHP (hier Version 5.4.7) steht dieser Eintrag sogar am Anfang des Include-Pfads („PHP’s default setting for include_path is ‚.;/path/to/php/pear‘“). Das bedeutet, dass Schritt 3 in der Regel zu Beginn der Auswertung noch vor Schritt 1 ausgefĂŒhrt wird. Das ist vermutlich ein Indiz dafĂŒr, dass bei virtuellen Pfaden bereits in Schritt 1 eine Entsprechung erwartet wird und dass vor allem Schritt 2 nicht unbedingt die vorgesehene FunktionalitĂ€t ist.

Bei Bedarf kann der Pfad aus Schritt 2, der vom Skriptverzeichnis ausgeht, ĂŒber die magische Konstante __DIR__ als absoluter Pfad erzeugt werden.

$path = __DIR__ . '/includes/test.php';
include $path;

Include mit virtuellen Pfaden

Die Vorgehensweise beim Analysieren virtueller Pfade fĂŒhrt zu Situationen wie dieser:

(Jeder Stichpunkt symbolisiert Speicherort und Inhalt einer Datei eines kleinen Beispielprojekts.)

Ausgabe:

ich komme aus dem Projektverzeichnis
ich komme aus dem Projektverzeichnis

ich komme aus dem includepath
ich komme aus dem Projektverzeichnis

Im ersten Include-Block verweisen beide Zeilen auf a.php im Projektverzeichnis, da . der erste Eintrag auf dem Include-Pfad ist. Im zweiten Block wird der virtuelle Pfad wiederum auf das nun verÀnderte Verzeichnis aus dem Include-Pfad umgeleitet, wÀhrend der Dateipfad nach wie vor rein lokal ausgewertet wird.

Der Include-Pfad hÀtte im ersten Block auch auf ein nicht existierendes Verzeichnis gesetzt werden können. Im Zuge von Schritt 2 wÀre dann ebenfalls ./a.php eingebunden worden. Ein völlig leerer Include-Pfad (set_include_path("")) ist technisch nicht möglich.

Dieses Verhalten von Includes mit virtuellen Pfadangaben kann beabsichtigt sein, um beispielsweise zu ermöglichen, durch ein Umschreiben des Include-Pfads FunktionalitÀt auszutauschen, ohne die Originaldateien zu verÀndern. Umgekehrt besteht aber genauso die Gefahr, dass eine einzubindende Datei a.php aufgrund einer Namenskollision mit einem anderen Projekt unbeabsichtigt von der falschen Stelle eingebunden wird.

Beim Einsatz virtueller Pfade ist es also unabdingbar, den Aufbau des Include-Pfads zu kontrollieren. Das stellt eine Voraussetzung dar, die zwar nicht schwierig zu erfĂŒllen ist, die aber in vielen FĂ€llen den Einsatz einer Anwendung eher verkomplizieren als erleichtern dĂŒrfte.

Die „richtige“ Pfadangabe bei Includes

In diesem Abschnitt wird ein Vorschlag fĂŒr eine möglichst optimale Lösung fĂŒr Pfadangaben fĂŒr Includes in Standardanwendungen formuliert. Ungeachtet dessen ist die geschickteste Art der Pfadangabe jedoch immer diejenige, die fĂŒr den konkreten Anwendungsfall am besten geeignet ist.

Die These ist, dass eine Pfadangabe umso robuster ist, je weniger sie von externer Konfiguration abhÀngt. Durch diese Verringerung globaler AbhÀngigkeiten wird Quellcode leichter wiederverwendbar und einfacher austauschbar. Die beiden wesentlichen externen Faktoren, die Pfadangaben bei Includes beeinflussen, sind der Include-Pfad und das Arbeitsverzeichnis.

Der Include-Pfad und virtuelle Pfade sind sicherlich ohnehin aus der Mode gekommen. Sie dienen vor allem dazu, AbhÀngigkeiten von Klassen aufzulösen. Ein Beispiel aus einer PEAR-Klasse (HTML_BBCodeParser, Version 1.2.3):

require_once 'HTML/BBCodeParser/Filter.php';

class HTML_BBCodeParser_Filter_Basic extends HTML_BBCodeParser_Filter
{
    /* ... */
}

Beim Laden der Filter-Klasse wird – sofern noch nicht geschehen – die Elternklasse ebenfalls als Include mit eingebunden. Das setzt voraus, dass das entsprechende Include-Basisverzeichnis (hier das von PEAR) im Include-Pfad enthalten ist. Diese FunktionalitĂ€t wird heute zumeist von Autoloadern ĂŒbernommen, wodurch die AbhĂ€ngigkeit zu einem bestimmten Eintrag im Include-Pfad wegfĂ€llt.

FĂŒr die wenigen verbliebenen Include-Zeilen ist es ĂŒblicherweise empfehlenswert, Pfadangaben ausgehend vom aktuellen Skriptverzeichnis mit __DIR__ zu bilden, um die Anwendung weiterhin von der Notwendigkeit eines korrekt gesetzten Arbeitsverzeichnisses unabhĂ€ngig zu machen.

Dazu zwei Beispiele:

Eine Formulierung der Pfadangaben beginnend mit __DIR__ löst derlei Probleme. Der Code wird sowohl vom Include-Pfad als auch vom Arbeitsverzeichnis unabhĂ€ngig, da die Pfadangabe, die an den Include-Mechanismus ĂŒbergeben wird, bereits absolut ist.

Verweise: