PHP-Fehlerberichterstattung

Aus verschiedenen historischen Gründen gibt es im Bereich der Fehlerbehandlung und -meldung Tausende von schlechten Praktiken. Infolgedessen ist die Fehlerberichterstattung häufig unflexibel, schwer zu warten, nicht benutzerfreundlich oder sogar schädlich.

Doch der richtige Weg ist lächerlich einfach

TL; DR

Fügen Sie Ihrem PHP-Code in der Regel keine Fehlerberichtsoperatoren hinzu (insbesondere solche, die die Fehlermeldung direkt auf dem Bildschirm ausgeben). Lassen Sie stattdessen PHP selbst Fehler erzeugen. Und dann konfigurieren Sie PHP, um diese Nachrichten entsprechend zu leiten: Auf einem Entwickler-Server müssen sie auf dem Bildschirm angezeigt werden. Auf einem Live-Server sollten sie hingegen protokolliert werden, während einem Site-Besucher nur eine verallgemeinerte Fehlerseite ohne bestimmte Details angezeigt werden sollte.

Konfigurieren Sie dazu Ihr PHP wie folgt:

Beachten Sie, dass diese Werte besser in php.inioder in der Serverkonfiguration festgelegt werden sollten:

Nur als letzten Ausweg können Sie sie mit dem folgenden ini_set()Befehl direkt im Skript festlegen :

 error_reporting(E_ALL);
ini_set('display_errors', 1);

Bei einigen Fehlern (zum Beispiel beim Parsen von Fehlern) schlägt dies jedoch fehl.

Erstellen Sie dann einen Fehlerhandler für eine Live-Site, die im Falle eines PHP-Fehlers eine Ausredeseite anzeigt.

Um es kurz zu machen: Schreiben Sie Ihre Skripte ohne dedizierten Fehlerberichterstellungscode . Konfigurieren Sie stattdessen einfach PHP, um Fehler selbst zu melden. Erst wenn Ihr Code online ist, benötigen Sie Fehlerbehandlungscode. Dies kann jedoch ein kurzer Codeausschnitt sein, der alle Fehler auf einheitliche und zentralisierte Weise behandelt.

Die Regeln

Um die Fehlerberichterstattung auf die richtige Weise zu konfigurieren, sollten Sie immer die beiden folgenden Grundregeln befolgen:

Stattdessen sollte die Fehlermeldung zur späteren Bezugnahme protokolliert werden, während einem Site-Besucher nur eine allgemeine Fehlerseite angezeigt werden sollte.

Schlechte, schlechte Beispiele

Und eine solche Trennung - zwischen einem Live- und einem Entwickler-Server - ist die eigentliche Quelle der Verwirrung. Tatsächlich schreiben viele neue PHP-Benutzer ihren Code so, als wären sie immer die einzigen Benutzer ihrer Site , und leiten Fehlermeldungen direkt auf den Bildschirm. Glaubst du mir nicht Wie oft haben Sie einen solchen Code gesehen:

 // BAD example! Do not use!
$result = mysqli_query($conn, $query) or die(mysqli_error($conn));
oder

 // BAD example! Do not use!
try {
    $stmt = $pdo->query($sql);
} catch (PDOException $e) {
    die($e->getMessage());
}
?

Viele PHP-Tutorials, einschließlich des PHP-Handbuchs, verwenden diesen schrecklichen Ansatz. Anhand der obigen Regeln können Sie erkennen, warum es falsch ist: Es ist nur für die Entwicklung gedacht , aber für eine Live-Site absolut ungeeignet.

Auch Fehlermeldungen dürfen nicht unterdrückt werden . Das beinhaltet

All diese schlechten Praktiken hindern PHP daran, einem Programmierer das Problem zu erklären. Und wir haben bereits gelernt, dass Fehlermeldungen extrem wichtig sind.

Wie lässt sich also die Fehlerbehandlung so gestalten, dass diese widersprüchlichen Richtlinien erfüllt werden? Zeigen Sie einem Entwickler Fehler mit aller Kraft und verbergen Sie sie dennoch vollständig vor einem Site-Besucher.

Lass sie einfach in Ruhe!

Die Antwort ist überraschend einfach: Lassen Sie Fehlermeldungen einfach in Ruhe

Obwohl es für viele PHP-Benutzer ziemlich fremd klingt (die sich eine einzelne Datenbankinteraktion ohne mehrere Zeilen sorgfältig geschriebenen Fehlerbehandlungscodes nicht vorstellen können), ist dies die Wahrheit: Wenn Sie die Fehlermeldung in Ruhe lassen, wird die Handhabung flexibel und einfach konfigurierbar , so dass Sie mit nur einer PHP-Konfigurationsoption zwischen den Modi wechseln können!

Denken Sie nur daran: Es gibt eine php.iniDirektive namens display_errors. Wenn Sie den Wert auf 1 setzen, wird unsere Site in den Entwicklungsmodus versetzt, da PHP automatisch alle aufgetretenen Fehler anzeigt, ohne dass unsererseits Anstrengungen unternommen werden müssen! Wenn Sie den Wert auf 0 setzen, werden Fehlermeldungen vollständig stummgeschaltet, wodurch ein Hacker von den kritischen Informationen ausgeschlossen und der Benutzer weniger verwirrt wird. Trotzdem müssen wir eine Seite mit Ausreden zeigen, aber das ist eine etwas andere Geschichte, die wir später diskutieren werden.

Das Gleiche gilt für Ausnahmen: Eine nicht abgefangene Ausnahme verursacht einen regulären PHP-Fehler. Es macht also keinen Sinn, einen speziellen Code zu schreiben, um ihn nur anzuzeigen: PHP kann Ihnen bereits Fehler anzeigen! Lassen Sie es einfach in Ruhe und es verhält sich genau wie jeder andere Fehler gemäß den Regeln für die gesamte Site.

Was ist, wenn sich eine Funktion weigert, einen Fehler auszulösen?

Es gibt einige PHP-Module, die standardmäßig unbemerkt fehlschlagen, anstatt einen Fehler auszulösen. Nun, höchstwahrscheinlich haben sie eine geheime Konfigurationsoption, mit der sie sich wie gute Jungs verhalten - Fehler selbst auslösen. Wenn einige PHP-Funktionen keinen Fehler auslösen, aber anscheinend fehlschlagen, suchen Sie nach der Option, die Fehlerberichterstattung für das zugehörige Modul zu aktivieren. Beispielsweise könnte (und sollte) PDO so konfiguriert sein, dass Ausnahmen ausgelöst werden , ebenso wie mysqli .

Aber manchmal gibt es überhaupt keine Möglichkeit, eine Funktion dazu zu bringen, ihren Fehler von sich aus zu melden. Ein gutes Beispiel ist die späte mysql_query()Funktion oder json_decode()*: Beide kehren false im Fehlerfall stillschweigend zurück . In diesem Fall sollten Sie sich noch einmal umschauen und den für diese Funktion vorgesehenen Fehlermeldungsanbieter suchen. In unserem Fall wird es sein mysql_error()und json_last_error_msg()jeweils.

Die Ausgabe dieser Funktionen sollte jedoch niemals an den berüchtigten die()Bediener weitergeleitet werden!

Stattdessen sollte es in einen PHP-Fehler übertragen werden. Am einfachsten geht das mit der trigger_error()Funktion:

 $result = mysql_query($query) or trigger_error(mysql_error());

Für den Fall, dass Sie mit Legacy-Code umgehen müssen, der letztendlich diese veraltete Erweiterung verwendet, die 2015 vollständig aus der Sprache entfernt wurde, sollte zumindest die Fehlerberichterstattung auf diese Weise erfolgen.

Noch besser wäre es, eine Ausnahme zu machen. Am einfachsten ist es, das Ergebnis der Funktion zu testen und einen throwOperator hinzuzufügen (beachten Sie or, dass die Bedingung explizit sein sollte, wenn Sie sie nicht sauber mit dem Operator verwenden können):

 if ($error = json_last_error_msg())
{
    throw new \Exception("JSON decode error: $error");
}

Aber das ist nur eine schnelle und schmutzige Lösung. Am besten erstellen Sie für einen solchen Fehler eine eigene Exception-Klasse. Es ist keine große Sache, im Grunde ist es nur eine einzelne Zeile:

 class JsonDecodeException extends ErrorException {}

Nachdem wir die dedizierte Klasse definiert haben, können wir eine gesprächigere Version erstellen von json_decode():

 function jsonDecode($json, $assoc = false){
    $ret = json_decode($json, $assoc);
    if ($error = json_last_error()){
        throw new JsonDecodeException(json_last_error_msg(), $error);
    }
    return $ret;
}

Jetzt wird es anfangen, den Fehler auszuspucken und uns den Grund zu nennen, wie erwartet:

 Fatal error: Uncaught exception 'JsonDecodeException' with message 'Syntax error' in /home/me/test.php:9
Stack trace:
#0 /home/me/test.php(14): jsonDecode('foobar')
#1 {main}
  thrown in /home/me/test.php on line 9

Diese Nachricht wird entweder an den Bildschirm oder an das Fehlerprotokoll gesendet und teilt uns mit, dass beim Dekodieren von JSON ein Fehler aufgetreten ist, der auf die falsche JSON-Syntax zurückzuführen ist.

* Update: Seit 7.3.0 ist es möglich , json_decode anzuweisen, eine Ausnahme auszulösen.

Konvertieren von Fehlern in Ausnahmen

In der Vergangenheit gab es nur Fehler in PHP. Dann wurden in PHP 5 Ausnahmen zur Sprache hinzugefügt und seitdem bewegt sich PHP langsam, um Fehler durch ErrorExceptions zu ersetzen. Da dieser Prozess noch nicht abgeschlossen ist, müssen wir uns mit zwei möglichen Arten von Fehlern befassen - Fehlern und ErrorExceptions. Es ist nicht so bequem. Außerdem sind Ausnahmen vielseitiger als Fehler - sie können abgefangen werden und enthalten standardmäßig einen Stack-Trace . Überlegen Sie sich daher, alle Fehler in Ausnahmen umzuwandeln, da dies eine einheitliche Fehlerbehandlung ermöglicht. Der Code dafür ist lächerlich einfach:

set_error_handler(function ($level, $message, $file = '', $line = 0){
    throw new ErrorException($message, 0, $level, $file, $line);
});

Alles, was wir tun müssen, ist, eine set_error_handler () -Funktion aufzurufen und sie anzuweisen , eine Ausnahme auszulösen .

Wenn Sie diese drei Zeilen zu Ihren PHP-Scripten hinzufügen, können Sie PHP-Fehler abfangen ! Zum Beispiel der folgende Code

 try {
    $a = 1/0;
} catch (Throwable $e) {
    echo "Come on, you cant't divide by zero!";
}

wird Ihnen sagen, nicht durch Null zu teilen, anstatt einen Fehler zu werfen. Ganz bequem. Und wieder wird die Fehlerbehandlung vereinheitlicht.

Zeigt eine schöne Seite für einen Benutzer

In Ordnung, es ist in Ordnung mit einem Programmierer, sie werden über jeden aufgetretenen Fehler entweder aus einer Protokolldatei oder einfach durch Beobachten des Bildschirms benachrichtigt. Aber was ist mit einem Site-Benutzer? Wenn die richtige Fehlerberichterstattung eingestellt ist, sehen sie sich nur einer leeren Seite gegenüber, was anscheinend nicht der richtige Weg ist. Wir müssen ihnen einige Erklärungen zeigen und sie bitten, es später zu versuchen. Dafür gibt es verschiedene Möglichkeiten. Am einfachsten konfigurieren Sie Ihren Webserver so, dass im Fehlerfall eine solche Seite angezeigt wird. Um beispielsweise Nginx so zu konfigurieren, dass bei 500 Fehlern eine benutzerdefinierte Fehlerseite angezeigt wird, erstellen Sie einfach eine solche benutzerdefinierte Fehlerseite und fügen Sie diese Zeile der Serverkonfiguration hinzu:

error_page 500 502 503 504 /500.html

Voila! Im Fehlerfall wird diese Seite anstelle eines leeren Bildschirms angezeigt, was Ihre Benutzer glücklich macht! Vergessen Sie nicht, die eigentliche 500.htmlSeite zu erstellen und im Site-Stammverzeichnis abzulegen.

Es wird jedoch berichtet, dass Apaches mod_php Probleme mit seiner ErrorDocumentDirektive und einem von PHP erzeugten 500-Fehler hat. Um die Lösung stabiler zu machen und mehr Kontrolle über die Fehlerbehandlung zu haben, ist es besser, Fehler auf der PHP-Seite zu behandeln, aber dennoch zentral zu behandeln.

Dafür bietet PHP zwei Funktionen, set_error_handler () und set_exception_handler () . Beide registrieren eine Funktion, die aufgerufen wird, wenn ein Fehler oder eine nicht erfasste Ausnahme auftritt.

Da wir die Fehlerbehandlungsfunktion bereits zum Konvertieren von Fehlern in Ausnahmen verwendet haben, benötigen wir jetzt nur noch eine Ausnahmebehandlungsfunktion.

set_exception_handler(function ($e){
   // here we can put some code to notify a user about an error.
});

Die universelle Handhabungsfunktion

Aber eine solche Funktion nur für die Bequemlichkeit eines Benutzers zu haben, macht den Programmierer unglücklich, da dieser die eigentliche Fehlermeldung sehen möchte. Erstellen wir also eine universelle Funktion, die beide befriedigen kann. Dazu benötigen wir einige Parameter, um eine Entwicklungsumgebung von einer Produktionsumgebung zu unterscheiden. Es gibt viele mögliche Lösungen, in meinem Fall würde ich nur den display_errorsKonfigurationsparameter php.ini verwenden. Da es auf dem Produktionsserver auf 0 gesetzt werden muss, können wir damit feststellen, dass wir uns in der Produktionsumgebung befinden. Aber natürlich können Sie Ihre eigenen Marker verwenden, zum Beispiel mit einer constantsehr gebräuchlichen Lösung.

 set_exception_handler(function ($e)
{
    error_log($e);
    http_response_code(500);
    if (ini_get('display_errors')) {
        echo $e;
    } else {
        echo "

500 Internal Server Error

An internal server error has been occurred.
Please try again later."; } });

Beachten Sie, dass dies eine sehr grundlegende Funktion ist. Es könnten viele Ergänzungen vorgenommen werden, um die Ausgabe sowohl für einen Site-Benutzer als auch für einen Programmierer zu verbessern. Dies ist jedoch bereits eine robuste Lösung, mit der Ihre Site den grundlegenden Regeln für die Fehlerberichterstattung entspricht.

Umgang mit schwerwiegenden Fehlern

Eine letzte Sache, um unsere Fehlerbehandlung vollständig zu vereinheitlichen.

Schwerwiegende Fehler werden vom Standardfehlerbehandler nicht abgefangen. Um sie wie andere Fehler zu behandeln, benötigen wir eine weitere Funktion, register_shutdown_function, die PHP anweist, bei jedem Abbruch der PHP-Ausführung eine Funktion aufzurufen, sowohl aus natürlichen Gründen als auch im Fehlerfall. Daher müssen wir den letzteren von dem ersteren unterscheiden, für den error_get_last () verwendet wird, und auch die Fehlerinformationen bereitstellen:

 register_shutdown_function(function ()
{
    $error = error_get_last();
    if ($error !== null) {
        // here we can put some code to notify a user about an error.
    }
});

Leider wird diese Funktion zu einem Zeitpunkt aufgerufen, an dem keine intelligente Behandlung möglich ist. Beispielsweise wird eine in dieser Funktion ausgelöste Ausnahme nicht mit dem Try-Catch-Operator abgefangen. Alles, was wir tun können, ist eine grundlegende Fehlerbehandlung.

Der vollständige Fehlerbehandlungs-Beispielcode

Versuchen wir nun, alles, was wir gelernt haben, zusammenzufassen und eine grundlegende Fehlerbehandlungslösung zu erstellen.

Zuerst müssen wir grundlegende Fehlerbehandlungsanweisungen in der PHP / Web-Server-Konfiguration konfigurieren, wie hier beschrieben . Dann erstellen wir eine Datei error_handler.phpmit dem folgenden Code und fügen sie in die Bootstrap-Datei ein:

<?php

error_reporting(E_ALL);

function myExceptionHandler ($e)
{
    error_log($e);
    http_response_code(500);
    if (ini_get('display_errors')) {
        echo $e;
    } else {
        echo "

500 Internal Server Error

An internal server error has been occurred.
Please try again later."; } } set_exception_handler('myExceptionHandler'); set_error_handler(function ($level, $message, $file = '', $line = 0) { throw new ErrorException($message, 0, $level, $file, $line); }); register_shutdown_function(function () { $error = error_get_last(); if ($error !== null) { $e = new ErrorException( $error['message'], 0, $error['type'], $error['file'], $error['line'] ); myExceptionHandler($e); } });

Hier definieren wir eine gemeinsame Fehlerbehandlungsfunktion, MyExceptionHandler()die die gesamte Fehlerbehandlungslogik kapselt. Dann richten wir den Ausnahmehandler ein, der unsere Funktion aufruft, wenn eine nicht erfasste Ausnahme auftritt.
Außerdem werden alle PHP-Fehler in Ausnahmen konvertiert, wie oben beschrieben, und werden daher vom gleichen Code verarbeitet.
Dieselbe Behandlungsfunktion wird schließlich auch für die schwerwiegenden Fehler aufgerufen.

Natürlich könnte diese Basislösung stark verbessert und erweitert werden. Die Idee hier ist jedoch, ein kurzes, leicht verständliches Lehrbeispiel zu liefern, das dennoch auch im Produktionscode verwendet werden kann. Zögern Sie nicht, diesen grundlegenden Ansatz zu erweitern, um Ihre Bedürfnisse zu befriedigen.

Wenn Sie Ideen zur erweiterten Fehlerbehandlung haben möchten, schauen Sie sich den Fehlerbehandlungscode von Laravel an