PHP/Tutorials/Dateien mittels include einbinden

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

In diesem Tutorial lernen Sie, wie Sie immer wiederkehrende Elemente Ihrer Webseiten, wie Navigation, Seitenkopf <header> und die Fußzeile <footer> in eigene Dateien auslagern können, die Sie dann problemlos einbinden können. Änderungen, wie das Einfügen eines Links zu einer neuen Seite, müssen dann nur einmal zentral erledigt werden.

Gerade bei mittelgroßen Webprojekten kann die ständige Aktualisierung zahlreicher Seiten hohen Arbeitsaufwand bedeuten. Alternativ käme der Einsatz eines CMS in Frage.

Inhaltsverzeichnis

[Bearbeiten] Kann mein Webspace PHP?

Heutzutage ist PHP Standard bei allen Providern. Sie können dies einfach testen, indem Sie folgendes Programm auf ihren Server laden.

Beispiel: phpinfo.php
<?php
  phpinfo();
?>

Falls PHP installiert ist, gibt Ihnen dieses kleine Programm sehr viele Informationen über ihre PHP-Installation und den Webserver aus, was recht nützlich ist. Merken Sie es sich also gut.

Screenshot der Ausgabe von phpinfo() in PHP 7.1

Allerdings kann es sein (und ist nicht wirklich selten!), dass Webhoster in dem (fragwürdigen) Glauben, hierdurch die Sicherheit der Installation zu erhöhen, die Abarbeitung der Funktion phpinfo() verbieten. In dem Fall testen Sie mit:

Beispiel: phptest.php
<?php echo 'PHP? Funktioniert! Version ist: ', phpversion(), "\n"; ?>

Wird etwas wie: PHP? Funktioniert! Version ist: 7.1.0 ausgegeben, dann ist PHP ganz offensichtlich tätig geworden, also installiert und konfiguriert.

[Bearbeiten] Auslagern von Seitenkopf und Navigation

Als Beispieldatei verwenden wir unsere Webseite aus dem HTML5-Tutorial.

Da der head neben dem Seitentitel auch weitere Metaangaben hat, die für jede Seite unterschiedlich sein sollen, schneiden wir nur den <header> aus unserem Dokument aus und speichern ihn als header.php ab:

Beispiel: ausgelagerte Navigation in header.php
  <header>
    <img src="logo.gif" alt="logo">
    <h1>Titel</h1>
    <nav>
      <ul>
        <li><a href="#link_1.html">Wiki</a></li>
        <li><a href="#link_2.html">Blog</a></li>
        <li><a href="#link_3.html">Forum</a></li>
      </ul>
    </nav>
  </header>

Mit dem Footer können wir genauso verfahren.

[Bearbeiten] Einbinden in unsere Webseite

Beispiel: webseite.php
<!doctype html> <head> <meta charset="utf-8"> <title>Meine erste HTML5-Seite</title> </head> <body> <?php include ("header.php"); ?> <main> <article> <h1>Überschrift</h1> <p>Dies ist meine erste HTML5-Seite</p> ... mehr Inhalt </article> <aside> <h2>Weiterführende Links</h2> <ul> <li><a href="#link_1.html">Wiki</a></li> <li><a href="#link_2.html">Blog</a></li> <li><a href="#link_3.html">Forum</a></li> </ul> </aside> </main> <?php include ("footer.php"); ?> </body> </html>

Anstelle der ausgeschnittenen Code-Abschnitte fügen wir jeweils ein include() ein, das die jeweilige Datei aufruft. Damit der PHP-Befehl erkannt und ausgeführt wird, müssen wir unsere HTML-Datei in index.php umbenennen. Die includierten Daten müssen nicht die Endung .php haben, diese ist völlig beliebig! Die Dateien sollten die Endung .php aber durchaus haben, wenn es Dateien sind, die php-Code enthalten – allein schon weil gute Editoren dann das Syntax-Highlighting entsprechend einschalten. Dann bietet sich ggf. zur Klarheit auch die Kombination mehrerer Endungen an, z. B. datei.inc.php. Wenn in den includierten Dateien PHP-Code vorkommt, dann muss dieser auch als PHP-Code markiert sein. Also das öffnende <?php und ggf. schließende ?> enthalten.

Verzeichnisstruktur bei der Verwendung von Includes

Von außen sieht niemand, dass die Navigation aus einer ausgelagerten Datei stammt, da der Browser nur den ihm gelieferten HTML-Code zu Gesicht bekommt und darstellt.

Beachten Sie: Während Sie HTML-Dateien einfach von Ihrer Festplatte aus öffnen können, benötigen Sie für PHP-Dateien einen PHP-Interpreter auf einem Webserver. Sie können auch auf Ihrem Computer einen solchen Webserver einrichten.

[Bearbeiten] Die Alternativen include, include_once, require, require_once - Regeln zur Verwendung

  • include benutzen Sie, wenn Sie eine Datei (womöglich) mehrfach einbinden wollen und PHP die Verarbeitung des Skriptes nicht abbrechen soll, falls diese Datei nicht vorhanden ist. Ist die Datei nicht vorhanden wird allerdings eine Notiz „geworfen“, welche man mit sinnvollen Einstellungen des Error-Reportings unterbinden kann.
  • include_once benutzen Sie, wenn Sie eine Datei genau einmal einbinden wollen und wenn PHP die Verarbeitung des Skriptes nicht abbrechen soll, falls diese Datei nicht vorhanden ist. Einen Versuch, die Datei mehrfach einzubinden, wird PHP schweigend (also ohne Fehlermeldung oder Notiz) übergehen. Ist die Datei nicht vorhanden wird allerdings eine Notiz „geworfen“, welche man mit sinnvollen Einstellungen des Error-Reportings unterbinden kann.
  • require benutzen Sie, wenn Sie eine Datei (womöglich) mehrfach einbinden wollen und wenn PHP die Verarbeitung des Skripts definitiv mit einem Fehler abbrechen soll, wenn diese Datei nicht vorhanden oder nicht lesbar ist.
  • require_once benutzen Sie, wenn Sie eine Datei genau einmal einbinden wollen und wenn PHP die Verarbeitung des Skripts definitiv mit einem Fehler abbrechen soll, wenn diese Datei nicht vorhanden oder nicht lesbar ist. Einen Versuch, die Datei mehrfach einzubinden, wird PHP schweigend (also ohne Fehlermeldung oder Notiz) übergehen.

[Bearbeiten] Beispiele:

[Bearbeiten] include('datei')

include wird gerne genutzt, um wiederkehrende Abschnitte einzubinden. Das Szenario ist folgendes. Ein Benutzer darf z. B. Dateien mit dem Namen produktversion.VERSION.inc.php bearbeiten, damit nicht etwa ein (teurer) Programmierer inhaltliche Änderungen an der Webseite vornehmen muss. Eine der vorgesehenen Dateien existiert aber (noch) nicht:

Beispiel: produktversion.0815.inc.php
<?php
$P_titel = "Toller Teppich";
$P_descr = "Diesen tollen Teppich können Sie nach Belieben ein- oder ausrollen.";
Beispiel: produktversion.0815-rot.inc.php
<?php
$P_titel = "Toller roter Teppich";
$P_descr = "Diesen tollen roten Teppich können Sie nach Belieben ein- oder ausrollen.";


In der includierenden PHP-Datei sieht das dann so aus:

Beispiel: include_products.php
<?php
$arVersionen=array('0815', '0815-rot', '0815-gruen');
foreach ($arVersionen as $version) {
   include ('produktversion.' . $version . '.inc.php'); # Hier werden die Variablen überschrieben!
   echo '<h4>', $P_titel, '</h4><p>', $P_descr, '</p>';
}

Da die Datei „produktversion.0815-gruen.inc.php“ nicht existiert, wird einfach schweigend nochmals auf die durch das Inkludieren der Datei „produktversion.0815-rot.inc.php“ gesetzten Variablen zurückgegriffen. Die Notiz (Warnung) kann man ja unterdrücken. Es würde also (es werden nur die Überschriften gezeigt)

  • „Toller Teppich“
  • „Toller roter Teppich“
  • „Toller roter Teppich“

ausgegeben. Manchmal ist das tatsächlich erwünscht. Allerdings sollte nicht verschwiegen werden, dass dieses „Manchmal“ eine recht seltene Ausnahme ist. Und weil das so ist, gibt es mit require() bzw. require_once() andere Lösungen, aber wir sehen uns erst einmal einen sinnvollen Einsatz von include_once() an:

[Bearbeiten] include_once('datei')

Szenario: In einem größeren Projekt werden Informationen verarbeitet und der Programmierer möchte mit Konstanten arbeiten. Es wird weiter ziemlich viel mit includes gearbeitet. Jetzt kann es aber passieren, dass nicht klar ist, ob eine Datei, nennen wir sie „settings.inc“, bereits inkludiert ist und dass das mehrfache Inkludieren zu einem Fehler führt:

Beispiel: settings.inc
<?php
define ('PROJEKTNAME', 'Fehler vermeiden');

Würde man jetzt diese Datei mehrfach inkludieren, dann würde daraus ein Fehler entstehen:

Beispiel: include.php
<?php
include ('settings.inc');
## weiter unten:
include ('settings.inc');

Denn das würde aus Sicht von PHP zu folgendem Resultat führen:

Beispiel: Was der Interpreter daraus macht:
define ('PROJEKTNAME', 'Fehler vermeiden');
## weiter unten:
define ('PROJEKTNAME', 'Fehler vermeiden');

Eine Konstante ist aber eine Konstante und kann ergo im Programmverlauf nur einmal definiert werden! Der Interpreter würde also einen Fehler melden. Deshalb gibt es include_once():

Beispiel: include_once.php
include_once ('settings.inc');
## weiter unten:
include_once ('settings.inc');
echo '<h1>', PROJEKTNAME, '</h1>';

Der Interpreter stellt fest, er soll diese Datei nur dann inkludieren, wenn er das noch nicht getan hat. So ist einerseits (noch nicht ganz, wie wir gleich sehen werden) sicher gestellt, dass die Informationen geladen werden, aber eben auch, dass nicht mehrfach versucht wird, die Konstanten zu definieren. Die Ausgabe wäre im Erfolgsfall:

<h1>Fehler vermeiden</h1>

[Bearbeiten] require('datei') bzw. require_once('datei')

Was aber passiert, wenn durch einen Fehler die angegebene Datei settings.inc nicht existiert oder nicht lesbar ist? Dann gäbe PHP eine Notiz aus:

<h1>PHP Notice: Use of undefined constant PROJEKTNAME - assumed 'PROJEKTNAME' in - on line 2 PROJEKTNAME</h1>

oder, wenn die Notizen z. B. durch error_reporting(0) unterdrückt sind:

<h1>PROJEKTNAME</h1>

PROJEKTNAME soll da nicht stehen! Das ist natürlich ein „Kardinalfehler“ – und da kommt require() bzw. require_once() ins Spiel. Denn require() würde einen Fehler melden und PHP würde die Skriptverarbeitung abbrechen:

Beispiel: require_once.php
require_once ('settings.inc');
## weiter unten:
require_once ('settings.inc');
echo '<h1>', PROJEKTNAME, '</h1>';

PHP Fatal error: require(): Failed opening required 'settings.inc' (include_path='.:/usr/share/php:/usr/share/pear') in - on line 1

require_once() verhindert dann genau so wie include_once(), dass mehrfach (und vergeblich!) versucht wird die Datei zu includieren und damit die Konstante wiederholt zu definieren.

[Bearbeiten] Ein Wort zur Sicherheit: Externe Quellen für den Namen der zu includierenden Datei sind gefährlich!

Fehler in diesem Abschnitt bitte korrigieren oder, wenn Sie sich das nicht zutrauen, per Email melden:

Informationen zur Autor(in)

Name:
Jörg Reinholz
E-Mail:
Homepage:

So manchen wird die Möglichkeit, Dateien so einfach einzubinden auf so manche, nur vordergründig gute Idee bringen. Nämlich die, etwas zu schreiben wie:

Beispiel: selbstmord.php
<?php
  include ($_GET['file']);

Sollte das jemandem auffallen - und das wird es, dann dauert es womöglich nur Minuten bis dieser etwas wie das folgende versucht:

http://localhost/selbstmord.php?file=../../../../../etc/passwd

Mit dem Resultat, dass er den Inhalt der Datei /etc/passwd enthält. Und damit auf einem Linux-System eine für ihn wertvolle Datei mit Benutzerinformationen. Solche Angriffsversuche sind nicht etwa selten, sondern tägliches, automatisiert stattfindendes Geschehen und es gibt terabyteweise Benutzernamen/Passwörter und Bankdaten, gestohlen von ziemlich naseweisen Serverbetreibern im Internet, welche beweisen, dass derart grob nachlässig gehandelt wurde.

[Bearbeiten] Sich gegen diesen Angriff absichern

Möglichkeit: Lassen Sie den Angreifer den Name der Datei nicht (vollständig!) bestimmen:

Beispiel: immer_noch_sehr_gefährlich.php
<?php
  include ($_GET['file'] . '.html');

Jetzt wird an jede Datei die Endung '.html' angehängt. Das kann aber immer noch nicht erwünscht sein, weil Sie womöglich folgendes nicht wollen:

http://localhost/gefaehrlich.php?file=../gesperrte_dateien/foo

… würde die Datei /gesperrte_dateien/foo.html einbinden und also ausliefern. Das kann nur unschön sein aber auch der berufliche Selbstmord und in manchen Fällen sogar zu erheblichen juristischen Problemen führen, denn die Datei war ja nicht grundlos im Ordner „gesperrte_dateien“. An vielen Stellen wird empfohlen open_basedir in der php.ini zu setzen:

Beispiel: nur_angeblich_nicht_ganz_so_gefährlich.php
<?php
  iniset ('open_basedir', (__DIR__. '/files/'); 
  include ($_GET['file'];)

Das hält aber auch nicht mehr, als sehr genau das, was es verspricht, denn spätestens eine Datei .htaccess wird aus dem Ordner files ausgeliefert und es ist höchst fragwürdig, ob das gut ist, denn die Programmierer verlassen sich oft darauf, dass eine Datei, die mit „.ht“ beginnt, auch nicht ausgeliefert wird!

[Bearbeiten] Nachdenken!

Um so etwas also hinreichend sicher zu machen, muss man den übergebenen Wert doch etwas aufwändiger untersuchen. Es gibt nämlich, neben dem Ausschluss eines Angriffs auf das System selbst, jede Menge weiterer guter Gründe, Zugriffe nicht zuzulassen und diese Zugriffe dürfen dann auch nicht über einen Umweg also über ein nassforsches include geschehen.

Beispiel: include_mit_vielen_pruefungen.php
<?php
 
  ## Erlaubte Dateien sollen die Endung 'inc' haben, Diese Endung wird aber NICHT übergeben
  ## sondern vom Skript angehängt:
  #define('MY_INCLUDE_EXT', 'inc'); #ohne Punkt!
    
  ## Erlaubte Dateien sollen im Unterverzeichnis /lib stehen. Das Verzeichnis  wird aber NICHT übergeben,
  ## sondern davor gesetzt:
  #define('MY_INCLUDE_DIR', __DIR__ . '/lib/');
  
  # Extra-Wunsch aus der Abteilung:
  # Es gäbe eine weitere Software, die Dateien nicht löscht, sondern mit der Endung
  # .deleted oder .forbidden versieht, die natürlich auch nicht ausgeliefert werden sollen:
  # Notiert als Array regulärer Ausdrücke:
  $arMY_EXTRA_NOINCLUDES = array(
    '/\.deleted$/' ,
    '/.forbidden$/'
  );
 
 
  safer_include( $_GET['file'], $arMY_EXTRA_NOINCLUDES);
  exit;
 
  function safer_include($fileName, $arMY_EXTRA_NOINCLUDES=false) {
      ##Vermeintliche Sicherheit lasse ich weg:
      #iniset ('open_basedir', (__DIR__. '/files/'); 

      # Aus gutem Grund brauchen wir eine Kopie;
      $fileNameKopie=$fileName;
 
      # Regeln für den Dateinamen:
      # darf ../ nicht enhalten.
      # darf /.. nicht enhalten.
      # darf /. nicht enthalten. (Versteckte Dateien unter Unix beginnen mit einem Punkt) Regel darüber kann weggelassen werden.
      # darf keine URL enthalten (die haben etwas wie :// drin: ftp:// https:// http:// gopher:// bei Windows-Freigaben kann auch \\ oder // vorkommen:
      $arForbiddenStrings = array('../', '/.', '://', '\\\\', '//' );
      # Die werden jetzt brutal durch 'Nichts' ersetzt:
      $fileName=str_replace($arForbiddenStrings, '', $fileName);
 
      # Wozu habe ich die Kopie gebraucht? Ganz einfach. Entspricht jetzt $_GET['file'] nicht mehr der Kopie,
      # dann hat jemand etwas verbotenes versucht.
      if (! $fileName == $fileNameKopie ) {
	return error_403();
      }
 
      # Eine verdammt gute Idee ist es jetzt, alle Zeichen zu verbieten, die auch nur irgendwie schädlich sein KÖNNTEN
      # und das sind alle die ich nicht explizit erlaube. 
      # Erlaubt werden 
      ### alle Buchstaben (ASCII!), 
      ### alle Ziffern, 
      ### der Unterstrich, das Minus, das Plus, der Asterix, der Doppelpunkt (Vorsicht auf alten Macintoshs!), der Punkt und das Komma
      ### Wenn das nicht genügt, dann ist diese Liste entsprechend vorsichtig zu erweitern und zu dokumentieren, auch WER das WANN und WARUM verlangt hat
      
      # Dafür ist ein regulärer Ausdruck eine gute Idee:
      #$forbiddenChars='/[^A-Za-z0-9_.+*:,-]/';   #Standard: Nicht mit Unterverzeichnissen möglich
      $forbiddenChars='/[^A-Za-z0-9_.\/*+:,-]/'; # Mit Unterverzeichnissen möglich, Anordnung von AL Frank Meier, per Mail am 15.1.2015 13:12
            
      if ( preg_match($forbiddenChars, $fileName) ) {
	# Brutal:
	return error_403();
      }
 
      # jetzt noch die Prüfung, ob der übergebene String etwa mit einem Punkt beginnt:
      if ( $fileName && '.' == $fileName{0} ) {
	# Brutal:
	return error_403();
      }
 
      # Die extra-Ausschlüsse aus dem Array:
      if ( is_array($arMY_EXTRA_NOINCLUDES) ) {
	foreach ($arMY_EXTRA_NOINCLUDES as $s)
	  if ( preg_match($s, $fileName) ) {
	    # Brutal:
	    return error_403();
	  }
      }
 
      ## wenn sich alle Dateien in einem bestimmten Verzeichnis befinden sollen:
      if ( defined('MY_INCLUDE_DIR') ) { 
        $fileName = MY_INCLUDE_DIR . '/' . $fileName;
      }
 
      ## wenn alle zu includierenden Dateien eine Endung erhalten sollen
      if ( defined('MY_INCLUDE_EXT') ) { 
        $fileName = $fileName . '.' . MY_INCLUDE_EXT; 
      }
 
      # So! Noch ein kleiner Test, ob die Datei existiert:
      if ( ! is_file($fileName ) || ! is_readable($fileName) ) {
        return error_404($fileName);
      }
      #Feuer frei!
      include($fileName);
      return true;
  }
  function error_403() {
    header('HTTP/1.0 403 Forbidden');
    echo "403 Forbidden (Nice try!)";
    return false;
  }
 
  function error_404($fileName) {
    header('HTTP/1.0 404 Not Found');
    echo '404: File "', htmlspecialchars($fileName) , '" not found';
    return false;
  }

[Bearbeiten] Sehr sicher und vergleichsweise einfach: includieren mit abstrakten Angaben (Zahlen)

Natürlich besteht auch die sehr sichere Möglichkeit, die zu includierenden Dateien nicht mit deren Name anzugeben, sondern z. B. mit einer (abstrakten) Nummer. Dann muss das Skript nur den richtigen Name der Datei aus einer Ressource heraussuchen. Das ist sehr leicht zu programmieren:

Beispiel: include_nach_zahlen.php
<?php
## aufruf mit include_per_nr.php?f=11
## In der nicht geheimen, aber sicher (ohne Schreibzugriff!) gespeicherten Datei 'includes.csv' stehen 
## die Datei und deren Nummer:
define ('INC_TABLE', 'includes.csv');
## So sieht diese aus: 
/*
# Beginn mit 1!
1;Foo.txt
2;Bar.txt
*/
 
# Aufruf:
my_include_per_number($_GET['f']);
exit;
 
function my_include_per_number ($nr) {
   # Wir sorgen dafür, dass es eine ganze Zahl oder 0 ist:
   $nr=intval($nr);   
   # Nur wenn es nicht Null ist:
   if ($nr) {
      # Die Ressource in Zeilen zerlegen
      $lines=file(INC_TABLE);
      # und diese abarbeiten:
      foreach ($lines as $line) {
         # Das Zeilenende wird von file() nicht entfernt!
         $line=trim($line); 
         # Ups. Die Zeile kann leer sein oder eine Kommentarzeile 
         # und wir erwarten ein Semikolon, und das nicht an erster Stelle:
         if ($line && ('#' != $line{0}) && (strpos($line, ';') ) ) {
            # Wir zerlegen die Zeile am Semikolon in zwei Teile:
	    list($i, $filename) = explode(';', $line, 2);
            # Ein kleiner Vergleich:
	    if ( intval($i) == $nr ) {
               # Die Datei existiert und ist lesbar?
	       if ( is_file($filename) && is_readable($filename) ) {
		 return include($filename);
	       } else {
		 return error_404();
	       }
	    }
	 }
      }
   } 
   error_404();
}
 
function error_404() {
   header('HTTP/1.0 404 Not Found');   
   echo('404 Not found');
   return false;
}

[Bearbeiten] Weiterführende Informationen

Meine Werkzeuge
Namensräume

Varianten
Aktionen
Übersicht
Index
Mitmachen
Werkzeuge
Spenden
SELFHTML