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.
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:
/
oder einem Laufwerksbuchstaben (Windows) beginnt, beispielsweise /includes/test.php
oder c:/includes/test.php
../
oder ../
beginnt, beispielsweise ./includes/test.php
oder ../includes/test.php
.includes/test.php
.__DIR__
.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.
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;
Die Vorgehensweise beim Analysieren virtueller Pfade fĂŒhrt zu Situationen wie dieser:
(Jeder Stichpunkt symbolisiert Speicherort und Inhalt einer Datei eines kleinen Beispielprojekts.)
./includepath/a.php:
ich komme aus dem includepath
./a.php:
ich komme aus dem Projektverzeichnis
./index.php:
<?php
set_include_path('.');
include 'a.php';
include './a.php';
set_include_path(__DIR__ . '/includepath');
include 'a.php';
include './a.php';
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.
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:
Szenario 1: In einer Klasse wird intern ein Pfad zu einer ausgelagerten Ressource benötigt, die im selben Verzeichnis wie die Klasse liegt (etwa eine XSL-Datei oder andere interne Hilfsdaten). Wird der Pfad zu dieser Datei nicht absolut angegeben, wird er zuerst dem Pfad der ursprĂŒnglich aufgerufenen Skriptdatei entsprechend gebildet, nicht dem Pfad der Klassendatei entsprechend. Das wird zum Problem, sobald sich die Anordnung der Verzeichnisse verschiebt: Eine Verzeichnisebene mehr oder weniger oder verĂ€nderte Verzeichnisnamen fĂŒhren dazu, dass die Pfade nicht mehr korrekt sind und angepasst werden mĂŒssen.
Szenario 2: Relative Pfade sind in der Regel relativ zum Arbeitsverzeichnis. Das ist bei Aufruf ĂŒber den Webserver vermutlich unproblematisch, sorgt aber bei Aufruf ĂŒber die Kommandozeile fĂŒr Schwierigkeiten, falls der Aufruf nicht aus dem Verzeichnis der Skriptdatei heraus getĂ€tigt wird. Das ist zum Beispiel bei einem Aufruf per $ php ./dir1/dir2/file.php
der Fall. Das Arbeitsverzeichnis ist in diesem Fall nicht ./dir1/dir2
, sondern dasjenige Verzeichnis, in dem sich die Unterverzeichnisse dir1/dir2
befinden. Anweisungen wie include './a.php'
wĂŒrden somit ins falsche Verzeichnis zeigen.
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: