Hallo,
einiges kann schiefgehen, wenn man versucht relative Pfade beim include/require zu verwenden. Es ist dabei zu beachten, dass ein include ohne vollstaendige Pfadangabe:
seinen korrekten Pfad ueber den Pfadnamen des ausfuehrenden Skriptes aufloest.
Der PHP-Interpreter verwendet also den Ordner des Skriptes als Basis, mit dem er aufgerufen wird. Dabei ist es voellig egal, welche weiteren Skripte im Verlauf mit include/require eingebunden werden. Sich den Unterschied klar zu machen, ist wichtig.
Auch kann dies etwas verwirrend sein und mag unlogisch erscheinen, z.B. bei folgendem Dateisystem-Szenario:
global.inc.php
wwwroot/index.php
classes/MyClass.class.php:
Die Pfade scheinen einzeln und fuer sich genommen korrekt gesetzt zu sein, aber wenn man "index.php" nun aufruft, wird das Skript "global.inc.php" - das noch erfolgreich von "index.php" eingebunden wurde - mit einem include-Fehler abbrechen, da "classes/MyClass.class.php" nicht gefunden wurde.
Beziehen wir nun ein, dass dirname($_SERVER["SCRIPT_FILENAME"]) als Basispfad verwendet wird, macht das ganze Sinn, denn der PHP-Interpreter wurde mit "index.php" aufgerufen und verwendet nun dessen Ordner als Basis-Verzeichnis, und "wwwroot/" . "classes/MyClass.class.php" existiert nunmal nicht.
Wie loesen wir also das Problem? Nun es gibt viele Loesungen, wobei ich persoenlich in letzter Zeit zur am Ende beschriebenen Loesung tendiere, alle Loesungen sind allerdings akzeptabel. Wenn man PHP allerdings beruflich verwendet, sollte man sich schon ueberlegen, ob nicht ein professionelleres Handling Sinn macht.
1.) Wenn man weiss, dass "global.inc.php" immer aus "wwwroot" aufgerufen wird, kann man dieses Vorwissen nutzen und einsetzen, in dem man "global.inc.php" einfach dahingehend manipuliert, dass man den relativen Pfad so anpasst, dass er aus "wwwroot" loslaufend, korrekt ist:
global.inc.php:
Der Nachteil ist, wird nun ein Skript aus einem Unterordner von "wwwroot" aufgerufen, also z.B. "wwwroot/news/index.php" funktioniert das ganze nicht mehr.
2.) Feste, absolute Pfade:
Der Nachteil von relativen Pfaden ist ganz offensichtlich, dass man vom Basis-Pfad abhaengig ist. Umgehen kann man das, in dem man absolute Pfade verwendet:
global.inc.php:
Dies wird uebrigens von Windows und Linux verstanden, obwohl der DIRECTORY_SEPARATOR von Windows der Backslash (\) ist.
Der Nachteil ist aber offensichtlich, das Projekt muss wissen wo es sich befindet, die Pfade stehen fest und sie muessen bei einem Server-Umzug (und sei es nur von der Entwicklungs- in die Produktiv-Umgebung) muehsam angepasst werden. Search&Replace ist eine feine Sache, Komfort sieht aber anders aus.
3.) Dynamische, absolute Pfade:
Man kann sich natuerlich auch absolute Pfade selber zusammen bauen, in dem man die magische Konstante __FILE__ und wieder die Funktion dirname() verwendet. __FILE__ ist laut Beschreibung des Manual lediglich dies:
http://us2.php.net/manual/de/languag...predefined.php
Ruft man nun dirname(__FILE__) auf, hat man sich so seinen eigenen absoluten Basispfad erstellt:
global.inc.php
Diese Intelligenz kann man natuerlich in eine Pfad-Konfigurationsdatei (belassen wir sie der Einfachheit halber in "global.inc.php") auslagern:
Nun kann jede Datei, die "global.inc.php" einbindet, diese Konstante verwenden, egal aus welcher Pfad-Ebene sie aufgerufen wird, immer absolut und damit korrekt:
wwwroot/news/index.php:
Aber auch diese Loesung hat den Nachteil, dass die Anwendung ihren relativen Ort zur "global.inc.php" kennen muss (und wenn dies jede Datei muss, ist die Struktur de-facto star) und die Verwendung einer Konstanten (und die Kenntnis ihres Namens) fuer jeden include/require Befehl benoetigt wird. Eine zwar kleine Abhaengigkeit, aber auch eine kleine Abhaengigkeit ist eine Abhaengigkeit, die man natuerlich vermeiden moechte.
4.) __autoload
Wenn es nur darum geht PHP-Klassen einzubinden, kann man die Funktion __autoload() verwenden:
http://us.php.net/__autoload
Die funktioniert allerdings nur fuer Klassen, nicht fuer andere Inline-Skripte oder Funktionen.
5.) PHP-Einstellung "include_path":
Der "include_path" wird viel zu selten verwendet, was eigentlich verwundert. Schlaegt ein include fehl, weil die Datei nicht gefunden wird, werden die im "php_flag" "include_path" aufgefuehrten und mit PATH_SEPARATOR getrennten Pfade als Basis zur include-Anweisung verwendet und nacheinander bis zum Erfolg oder endgueltigen Misserfolg ausprobiert. Was nach einem endgueltigen Misserfolg passiert, entscheiden die Konstrukte include/require selbst und ist hier nachzulesen:
http://us3.php.net/include/
http://us3.php.net/require/
Der Pfad laesst sich durch die Datei "php.ini", Flag "include_path" entweder hart per Server-Einstellung setzen, oder aber nachtraeglich ueber ".htaccess" oder dynamisch mit set_include_path(). Auslesen ist ueber get_include_path() moeglich.
Wie erwaehnt werden hier die Pfade als String aufgelistet und sind nur mit dem in der PHP-Konstanten PATH_SEPARATOR festgelegten Zeichen getrennt. Es ist ein abhaengig vom Betriebssystem festgelegtes Zeichen, dass in seinen Pfaden nicht vorkommen darf, in Linux ueblicherweise der Doppelpunkt (, unter Windows das Semikolon (.
Eine Funktion add_include_path($strIncludePath) koennte also so aussehen:
Nun koennten wir statt unserer Pfad-Konstante PATH_CLASSES einfach alle wichtigen Verzeichnisse zu unserem "include_path" hinzufuegen. Wildes includen funktioniert dann natuerlich immer noch nicht, denn wir muessen ja vorher bereits eine Vorauswahl getroffen haben, welche Pfade "wuerdig" sind, als Basisverzeichnis in den "include_path" aufgenommen zu werden. Dies gewaehrleistet ein systematisches aber flexibles Schema (was include ich grundsaetzlich bzw. konkret) und bleibt bei den ueblichen Anwendungen auch meistens ueberschaubar: Klassen, Funktionen, externe Bibliotheken (PEAR, Zend, phpMailer, ..) oder Models/Views/Controllers. Externe Bibliotheken verlangen ohnehin dass sie in den "include_path" aufgenommen werden (und PEAR ist es unter XAMPP auch schon automatisch), wie sollten sie auch sonst ohne die Verwendung von absoluten Pfad-Konstrukten wissen, wo und wie sie ihre eigenen Unterbibliotheken nachladen koennen.
Da die genannten externen Bibliotheken PEAR, Zend und phpMailer eigentlich nur Klassen-Sammlungen sind koennen wir die Bibliotheken auch nur bei Bedarf verfuegbar machen:
PEAR.inc.php:
Ein
genuegt (sofern sich der Pfad von "PEAR.inc.php" bereits im "include_path" befindet).
Das ganze automatisieren bzw. auf ein Minimum an Anweisungen runterbrechen kann man nun mit dem PHP-Flag "auto_prepend_file". Eingestellt, wird das darueber angegebene Skript vor jeder Ausfuehrung eines PHP-Skriptes ausgefuehrt:
wwwroot/.htaccess:
Nun wird, bevor ein Skript ueber den Webserver ausgefuehrt wird (denn nur auf den hat ".htaccess" Einfluss), die Datei "global.inc.php" im leider absolut angegebenen Verzeichnis ausgefuehrt. Wenn wir nicht sowieso schon eine Bootstrap-Datei verwenden, bei der dieses Verfahren (fast) unnuetz ist, haben wir es jetzt geschafft und koennen in "prepend.inc.php" unseren "include_path" setzen:
prepend.inc.php:
Es ist anzuraten, dass in dieser Datei so wenig Intelligenz wie moeglich verwendet wird, nach Moeglichkeit sollte sie nur den "include_path" setzen, fertig. Denn auch das Debuggen in dieser Datei ist schwierig und da diese Datei vor jedem Skript ausgefuehrt wird, sollte es keinen irgendwie gearteten Bug produzieren. Bugs entstehen in komplexen Systemen, also versuchen wir dem mit Einfachheit entgegenzuwirken.
Der Nachteil wurde bereits genannt: Wir muessen einmalig unseren absoluten Pfad fuer das Projekt kennen und ihn in der ".htaccess" Datei setzen. Das ist schlecht, laesst sich aber meines Wissens nicht vermeiden (wenn diese Information falsch ist, lasst es mich wissen). Einmalig heisst aber wirklich einmalig, pro Entwicklungsumgebung.
Der Vorteil ist nun, dass unsere komplexe Anwendung nun rein garnichts ueber sich wissen muss, weder wie die Pfadkonstante heissen, ob es welche gibt und schon garnicht muss die Anwendung wissen, wo sich welche Dateien befinden. All dies uebernimmt nun PHP aus dem Wissen der fuer "include_path" gelieferten Pfade. Eine clevere __autoload Funktion erledigt den Rest:
Auch sie kennt nur die Details, die sie wissen muss. Alle Bibliotheken funktionieren nun in jeder Umgebung, in der "include_path" korrekt gesetzt ist, eine Voraussetzung die sich notfalls nachtraeglich schaffen laesst.
Die "auto_prepend_file" Direktive laesst sich wie erwaehnt natuerlich bei der Verwendung von "Bootstrapping" vermeiden (womit man auch auf absolute Pfade vermeiden kann), allerdings kommt es in komplexen Anwendungen oft genug vor, dass man [mein Gott der Hund hier furzt wie ein Weltmeister] dann doch das ein oder andere Skript hat, das selbstaendig laeuft, seien es Bild-Generatoren oder Download-Manager, die man vielleicht von der komplexen Controller-Struktur entkoppeln moechte und dann faengt man wieder an, das Wissen ueber den Aufbau der Anwendung zu verteilen.
Soviel dazu.
einiges kann schiefgehen, wenn man versucht relative Pfade beim include/require zu verwenden. Es ist dabei zu beachten, dass ein include ohne vollstaendige Pfadangabe:
PHP-Code:
<?php
include "classes/MyClass.class.php"
?>
Das Verzeichnis des ausfuehrenden Skriptes
und nicht die Datei, die die include/require Anweisung ausfuehrt, wird als Basis-Verzeichnis fuer relative include/require Anweisungen verwendet.
PHP-Code:
<?php
dirname($_SERVER["SCRIPT_FILENAME"])
?>
Der PHP-Interpreter verwendet also den Ordner des Skriptes als Basis, mit dem er aufgerufen wird. Dabei ist es voellig egal, welche weiteren Skripte im Verlauf mit include/require eingebunden werden. Sich den Unterschied klar zu machen, ist wichtig.
Auch kann dies etwas verwirrend sein und mag unlogisch erscheinen, z.B. bei folgendem Dateisystem-Szenario:
Code:
global.inc.php wwwroot/index.php classes/MyClass.class.php
PHP-Code:
<?php
include "classes/MyClass.class.php";
?>
PHP-Code:
<?php
include "../global.inc.php";
$objMyClass = new MyClass();
?>
PHP-Code:
<?php
class MyClass { }
?>
Beziehen wir nun ein, dass dirname($_SERVER["SCRIPT_FILENAME"]) als Basispfad verwendet wird, macht das ganze Sinn, denn der PHP-Interpreter wurde mit "index.php" aufgerufen und verwendet nun dessen Ordner als Basis-Verzeichnis, und "wwwroot/" . "classes/MyClass.class.php" existiert nunmal nicht.
Wie loesen wir also das Problem? Nun es gibt viele Loesungen, wobei ich persoenlich in letzter Zeit zur am Ende beschriebenen Loesung tendiere, alle Loesungen sind allerdings akzeptabel. Wenn man PHP allerdings beruflich verwendet, sollte man sich schon ueberlegen, ob nicht ein professionelleres Handling Sinn macht.
1.) Wenn man weiss, dass "global.inc.php" immer aus "wwwroot" aufgerufen wird, kann man dieses Vorwissen nutzen und einsetzen, in dem man "global.inc.php" einfach dahingehend manipuliert, dass man den relativen Pfad so anpasst, dass er aus "wwwroot" loslaufend, korrekt ist:
global.inc.php:
PHP-Code:
<?php
include "../classes/MyClass.php";
?>
2.) Feste, absolute Pfade:
Der Nachteil von relativen Pfaden ist ganz offensichtlich, dass man vom Basis-Pfad abhaengig ist. Umgehen kann man das, in dem man absolute Pfade verwendet:
global.inc.php:
PHP-Code:
<?php
include "/path/to/my/project/classes/MyClass.class.php";
?>
Dies wird uebrigens von Windows und Linux verstanden, obwohl der DIRECTORY_SEPARATOR von Windows der Backslash (\) ist.
Der Nachteil ist aber offensichtlich, das Projekt muss wissen wo es sich befindet, die Pfade stehen fest und sie muessen bei einem Server-Umzug (und sei es nur von der Entwicklungs- in die Produktiv-Umgebung) muehsam angepasst werden. Search&Replace ist eine feine Sache, Komfort sieht aber anders aus.
3.) Dynamische, absolute Pfade:
Man kann sich natuerlich auch absolute Pfade selber zusammen bauen, in dem man die magische Konstante __FILE__ und wieder die Funktion dirname() verwendet. __FILE__ ist laut Beschreibung des Manual lediglich dies:
Der vollständige Pfad- und Dateiname einer Datei. Wird diese Konstante innerhalb einer nachgeladenen Datei verwendet, wird der Name dieser eingebundenen Datei zurückgegeben.
Ruft man nun dirname(__FILE__) auf, hat man sich so seinen eigenen absoluten Basispfad erstellt:
global.inc.php
PHP-Code:
<?php
include dirname(__FILE__) . "/classes/MyClass.class.php";
?>
Diese Intelligenz kann man natuerlich in eine Pfad-Konfigurationsdatei (belassen wir sie der Einfachheit halber in "global.inc.php") auslagern:
PHP-Code:
<?php
define("PATH_CLASSES", dirname(__FILE__) . "/classes");
?>
Nun kann jede Datei, die "global.inc.php" einbindet, diese Konstante verwenden, egal aus welcher Pfad-Ebene sie aufgerufen wird, immer absolut und damit korrekt:
wwwroot/news/index.php:
PHP-Code:
<?php
include "../../global.inc.php";
include PATH_CLASSES . "/MyClass.class.php";
?>
Aber auch diese Loesung hat den Nachteil, dass die Anwendung ihren relativen Ort zur "global.inc.php" kennen muss (und wenn dies jede Datei muss, ist die Struktur de-facto star) und die Verwendung einer Konstanten (und die Kenntnis ihres Namens) fuer jeden include/require Befehl benoetigt wird. Eine zwar kleine Abhaengigkeit, aber auch eine kleine Abhaengigkeit ist eine Abhaengigkeit, die man natuerlich vermeiden moechte.
4.) __autoload
Wenn es nur darum geht PHP-Klassen einzubinden, kann man die Funktion __autoload() verwenden:
http://us.php.net/__autoload
Die funktioniert allerdings nur fuer Klassen, nicht fuer andere Inline-Skripte oder Funktionen.
5.) PHP-Einstellung "include_path":
Der "include_path" wird viel zu selten verwendet, was eigentlich verwundert. Schlaegt ein include fehl, weil die Datei nicht gefunden wird, werden die im "php_flag" "include_path" aufgefuehrten und mit PATH_SEPARATOR getrennten Pfade als Basis zur include-Anweisung verwendet und nacheinander bis zum Erfolg oder endgueltigen Misserfolg ausprobiert. Was nach einem endgueltigen Misserfolg passiert, entscheiden die Konstrukte include/require selbst und ist hier nachzulesen:
http://us3.php.net/include/
http://us3.php.net/require/
Der Pfad laesst sich durch die Datei "php.ini", Flag "include_path" entweder hart per Server-Einstellung setzen, oder aber nachtraeglich ueber ".htaccess" oder dynamisch mit set_include_path(). Auslesen ist ueber get_include_path() moeglich.
Wie erwaehnt werden hier die Pfade als String aufgelistet und sind nur mit dem in der PHP-Konstanten PATH_SEPARATOR festgelegten Zeichen getrennt. Es ist ein abhaengig vom Betriebssystem festgelegtes Zeichen, dass in seinen Pfaden nicht vorkommen darf, in Linux ueblicherweise der Doppelpunkt (, unter Windows das Semikolon (.
Eine Funktion add_include_path($strIncludePath) koennte also so aussehen:
PHP-Code:
<?php
function add_include_path($strIncludePath) {
$strIncludePath = realpath($strIncludePath);
if (empty($strIncludePath)) {
return false;
}
$arrIncludePaths = explode(PATH_SEPARATOR, get_include_path());
$arrIncludePaths[] = $strIncludePath;
$arrIncludePaths = array_unique($arrIncludePaths);
set_include_path(implode(PATH_SEPARATOR, $arrIncludePaths));
return true;
}
?>
Nun koennten wir statt unserer Pfad-Konstante PATH_CLASSES einfach alle wichtigen Verzeichnisse zu unserem "include_path" hinzufuegen. Wildes includen funktioniert dann natuerlich immer noch nicht, denn wir muessen ja vorher bereits eine Vorauswahl getroffen haben, welche Pfade "wuerdig" sind, als Basisverzeichnis in den "include_path" aufgenommen zu werden. Dies gewaehrleistet ein systematisches aber flexibles Schema (was include ich grundsaetzlich bzw. konkret) und bleibt bei den ueblichen Anwendungen auch meistens ueberschaubar: Klassen, Funktionen, externe Bibliotheken (PEAR, Zend, phpMailer, ..) oder Models/Views/Controllers. Externe Bibliotheken verlangen ohnehin dass sie in den "include_path" aufgenommen werden (und PEAR ist es unter XAMPP auch schon automatisch), wie sollten sie auch sonst ohne die Verwendung von absoluten Pfad-Konstrukten wissen, wo und wie sie ihre eigenen Unterbibliotheken nachladen koennen.
Da die genannten externen Bibliotheken PEAR, Zend und phpMailer eigentlich nur Klassen-Sammlungen sind koennen wir die Bibliotheken auch nur bei Bedarf verfuegbar machen:
PEAR.inc.php:
PHP-Code:
<?php
add_include_path(dirname(__FILE__));
?>
Code:
PEAR.inc.php PEAR/PEAR.php PEAR/PEAR/.. ..
Ein
PHP-Code:
<?php
include_once "PEAR.inc.php"
?>
Das ganze automatisieren bzw. auf ein Minimum an Anweisungen runterbrechen kann man nun mit dem PHP-Flag "auto_prepend_file". Eingestellt, wird das darueber angegebene Skript vor jeder Ausfuehrung eines PHP-Skriptes ausgefuehrt:
wwwroot/.htaccess:
Code:
php_flag auto_prepend_file "C:/path/to/my/project/includes/prepend.inc.php"
Nun wird, bevor ein Skript ueber den Webserver ausgefuehrt wird (denn nur auf den hat ".htaccess" Einfluss), die Datei "global.inc.php" im leider absolut angegebenen Verzeichnis ausgefuehrt. Wenn wir nicht sowieso schon eine Bootstrap-Datei verwenden, bei der dieses Verfahren (fast) unnuetz ist, haben wir es jetzt geschafft und koennen in "prepend.inc.php" unseren "include_path" setzen:
prepend.inc.php:
PHP-Code:
<?php
$arrIncludePaths = explode(PATH_SEPARATOR, get_include_path());
$arrIncludePaths[] = realpath("/path/to/my/project/classes");
$arrIncludePaths[] = realpath("/path/to/my/project/functions");
$arrIncludePaths = array_unique($arrIncludePaths);
set_include_path(implode(PATH_SEPARATOR, $arrIncludePaths));
?>
Es ist anzuraten, dass in dieser Datei so wenig Intelligenz wie moeglich verwendet wird, nach Moeglichkeit sollte sie nur den "include_path" setzen, fertig. Denn auch das Debuggen in dieser Datei ist schwierig und da diese Datei vor jedem Skript ausgefuehrt wird, sollte es keinen irgendwie gearteten Bug produzieren. Bugs entstehen in komplexen Systemen, also versuchen wir dem mit Einfachheit entgegenzuwirken.
Der Nachteil wurde bereits genannt: Wir muessen einmalig unseren absoluten Pfad fuer das Projekt kennen und ihn in der ".htaccess" Datei setzen. Das ist schlecht, laesst sich aber meines Wissens nicht vermeiden (wenn diese Information falsch ist, lasst es mich wissen). Einmalig heisst aber wirklich einmalig, pro Entwicklungsumgebung.
Der Vorteil ist nun, dass unsere komplexe Anwendung nun rein garnichts ueber sich wissen muss, weder wie die Pfadkonstante heissen, ob es welche gibt und schon garnicht muss die Anwendung wissen, wo sich welche Dateien befinden. All dies uebernimmt nun PHP aus dem Wissen der fuer "include_path" gelieferten Pfade. Eine clevere __autoload Funktion erledigt den Rest:
PHP-Code:
<?php
function __autoload($strClass) {
require str_replace("_", DIRECTORY_SEPARATOR, $strClass . ".class.php");
}
?>
Auch sie kennt nur die Details, die sie wissen muss. Alle Bibliotheken funktionieren nun in jeder Umgebung, in der "include_path" korrekt gesetzt ist, eine Voraussetzung die sich notfalls nachtraeglich schaffen laesst.
Die "auto_prepend_file" Direktive laesst sich wie erwaehnt natuerlich bei der Verwendung von "Bootstrapping" vermeiden (womit man auch auf absolute Pfade vermeiden kann), allerdings kommt es in komplexen Anwendungen oft genug vor, dass man [mein Gott der Hund hier furzt wie ein Weltmeister] dann doch das ein oder andere Skript hat, das selbstaendig laeuft, seien es Bild-Generatoren oder Download-Manager, die man vielleicht von der komplexen Controller-Struktur entkoppeln moechte und dann faengt man wieder an, das Wissen ueber den Aufbau der Anwendung zu verteilen.
Soviel dazu.
Kommentar