PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Auf Warnings, Notices und sogar Fatal Errors reagieren

with 14 comments

In besonders kritischen Scripten baue ich gern zur Sicherheit noch eine erweiterte Log-Funktion ein, die auch noch aufgerufen wird bei einem FATAL ERROR oder bei einem exit() oder die() in einer Fremdbibliothek (pfui). Man kann so auch beim Vorkommen einer Notice das Script beenden, das kann manchmal sehr sinnvoll sein. Natürlich kann man mit Hilfe der error_log Einstellung in der php.ini und einem error_reporting „E_ALL^E_STRICT“ diese Fehler auch wegloggen, aber man ist nicht flexibel wenn man im Code darauf reagieren möchte. Beispielsweise könnte es sein dass bei einem FATAL ERROR noch etwas aufzuräumen ist, damit kein Müll hinterlassen wird.

Nehmen wir folgendes Beispiel:

<?php
register_shutdown_function('shutdownFunction');
function shutDownFunction() {
    $error = error_get_last();
    if ($error['type'] == 1) {
        // log error to syslog, send email or sms, this should not happen!
    }
}

echo nonExistingFunction();

Bei sauberem Code sollte das natürlich nicht passieren. Aber es gibt Situationen in denen ein Fatal Error passieren kann, wenn man beispielsweise eine Funktion aufruft mittels call_user_func() ohne eine vorherige is_callable() Prüfung, oder oder.

OK, nächstes Beispiel:

<?php
$fileContent = file_get_contents('/data/source.txt');
$fileContent = str_replace('Michael', 'Marcel', $fileContent);
file_put_contents('/data/target.txt', $fileContent);

Wir legen dieses kleine Script auf einen Server und es läuft wunderbar. Doch plötzlich ist die source.txt nicht da. Was passiert? Es gibt zwar eine Warning, aber das Script bricht nicht ab, $fileContent wird mit einem Leerstring gefüllt, und die Datei target.txt wird geschrieben, sie ist nun defekt (leer), und ein anderes Programm wird mit dieser defekten Datei weiterarbeiten.

Wir hätte man dieses Problem verhindern können? Die naheliegende Lösung wäre natürlich ein file_exists() gewesen, aber genau das haben wir ja vergessen. Wir hätten die Warnung von PHP ernst nehmen können und beim Warning des file_get_contents() Befehls abbrechen können, wir könnten generell bei jeder Notice oder Warning, die irgendwo passiert im Code, den Fehler loggen und das Script abbrechen. Ein Abbruch ist besser als ein Script, das in einem undefinierten Zustand weiterläuft.

Fügen wir ein paar Zeilen hinzu:

<?php
// error handler function
function myErrorHandler($errno, $errstr, $errfile, $errline) {
    if (!(error_reporting() & $errno)) {
        // This error code is not included in error_reporting
        return false;
    }

    $errorMsg = 'In file '.$errfile.' on line '.$errline.': '.$errno.' - '.$errstr;

    mail('admin@domain.de', 'Error occured', $errorMsg);
    exit;
}
set_error_handler("myErrorHandler");

$fileContent = file_get_contents('/data/source.txt');
$fileContent = str_replace('michael', 'marcel', $fileContent);
file_put_contents('/data/target.txt', $fileContent);

Wir definieren also einen eigenen Error-Handler, der bei allen Arten von Errors (Notices, Warnings etc) aufgerufen wird. Solange der ErrorLevel von error_reporting abgedeckt ist, schicken wir uns den Fehler per E-Mail und beenden das Script sofort. Eine nicht geschriebene target.txt ist in diesem Fall besser als eine defekte.

Eventuell auch interessant ist die Umwandlung von Notices, Warnings etc. in Exceptions, man nimmt einfach folgenden Error-Handler:

function myErrorHandler($errno, $errstr, $errfile, $errline ) {
    throw new Exception($errstr, 0, $errno, $errfile, $errline);
}

Dann kann man Notices/Warnings etc. catchen 😉

Written by Michael Kliewe

Juli 12th, 2011 at 9:29 am

14 Responses to 'Auf Warnings, Notices und sogar Fatal Errors reagieren'

Subscribe to comments with RSS or TrackBack to 'Auf Warnings, Notices und sogar Fatal Errors reagieren'.

  1. Warnt dich dein Skript denn auch, dass dein str_replace ins Leere läuft? 😉

    Marcel

    12 Jul 11 at 11:08

  2. Erwischt, berichtigt 😉 Habe da nicht so genau hingeschaut beim Testcode schreiben, habe nur darauf geschaut ob eine Datei geschrieben wird oder nicht 😉

    Michael Kliewe

    12 Jul 11 at 11:25

  3. Wie definiert sich eigentlich ein *Fatal* Error genau? Gleichsetzen mit dem E_ERROR aus dem error_reporting kann man ihn glaube ich auch nicht?

    Leider kann man ja mit dieser Methode auch trotzdem nicht alle Fehler fangen. Ist z.B. in einer inkludierten Datei ein Parsing-Fehler bricht das Script ab ohne den Error-Handler aufzurufen.

    Florian Heinze

    12 Jul 11 at 11:36

  4. Gerade wenn ich Fremdkomponenten verwende, habe ich doch NULL Kontrolle über „geworfene“ Notices. Daher sehe ich die Idee als vollkommen kontraproduktiv an.

    nk

    12 Jul 11 at 16:35

  5. Ein guter ErrorHandler ist imo essentiell wichtig für die Qualität eines Software-Produkt. Man kann im lokalen Testing einfach nicht alle Fehler finden und wenn diese online passieren, möchte man wenigstens informiert werden damit man angemessen & schnell darauf reagieren kann.

    Die register_shutdown_function halte ich im übrigen für kaum nutzbar. PHP befindet sich dort in irgend einen undefinierten Zustand, so dass gar kein Autoloader etc. mehr funktionieren. Das hat zur Folge das man die schönen Abstraktionen von Mails & Logging (für unterschiedliche Environments) leider nicht nativ nutzen kann. Irgendwie gab es auch mal viele Memory-Leaks bei Verwendung in einem Zend-Projekt. Ein guter Error-Handler + PHP-Log-File tuen es imo auch, nach einer gewisser Zeit sollte das PHP-Log-File (fast) leer bleiben, da alle FATAL ERROR aus der Applikation entfernt sind.

    Ulf Kirsten

    13 Jul 11 at 07:29

  6. Einer der größten Nachteile von PHP ist das man nicht gescheit auf Fehler reagieren kann. Mal wird ein Fehler(E_*), mal eine Exception geworfen. Es muss doch möglich sein die Anwendung bei einem Fehler in einen konsistenten Zustand zu bringen um den Benutzer eine Information anzuzeigen zu können, oder den Entwickler zu informieren das ein Fehler, welcher Art auch immer, aufgetreten ist. Klar ist das mehr oder minder durch das benutzen eines Error- oder Exception-Handlers möglich. Schön wäre es jedoch wenn PHP von Haus aus eine einheitliche Lösung mitbringen würde.

    Einige Vorschläge gibt es ja dazu.
    https://wiki.php.net/rfc/enhanced_error_handling
    https://wiki.php.net/rfc/errors_as_exceptions

    Auf github gibt es auch eine Error-Handler Klasse die auch mit den Fehlern (E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR) umgehen kann.

    https://github.com/Tyrael/php-error-handler

    Christian Kaps

    13 Jul 11 at 09:43

  7. function errorToException($code, $message, $file = null, $line = 0) {
    if (error_reporting() == 0) {
    return true;
    }

    throw new \ErrorException($message, $code, $file, $line);
    }

    cryptocompress

    13 Jul 11 at 11:35

  8. @cryptocompress

    Die Funktion fängt aber nur E_USER_* Errors ab.

    Christian Kaps

    13 Jul 11 at 13:17

  9. @Christian Kaps: ja!

    1. Errors:
    set_error_handler(‚errorToException‘);

    function errorToException($code, $message, $file = null, $line = 0) {
    if (error_reporting() == 0) {
    return true;
    }
    throw new \ErrorException($message, $code, $file, $line);
    }

    2. Exceptions:
    set_exception_handler(array($this, ‚exception‘));

    public function exception(\Exception $e) {
    $this->logger->log($e);
    }

    3. Fatal Errors:
    register_shutdown_function(array($this, ’shutdown‘));

    public function shutdown() {
    $error = error_get_last();
    if (isset($error)) {
    $this->exception(new \FatalException($error[‚message‘], $error[‚type‘], $error[‚file‘], $error[‚line‘]));
    }
    }

    cryptocompress

    14 Jul 11 at 13:33

  10. Was man bei shutdown-Funktionen jedoch beachten muss ist, dass wenn man darin einen Fehler verursacht (schreiben in eine Datei die es nicht gibt zum Beispiel), dann erzeugt PHP einen Fehler ohne Stacktrace!

    Sven Weingartner

    16 Jul 11 at 19:33

  11. […] Auf Warnings, Notices und sogar Fatal Errors reagieren. […]

  12. […] aufgerufen und ich bekomm eine E-Mail. Eigentlich recht einfach. Bei PHP Gangsta gibt es dazu noch weiterführende Informationen. Mein obiges Beispiel ist ja eigentlich auch von dort übernommen Sag es […]

  13. http://www.leftjoin.net/2011/08/throwing-exception-when-type-hinting-failed/

    Da ist auch ein netter Einsatz eines Error-Handlers präsentiert wenn ein Type-Hint fehlschlägt.

    Michael Kliewe

    11 Aug 11 at 11:28

  14. […] noch kurz zum Abschluss der Woche ein sehr interessanter Beitrag vom PHP Gangsta über das Reagieren auf PHP Fehlermeldungen. Das ganze gibt es mit ein paar Beispielscripts zum Abfangen von Fehlern und einer interessant […]

Leave a Reply

You can add images to your comment by clicking here.