Ankündigung

Einklappen
Keine Ankündigung bisher.

PHP: Falsche Pfade bei include/require ohne include_path

Einklappen

Neue Werbung 2019

Einklappen
Dieses Thema ist geschlossen.
X
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • PHP: Falsche Pfade bei include/require ohne include_path

    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:
    PHP-Code:
    <?php
    include "classes/MyClass.class.php"
    ?>
    seinen korrekten Pfad ueber den Pfadnamen des ausfuehrenden Skriptes aufloest.


    Das Verzeichnis des ausfuehrenden Skriptes
    PHP-Code:
    <?php
    dirname
    ($_SERVER["SCRIPT_FILENAME"])
    ?>
    und nicht die Datei, die die include/require Anweisung ausfuehrt, wird als Basis-Verzeichnis fuer relative include/require Anweisungen verwendet.

    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
    global.inc.php
    PHP-Code:
    <?php
    include "classes/MyClass.class.php";
    ?>
    wwwroot/index.php
    PHP-Code:
    <?php
    include "../global.inc.php";
    $objMyClass = new MyClass();
    ?>
    classes/MyClass.class.php:
    PHP-Code:
    <?php
    class MyClass { }
    ?>
    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:
    PHP-Code:
    <?php
    include "../classes/MyClass.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:
    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.
    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
    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_SEPARATORget_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"
    ?>
    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:
    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_SEPARATORget_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.

  • #2
    sorry, war zu faul das alles durchzulesen, nur noch die anmerung:

    der apache webserver ändert während des shutdowns eines moduls (cgi weiß ich jetzt nicht) den aktuellen pfad auf den pfad des homedirs des webservers. bedeutend ist dies, wenn man während einer shutdown funktion wie destruct in eine datei schreiben muss. dann MUSS man den absoluten pfad angeben.
    [B]PHP4?!?[/B]>>>[B]Aktuelle[/B] PHP Version: [B]5.2.11 || 5.3.0
    [URL="http://en.opensuse.org/Factory/News"]Suse 11.2 *vorfreude*[/URL]
    [/B]

    Kommentar


    • #3
      Ich hab jetzt auch keine Lust das alles zu lesen, aber ich hab einen Tipp der mich schon eine Menge Zeit gekostet hat! Und zwar hat PHP 5.2.5 einen Bug das set_include_path mal funktioniert und mal nicht, fall einem das mal zustößen sollte...

      Kommentar


      • #4
        Ihr seid so hoeflich - die geborenen Informatiker eben

        Kommentar


        • #5
          Zitat von Zergling
          Ihr seid so hoeflich - die geborenen Informatiker eben
          Man spart mit Arbeit wo man kann

          Kommentar


          • #6
            Auch wenn das ganze schon ziemlich alt ist, wollte ich einmal danke sagen!

            Ich bin über Google auf diesen Beitrag gestoßen, da ich ein wenig Hilfe bei der Entwicklung eines durchdachten Pfad/Include-Systems brauchte.

            Kommentar

            Lädt...
            X