In der Fortsetzung der PHP for Beginners-Reihe wird sich der heutige Artikel darauf konzentrieren, wie PHP Dateien sucht und verbindet.
Warum und warum
PHP ist eine Skriptsprache, die ursprünglich für die schnelle Gestaltung von Homepages entwickelt wurde (ja, ja, ursprünglich war es ursprünglich Personal Home Age Tools). Später wurden auf dem Knie Geschäfte, soziale Programme und andere Handwerke erstellt, die über das beabsichtigte Maß hinausgingen , aber warum bin ich - und die Tatsache, dass je mehr Funktionalität codiert ist, desto größer ist der Wunsch, sie korrekt zu strukturieren, Code-Duplikationen zu beseitigen, sie in logische Teile zu zerlegen und nur bei Bedarf eine Verbindung herzustellen (dies ist das gleiche Gefühl, das Sie hatten, als du hast es schon mal gelesen Position könnte es in einzelne Teile zerbrochen werden). Zu diesem Zweck verfügt PHP über mehrere Funktionen, deren allgemeine Bedeutung darin besteht, die angegebene Datei zu verbinden und zu interpretieren. Schauen wir uns ein Beispiel für das Verbinden von Dateien an:
Wenn Sie das Skript
index.php ausführen, stellt PHP eine Verbindung her und führt dies alles nacheinander aus:
$a = 0; $a++; $a++; echo $a;
Wenn eine Datei verbunden ist, befindet sich ihr Code im selben Bereich wie die Zeile, in der sie verbunden war, sodass alle in dieser Zeile verfügbaren Variablen in der enthaltenen Datei verfügbar sind. Wenn Klassen oder Funktionen in der Include-Datei deklariert wurden, fallen sie in den globalen Bereich (es sei denn, für sie wurde natürlich ein Namespace angegeben).
Wenn Sie die Datei innerhalb der Funktion verbinden, erhalten die enthaltenen Dateien Zugriff auf den Funktionsumfang, sodass auch der folgende Code funktioniert:
function() { $a = 0; include ('increment.php'); include ('increment.php'); echo $a; } a();
Separat __DIR__
ich die magischen Konstanten fest : __DIR__
, __FILE__
, __LINE__
und andere - sie sind an den Kontext gebunden und werden ausgeführt, bevor die Aufnahme erfolgt
Die Besonderheit beim Verbinden von Dateien besteht darin, dass beim Verbinden einer Datei beim Parsen in den HTML-Modus gewechselt wird. Aus diesem Grund muss jeder Code in der enthaltenen Datei in PHP-Tags eingeschlossen sein:
<?php
Wenn Sie nur PHP-Code in der Datei haben, ist es üblich, das schließende Tag wegzulassen, um nicht versehentlich einen Zeichenfaden nach dem schließenden Tag zu vergessen, der mit Problemen behaftet ist (ich werde dies im nächsten Artikel diskutieren).
Haben Sie eine Site-Datei mit 10.000 Zeilen gesehen? Schon Tränen in meinen Augen (╥_╥) ...
Dateiverbindungsfunktionen
Wie oben erwähnt, gibt es in PHP verschiedene Funktionen zum Verbinden von Dateien:
- include - schließt die angegebene Datei ein und führt sie aus, wenn sie nicht gefunden wird - gibt eine Warnung
E_WARNING
- include_once - ähnelt der obigen Funktion, enthält jedoch die Datei einmal
- require - schließt die angegebene Datei ein und führt sie aus, wenn sie nicht gefunden wird - gibt es einen schwerwiegenden Fehler
E_ERROR
- require_once - ähnlich der obigen Funktion, enthält jedoch die Datei einmal
In Wirklichkeit handelt es sich hierbei nicht genau um Funktionen, sondern um spezielle Sprachkonstrukte, und Klammern können weggelassen werden. Unter anderem gibt es andere Möglichkeiten, Dateien zu verbinden und auszuführen, aber graben Sie es selbst, lassen Sie es für Sie eine „Aufgabe mit einem Sternchen“ sein;)
Nehmen wir ein Beispiel für die Unterschiede zwischen
require
und
require_once
. Nehmen
wir eine
echo.php- Datei:
<p>text of file echo.php</p>
Und wir werden es mehrmals verbinden:
<?php
Das Ergebnis der Ausführung sind zwei Verbindungen zur Datei
echo.php :
<p>text of file echo.php</p> <p>text of file echo.php</p>
Es gibt einige weitere Anweisungen, die sich auf die Verbindung auswirken, aber Sie benötigen sie nicht -
auto_prepend_file und
auto_append_file . Mit diesen Anweisungen können Sie Dateien installieren, die verbunden werden, bevor alle Dateien verbunden sind bzw. nachdem alle Skripts ausgeführt wurden. Ich kann mir nicht einmal ein "Live" -Szenario ausdenken, wenn es erforderlich sein könnte.
AufgabeSie können ein Skript für die Verwendung der
auto_prepend_file
und
auto_append_file
auto_prepend_file
und
auto_append_file
. Sie können diese nur in
php.ini ,
.htaccess oder
httpd.conf ändern (siehe
PHP_INI_PERDIR ) :)
Wo sucht?
PHP sucht nach Include-Dateien in Verzeichnissen, die in der Anweisung
include_path angegeben sind. Diese Anweisung wirkt sich auch auf den Betrieb von
fopen()
,
file()
,
readfile()
und
file_get_contents()
. Der Algorithmus ist recht einfach: Bei der Suche nach Dateien überprüft PHP abwechselnd jedes Verzeichnis von
include_path
, bis eine zu verbindende Datei gefunden wird. Wenn dies nicht der Fall ist, wird ein Fehler zurückgegeben. Verwenden Sie die Funktion
set_include_path (), um
include_path
aus einem Skript zu ändern.
Beim Einrichten von
include_path
ist eine wichtige Sache zu beachten: Unter Windows und Linux werden verschiedene Zeichen als
include_path
verwendet - ";" und ":". Wenn Sie also Ihr Verzeichnis
PATH_SEPARATOR
, verwenden Sie die Konstante
PATH_SEPARATOR
, zum Beispiel:
Wenn Sie
include_path
in eine
include_path
Datei schreiben, können Sie Umgebungsvariablen wie
${USER}
:
include_path = ".:${USER}/my-php-library"
Wenn Sie beim Verbinden der Datei einen absoluten Pfad (beginnend mit "/") oder einen relativen Pfad (beginnend mit "." Oder "..")
include_path
wird die Anweisung
include_path
ignoriert und die Suche wird nur für den angegebenen Pfad ausgeführt.
Vielleicht lohnt es sich, über safe_mode zu sprechen, aber dies ist seit langem eine Geschichte (seit Version 5.4), und ich hoffe, Sie werden nicht darauf stoßen, aber wenn plötzlich, dann wissen Sie, was es war, aber es ist vorbei ...
Return verwenden
Ich erzähle Ihnen von einem kleinen Life-Hack. Wenn die enthaltene Datei mithilfe des
return
Konstrukts etwas zurückgibt, können diese Daten abgerufen und verwendet werden, sodass Sie die Verbindung von Konfigurationsdateien einfach organisieren können. Ich werde ein Beispiel zur Veranschaulichung geben:
return [ 'host' => 'localhost', 'user' => 'root', 'pass' => '' ];
$dbConfig = require 'config/db.php'; var_dump($dbConfig);
Interessante Fakten, ohne die es auch gut war: Wenn Funktionen in der enthaltenen Datei definiert sind, können sie in der Hauptdatei verwendet werden, unabhängig davon, ob sie vor oder nach der Rückgabe deklariert wurden
AufgabeSchreiben Sie Code, der die Konfiguration aus mehreren Ordnern und Dateien sammelt. Die Dateistruktur ist wie folgt:
config |-- default | |-- db.php | |-- debug.php | |-- language.php | `-- template.php |-- development | `-- db.php `-- production |-- db.php `-- language.php
In diesem Fall sollte der Code wie folgt funktionieren:
- Wenn es in der Systemumgebung eine
PROJECT_PHP_SERVER
Variable gibt, die der development
, sollten alle Dateien aus dem Standardordner verbunden sein, die Daten sollten in der Variablen $config
, die Dateien aus dem Entwicklungsordner sollten verbunden sein und die empfangenen Daten sollten die entsprechenden in $config
gespeicherten Elemente schleifen - ähnliches Verhalten, wenn
PROJECT_PHP_SERVER
production
(natürlich nur für den Produktionsordner ) - Wenn keine Variable vorhanden oder falsch eingestellt ist, werden nur Dateien aus dem Standardordner verbunden
Automatische Verbindung
Konstrukte mit Dateianhängen sehen sehr sperrig aus und folgen auch ihrem Update - ein weiteres Geschenk: Lesen Sie einen Code aus dem Beispielartikel
über Ausnahmen :
Der erste Versuch, ein solches „Glück“ zu vermeiden, war das Auftreten der
__autoload- Funktion. Genauer gesagt, es war nicht einmal eine bestimmte Funktion, Sie mussten diese Funktion selbst definieren und damit die Dateien verbinden, die wir über den Klassennamen benötigten. Die einzige Regel war, dass
für jede Klasse eine separate Datei mit dem Namen der Klasse erstellt werden sollte (d. H.
MyClass sollte sich in der Datei
myClass.php befinden ). Hier ist ein Beispiel für die Implementierung einer solchen Funktion
__autoload()
(entnommen aus Kommentaren im offiziellen Handbuch):
Die Klasse, die wir verbinden werden:
Die Datei, die diese Klasse verbindet:
Nun zu den Problemen mit dieser Funktion - stellen Sie sich eine Situation vor, in der Sie Code von Drittanbietern verbinden und dort bereits jemand die Funktion
__autoload()
für Ihren Code registriert hat, und voila:
Fatal error: Cannot redeclare __autoload()
Um dies zu vermeiden, haben wir eine Funktion erstellt, mit der Sie eine beliebige Funktion oder Methode als Klassenladeprogramm registrieren können -
spl_autoload_register . Das heißt, Wir können mehrere Funktionen mit einem beliebigen Namen zum Laden von Klassen erstellen und sie mit
spl_autoload_register
registrieren. Jetzt sieht
index.php
aus:
Die Überschrift „ spl_autoload_register()
Sie schon?“: Der erste Parameter spl_autoload_register()
ist optional. spl_autoload_register()
die Funktion ohne diesen Parameter spl_autoload_register()
, wird die Funktion spl_autoload als Loader verwendet. Die Suche wird für Ordner aus include_path
und Dateien mit der Erweiterung .php
und .inc
Die Liste kann mit der Funktion spl_autoload_extensions erweitert werden
Jetzt kann jeder Entwickler seinen Loader registrieren. Hauptsache, die Klassennamen stimmen nicht überein. Dies sollte jedoch kein Problem sein, wenn Sie Namespaces verwenden.
Da eine erweiterte Funktionalität wie spl_autoload_register()
schon lange existiert, ist die Funktion spl_autoload_register()
in PHP 7.1 bereits als veraltet deklariert, was bedeutet, dass diese Funktion in absehbarer Zukunft vollständig entfernt wird (X_x).
Nun, das Bild hat sich mehr oder weniger geklärt, obwohl alle registrierten Bootloader in der Warteschlange stehen, sobald sie registriert sind. Wenn ihn jemand in seinen Bootloader hineingelockt hat, wird anstelle des erwarteten Ergebnisses ein sehr unangenehmer Fehler auftreten. Um dies zu verhindern, haben erwachsene Smart-Jungs einen Standard beschrieben, mit dem Sie problemlos Bibliotheken von Drittanbietern verbinden können. Hauptsache, die Organisation der Klassen in ihnen entspricht dem
PSR-0- Standard (der bereits 10 Jahre alt ist) oder
PSR-4 . Was ist das Wesentliche der in den Normen beschriebenen Anforderungen:
- Jede Bibliothek muss in einem eigenen Namespace (dem sogenannten Vendor Namespace) leben.
- Jeder Namespace muss einen eigenen Ordner haben.
- Innerhalb des Namespace können sich Unterräume befinden - auch in separaten Ordnern
- Eine Klasse - eine Datei
- Der Dateiname mit der Erweiterung
.php
muss genau mit dem .php
übereinstimmen
Beispiel aus dem Handbuch:
Vollständiger Klassenname | Namespace | Basisverzeichnis | Voller Weg |
---|
\ Acme \ Log \ Writer \ File_Writer | Acme \ Log \ Writer | ./acme-log-writer/lib/ | ./acme-log-writer/lib/File_Writer.php |
\ Aura \ Web \ Response \ Status | Aura \ Web | / path / to / aura-web / src / | /path/to/aura-web/src/Response/Status.php |
\ Symfony \ Core \ Request | Symfony \ core | ./Vendor/Symfony/Core/ | ./vendor/Symfony/Core/Request.php |
\ Zend \ Acl | Zend | / usr / enthält / Zend / | /usr/includes/Zend/Acl.php |
Die Unterschiede zwischen diesen beiden Standards bestehen darin, dass PSR-0 alten Code ohne Namespace unterstützt (d. H. Vor Version 5.3.0) und PSR-4 frei von diesem Anachronismus ist und sogar unnötiges Verschachteln von Ordnern vermeidet.
Dank dieser Standards wurde es möglich, ein solches Tool wie
Composer zu entwickeln - einen universellen Paketmanager für PHP. Wenn jemand etwas verpasst hat, gibt es einen guten Bericht von
pronskiy über dieses Tool.
PHP-Injektion
Ich wollte auch über den ersten Fehler aller sprechen, die einen einzigen Einstiegspunkt für die Site in einer
index.php
und diese als MVC-Framework bezeichnen:
<?php $page = $_GET['page'] ?? die('Wrong filename'); if (!is_file($page)) { die('Wrong filename'); } include $page;
Sie sehen sich den Code an und möchten dort nur etwas Bösartiges übertragen:
// http://domain.com/index.php?page=../index.php // http://domain.com/index.php?page=config.ini // http://domain.com/index.php?page=/etc/passwd // , http://domain.com/index.php?page=user/backdoor.php
Das erste, was mir in den Sinn kommt, ist das gewaltsame Hinzufügen der
.php
Erweiterung. In einigen Fällen kann sie jedoch "dank" einer
Null-Byte-Sicherheitsanfälligkeit umgangen werden (lesen Sie, diese Sicherheitsanfälligkeit wurde schon
lange behoben , aber plötzlich stoßen Sie auf einen Interpreter, der älter als PHP 5.3 ist allgemeine Entwicklung empfehlen auch):
// http://domain.com/index.php?page=/etc/passwd%00
In modernen PHP-Versionen führt das Vorhandensein eines Null-Byte-Zeichens im Pfad der verbundenen Datei sofort zu dem entsprechenden Verbindungsfehler. Selbst wenn die angegebene Datei vorhanden ist und verbunden werden kann, ist das Ergebnis immer ein Fehler. Es wird wie folgt überprüft: strlen(Z_STRVAL_P(inc_filename)) != Z_STRLEN_P(inc_filename)
(dies ist aus dem Darm von PHP selbst)
Der zweite „lohnende“ Gedanke ist die Suche nach einer Datei im aktuellen Verzeichnis:
<?php $page = $_GET['page'] ?? die('Wrong filename'); if (strpos(realpath($page), __DIR__) !== 0) { die('Wrong path to file'); } include $page . '.php';
Die dritte, aber nicht die letzte Änderung der Prüfung ist die Verwendung der
open_basedir- Direktive. Mit ihrer Hilfe können Sie das Verzeichnis angeben, in dem genau PHP nach zu verbindenden Dateien sucht:
<?php $page = $_GET['page'] ?? die('Wrong filename'); ini_set('open_basedir', __DIR__); include $page . '.php';
Seien Sie vorsichtig, diese Anweisung wirkt sich nicht nur auf die Verbindung von Dateien aus, sondern funktioniert auch mit dem Dateisystem, d. H. Mit dieser Einschränkung müssen Sie sicherstellen, dass Sie nichts außerhalb des angegebenen Verzeichnisses vergessen haben, weder die zwischengespeicherten Daten noch Benutzerdateien (obwohl die Funktionen is_uploaded_file()
und move_uploaded_file()
weiterhin mit einem temporären Ordner für heruntergeladene Dateien funktionieren).
Welche anderen Prüfungen sind möglich? Viele Optionen hängen von der Architektur Ihrer Anwendung ab.
Ich wollte auch an die Existenz einer „wunderbaren“ Anweisung
allow_url_include erinnern (dies hängt von
allow_url_fopen ab ), mit der Sie Remote-PHP-Dateien verbinden und ausführen können, was für Ihren Server viel gefährlicher ist:
Säge, erinnere dich und benutze es nie. Der Vorteil ist standardmäßig deaktiviert. Sie benötigen diese Funktion etwas weniger als je zuvor. In allen anderen Fällen legen Sie die richtige Anwendungsarchitektur fest, bei der verschiedene Teile der Anwendung über die API kommunizieren.
AufgabeSchreiben Sie ein Skript, mit dem Sie PHP-Skripte aus dem aktuellen Ordner nach Namen verbinden können, während Sie sich an mögliche Schwachstellen erinnern und Fehler vermeiden.
Abschließend
Dieser Artikel ist die Basis in PHP. Studieren Sie also sorgfältig, erledigen Sie Aufgaben und archivieren Sie nicht, niemand wird für Sie unterrichten.
PS
Dies ist ein
Repost aus einer Reihe von Artikeln "PHP für Anfänger":
Wenn Sie Kommentare zum Material des Artikels oder möglicherweise in Form haben, beschreiben Sie das Wesentliche in den Kommentaren, und wir werden dieses Material noch besser machen.