web-dev-qa-db-fra.com

Comment attraper une PHP Fatal Error

Je peux utiliser set_error_handler() pour intercepter la plupart des erreurs PHP, mais cela ne fonctionne pas pour les erreurs fatales (E_ERROR), telles que l'appel d'une fonction inexistante. Y a-t-il un autre moyen d'attraper ces erreurs?

J'essaie d'appeler mail() pour toutes les erreurs et je lance PHP 5.2.3.

533
too much php

Consignez les erreurs fatales avec register_shutdown_function, qui nécessite PHP 5.2+:

register_shutdown_function( "fatal_handler" );

function fatal_handler() {
    $errfile = "unknown file";
    $errstr  = "shutdown";
    $errno   = E_CORE_ERROR;
    $errline = 0;

    $error = error_get_last();

    if( $error !== NULL) {
        $errno   = $error["type"];
        $errfile = $error["file"];
        $errline = $error["line"];
        $errstr  = $error["message"];

        error_mail(format_error( $errno, $errstr, $errfile, $errline));
    }
}

Vous devrez définir les fonctions error_mail et format_error. Par exemple:

function format_error( $errno, $errstr, $errfile, $errline ) {
    $trace = print_r( debug_backtrace( false ), true );

    $content = "
    <table>
        <thead><th>Item</th><th>Description</th></thead>
        <tbody>
            <tr>
                <th>Error</th>
                <td><pre>$errstr</pre></td>
            </tr>
            <tr>
                <th>Errno</th>
                <td><pre>$errno</pre></td>
            </tr>
            <tr>
                <th>File</th>
                <td>$errfile</td>
            </tr>
            <tr>
                <th>Line</th>
                <td>$errline</td>
            </tr>
            <tr>
                <th>Trace</th>
                <td><pre>$trace</pre></td>
            </tr>
        </tbody>
    </table>";
    return $content;
}

Utilisez Swift Mailer pour écrire la fonction error_mail.

Voir également:

617
user259973

Je viens de trouver cette solution (PHP 5.2.0+):

function shutDownFunction() { 
    $error = error_get_last();
    // fatal error, E_ERROR === 1
    if ($error['type'] === E_ERROR) { 
        //do your stuff     
    } 
}
register_shutdown_function('shutDownFunction');

Différents types d'erreur définis à l'adresse http://www.php.net/manual/en/errorfunc.constants.php

140
periklis

PHP ne fournit pas les moyens conventionnels pour attraper et récupérer des erreurs fatales. En effet, le traitement ne doit généralement pas être récupéré après une erreur fatale. La correspondance de chaîne avec un tampon de sortie (comme suggéré par la publication originale de la technique décrite sur PHP.net) est définitivement déconseillée. C'est tout simplement peu fiable.

L'appel de la fonction mail () depuis une méthode de gestionnaire d'erreur s'avère également problématique. Si vous aviez beaucoup d'erreurs, votre serveur de messagerie serait chargé de travail et vous pourriez vous retrouver avec une boîte de réception fastidieuse. Pour éviter cela, vous pouvez envisager d'exécuter un cron pour analyser périodiquement les journaux d'erreurs et envoyer des notifications en conséquence. Vous voudrez peut-être aussi vous pencher sur un logiciel de surveillance du système, tel que Nagios .


Pour en savoir plus sur l’enregistrement d’une fonction d’arrêt:

Il est vrai que vous pouvez enregistrer une fonction d'arrêt, et c'est une bonne réponse.

Le point ici est que nous ne devrions généralement pas essayer de récupérer des erreurs fatales, surtout en utilisant une expression régulière dans votre tampon de sortie. Je répondais au réponse acceptée , qui était lié à une suggestion sur php.net qui a été modifiée ou supprimée depuis.

Cette suggestion consistait à utiliser une expression rationnelle contre le tampon de sortie lors de la gestion des exceptions et, dans le cas d'une erreur fatale (détectée par la correspondance avec le texte d'erreur configuré que vous pourriez attendre), essayez de procéder à une sorte de récupération ou de traitement continu. Ce ne serait pas une pratique recommandée (je crois que c’est la raison pour laquelle je ne trouve pas la suggestion originale également. Je l’ignore ou la communauté php l’a abattue).

Il convient de noter que les versions les plus récentes de PHP (autour de la version 5.1) semblent appeler la fonction d'arrêt plus tôt, avant que le rappel de mise en mémoire tampon de sortie ne soit envoyé. Dans les versions 5 et antérieures, cet ordre était inversé (le rappel de mise en mémoire tampon de sortie était suivi de la fonction d'arrêt). De plus, depuis la version 5.0.5 environ (ce qui est beaucoup plus ancien que la version 5.2.3 du questionneur), les objets sont déchargés bien avant l'appel d'une fonction d'arrêt enregistrée. Vous ne pourrez donc plus vous fier à vos objets en mémoire. beaucoup de tout.

Il est donc correct d’enregistrer une fonction d’arrêt, mais le type de tâches qui devraient être exécutées par une fonction d’arrêt se limite probablement à une poignée de procédures d’arrêt en douceur.

La clé à retenir ici est juste quelques mots de sagesse pour quiconque trébuche sur cette question et voit le conseil dans la réponse initialement acceptée. Ne pas regex votre tampon de sortie.

114
keparo

Eh bien, il semble possible d’attraper les Erreurs Fatales d’une autre manière :)

ob_start('fatal_error_handler');

function fatal_error_handler($buffer){
    $error=error_get_last();
    if($error['type'] == 1){
        // type, message, file, line
        $newBuffer='<html><header><title>Fatal Error </title></header>
                    <style>                 
                    .error_content{                     
                        background: ghostwhite;
                        vertical-align: middle;
                        margin:0 auto;
                        padding:10px;
                        width:50%;                              
                     } 
                     .error_content label{color: red;font-family: Georgia;font-size: 16pt;font-style: italic;}
                     .error_content ul li{ background: none repeat scroll 0 0 FloralWhite;                   
                                border: 1px solid AliceBlue;
                                display: block;
                                font-family: monospace;
                                padding: 2%;
                                text-align: left;
                      }
                    </style>
                    <body style="text-align: center;">  
                      <div class="error_content">
                          <label >Fatal Error </label>
                          <ul>
                            <li><b>Line</b> '.$error['line'].'</li>
                            <li><b>Message</b> '.$error['message'].'</li>
                            <li><b>File</b> '.$error['file'].'</li>                             
                          </ul>

                          <a href="javascript:history.back()"> Back </a>                          
                      </div>
                    </body></html>';

        return $newBuffer;

    }

    return $buffer;

}
38
sakhunzai

J'ai développé un moyen d'attraper tous les types d'erreur dans PHP (presque tous)! Je n'ai pas de certitude sur E_CORE_ERROR (je pense que cela ne fonctionnera pas uniquement pour cette erreur)! Mais, pour les autres erreurs fatales (E_ERROR, E_PARSE, E_COMPILE ...), cela fonctionne bien si vous utilisez une seule fonction de gestion des erreurs! Voilà ma solution:

Mettez ce code suivant sur votre fichier principal (index.php):

<?php

define('E_FATAL',  E_ERROR | E_USER_ERROR | E_PARSE | E_CORE_ERROR | 
        E_COMPILE_ERROR | E_RECOVERABLE_ERROR);

define('ENV', 'dev');

//Custom error handling vars
define('DISPLAY_ERRORS', TRUE);
define('ERROR_REPORTING', E_ALL | E_STRICT);
define('LOG_ERRORS', TRUE);

register_shutdown_function('shut');

set_error_handler('handler');

//Function to catch no user error handler function errors...
function shut(){

    $error = error_get_last();

    if($error && ($error['type'] & E_FATAL)){
        handler($error['type'], $error['message'], $error['file'], $error['line']);
    }

}

function handler( $errno, $errstr, $errfile, $errline ) {

    switch ($errno){

        case E_ERROR: // 1 //
            $typestr = 'E_ERROR'; break;
        case E_WARNING: // 2 //
            $typestr = 'E_WARNING'; break;
        case E_PARSE: // 4 //
            $typestr = 'E_PARSE'; break;
        case E_NOTICE: // 8 //
            $typestr = 'E_NOTICE'; break;
        case E_CORE_ERROR: // 16 //
            $typestr = 'E_CORE_ERROR'; break;
        case E_CORE_WARNING: // 32 //
            $typestr = 'E_CORE_WARNING'; break;
        case E_COMPILE_ERROR: // 64 //
            $typestr = 'E_COMPILE_ERROR'; break;
        case E_CORE_WARNING: // 128 //
            $typestr = 'E_COMPILE_WARNING'; break;
        case E_USER_ERROR: // 256 //
            $typestr = 'E_USER_ERROR'; break;
        case E_USER_WARNING: // 512 //
            $typestr = 'E_USER_WARNING'; break;
        case E_USER_NOTICE: // 1024 //
            $typestr = 'E_USER_NOTICE'; break;
        case E_STRICT: // 2048 //
            $typestr = 'E_STRICT'; break;
        case E_RECOVERABLE_ERROR: // 4096 //
            $typestr = 'E_RECOVERABLE_ERROR'; break;
        case E_DEPRECATED: // 8192 //
            $typestr = 'E_DEPRECATED'; break;
        case E_USER_DEPRECATED: // 16384 //
            $typestr = 'E_USER_DEPRECATED'; break;

    }

    $message = '<b>'.$typestr.': </b>'.$errstr.' in <b>'.$errfile.'</b> on line <b>'.$errline.'</b><br/>';

    if(($errno & E_FATAL) && ENV === 'production'){

        header('Location: 500.html');
        header('Status: 500 Internal Server Error');

    }

    if(!($errno & ERROR_REPORTING))
        return;

    if(DISPLAY_ERRORS)
        printf('%s', $message);

    //Logging error on php file error log...
    if(LOG_ERRORS)
        error_log(strip_tags($message), 0);

}

ob_start();

@include 'content.php';

ob_end_flush();

?>

J'espère que cela aide beaucoup de gens! Je cherchais cette solution depuis trop longtemps et je ne l'ai pas trouvée! Puis j'en ai développé un!

22
Lucas Batistussi

Les erreurs fatales ou les erreurs fatales récupérables jettent maintenant des instances de Error dans PHP 7 ou versions supérieures . Comme toutes les autres exceptions, les objets Error peuvent être interceptés à l'aide d'un bloc try/catch.

Exemple:

<?php
$variable = 'not an object';

try {
    $variable->method(); // Throws an Error object in PHP 7 or higger.
} catch (Error $e) {
    // Handle error
    echo $e->getMessage(); // Call to a member function method() on string
}

https://3v4l.org/67vbk

Ou vous pouvez utiliser l'interface Throwable pour intercepter toutes les exceptions.

Exemple:

<?php
    try {
        undefinedFunctionCall();
    } catch (Throwable $e) {
        // Handle error
        echo $e->getMessage(); // Call to undefined function undefinedFunctionCall()
    }

https://3v4l.org/Br0MG

Pour plus d'informations: http://php.net/manual/en/language.errors.php7.php

21
LugiHaue

Vous ne pouvez pas attraper/gérer les erreurs fatales, mais vous pouvez les enregistrer/les signaler. Pour un débogage rapide, j'ai modifié une réponse à ce code simple

function __fatalHandler()
{
    $error = error_get_last();
//check if it's a core/fatal error, otherwise it's a normal shutdown
    if ($error !== NULL && in_array($error['type'], array(E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING,E_RECOVERABLE_ERROR))) {
        echo "<pre>fatal error:\n";
        print_r($error);
        echo "</pre>";
        die;
    }
}

register_shutdown_function('__fatalHandler');
18
zainengineer

Vous ne pouvez pas lancer d'exception à l'intérieur d'une fonction d'arrêt enregistrée comme ça:

<?php
function shutdown() {
    if (($error = error_get_last())) {
       ob_clean();
       throw new Exception("fatal error");
    }
}

try {
    $x = null;
    $x->method()
} catch(Exception $e) {
    # this won't work
}
?>

Mais vous pouvez capturer et rediriger la demande vers une autre page.

<?php
function shutdown() {
    if (($error = error_get_last())) {
       ob_clean();
       # raport the event, send email etc.
       header("Location: http://localhost/error-capture");
       # from /error-capture, you can use another redirect, to e.g. home page
    }
}
register_shutdown_function('shutdown');

$x = null;
$x->method()
?>
17
hipertracker

Si vous utilisez php> = 5.1.0 Faites juste quelque chose comme ceci avec la classe ErrorException:

<?php
//define an error handler
function exception_error_handler($errno, $errstr, $errfile, $errline ) {
    throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
}
//set ur error handle
set_error_handler("exception_error_handler");

/* Trigger exception */
try
{
  //try to do something like finding the end of the internet
}
catch(ErrorException $e)
{
  //anything you want to do with $e
}

?>
11
Cyril Tata

Je dois gérer les erreurs fatales pour une production afin d'afficher à la place une sortie HTML non disponible de style statique 503. C’est sûrement une approche raisonnable pour "attraper les erreurs fatales". Voici ce que j'ai fait:

J'ai une fonction personnalisée de gestion des erreurs "error_handler" qui affiche ma page HTML "Service indisponible 503" sur n'importe quel serveur E_ERROR, E_USER_ERROR, etc. Il sera désormais appelé par la fonction d'arrêt, interceptant mon erreur fatale.

function fatal_error_handler() {

    if (@is_array($e = @error_get_last())) {
        $code = isset($e['type']) ? $e['type'] : 0;
        $msg = isset($e['message']) ? $e['message'] : '';
        $file = isset($e['file']) ? $e['file'] : '';
        $line = isset($e['line']) ? $e['line'] : '';
        if ($code>0) error_handler($code,$msg,$file,$line);
    }
}
set_error_handler("error_handler");
register_shutdown_function('fatal_error_handler');

dans ma fonction personnalisée error_handler, si l'erreur est E_ERROR ou E_USER_ERROR, etc., j'appelle également @ob_end_clean (); vider le tampon, supprimant ainsi le message "erreur fatale" de PHP.

Prenez note des fonctions de vérification strict et @ silencing de isset (), car nous ne voulons pas que nos scripts error_handler génèrent des erreurs.

Toujours d'accord avec keparo, la détection d'erreurs fatales va à l'encontre du but de "l'erreur FATAL" et ne vous permet donc pas de poursuivre le traitement. N'exécutez aucune fonction mail () dans ce processus d'arrêt, car vous sauvegarderez certainement le serveur de messagerie ou votre boîte de réception. Enregistrez plutôt ces occurrences dans un fichier et planifiez un cron pour rechercher ces fichiers error.log et envoyez-les aux administrateurs.

8
Prof83

PHP a des erreurs fatales capturables. Ils sont définis comme E_RECOVERABLE_ERROR. Le manuel PHP décrit un E_RECOVERABLE_ERROR comme:

Erreur fatale capturable. Cela indique qu'une erreur probablement dangereuse s'est produite, mais ne laisse pas le moteur dans un état instable. Si l'erreur n'est pas interceptée par un descripteur défini par l'utilisateur (voir aussi set_error_handler () ), l'application est abandonnée car il s'agissait d'un E_ERROR.

Vous pouvez "attraper" ces erreurs "fatales" en utilisant set_error_handler () et en vérifiant E_RECOVERABLE_ERROR. Je trouve utile de lancer une exception lorsque cette erreur est interceptée. Vous pouvez alors utiliser try/catch.

Cette question et cette réponse fournissent un exemple utile: Comment attraper une "erreur fatale capturable" sur PHP indication de type?

Les erreurs E_ERROR, cependant, peuvent être traitées, mais ne peuvent pas être récupérées car le moteur est dans un état instable.

7
J.Money

Juste une astuce pour obtenir la méthode error_handler actuelle =)

<?php
register_shutdown_function('__fatalHandler');
function __fatalHandler()
{
    $error      = error_get_last();

    //check if it's a core/fatal error, otherwise it's a normal shutdown
    if($error !== NULL && $error['type'] === E_ERROR) {
        //Bit hackish, but the set_exception_handler will return the old handler
        function fakeHandler() { }
        $handler = set_exception_handler('fakeHandler');
        restore_exception_handler();
        if($handler !== null) { 
            call_user_func($handler, new ErrorException($error['message'], $error['type'], 0, $error['file'], $error['line']));
        }
        exit;
    }
}
?>

Aussi je ne veux pas noter que si vous appelez

<?php
ini_set('display_errors', false);
?>

Php arrête d'afficher l'erreur, sinon le texte de l'erreur sera envoyé au client avant votre gestionnaire d'erreur

5
Sander Visser

Comme la plupart des réponses ici ne sont pas prolixes, voici ma version non laide de la réponse la plus votée:

function errorHandler($errno, $errstr, $errfile = '', $errline = 0, $errcontext = array()) {
    //Do stuff: mail, log, etc
}

function fatalHandler() {
    $error = error_get_last();
    if($error) errorHandler($error["type"], $error["message"], $error["file"], $error["line"]);
}

set_error_handler("errorHandler")
register_shutdown_function("fatalHandler");
5
Mahn

Pas vraiment. Les erreurs fatales sont appelées ainsi, car elles sont fatales. Vous ne pouvez pas récupérer d'eux.

4
troelskn

Il existe certaines circonstances dans lesquelles des erreurs même fatales doivent être détectées (vous devrez peut-être nettoyer avant de quitter gracieusement et ne pas simplement mourir ..). J'ai mis en place un point d'ancrage pre_system sur mes applications codeigniter afin que je puisse obtenir mes erreurs fatales par courrier électronique, ce qui m'a aidé à détecter les bogues non signalés (ou signalés après qu'ils aient été corrigés, car je les connaissais déjà :)). Sendemail vérifie si l’erreur a déjà été signalée afin de ne pas vous spammer avec des erreurs connues plusieurs fois.

class PHPFatalError {

    public function setHandler() {
        register_shutdown_function('handleShutdown');
    }

}

function handleShutdown() {
    if (($error = error_get_last())) {
        ob_start();
        echo "<pre>";
        var_dump($error);
        echo "</pre>";
        $message = ob_get_clean();
        sendEmail($message);
        ob_start();
        echo '{"status":"error","message":"Internal application error!"}';
        ob_flush();
        exit();
    }
}
3
tix3

J'ai développé cette fonction pour rendre possible le "sandbox" du code pouvant causer une erreur fatale. Comme les exceptions levées depuis la fermeture register_shutdown_function ne sont pas émises par la pile d’appels d’erreur pré-fatale, je suis obligé de quitter après cette fonction pour fournir un moyen uniforme de l’utiliser.

function superTryCatchFinallyAndExit( Closure $try, Closure $catch = NULL, Closure $finally )
{
    $finished = FALSE;
    register_shutdown_function( function() use ( &$finished, $catch, $finally ) {
        if( ! $finished ) {
            $finished = TRUE;
            print "EXPLODE!".PHP_EOL;
            if( $catch ) {
                superTryCatchFinallyAndExit( function() use ( $catch ) {
                    $catch( new Exception( "Fatal Error!!!" ) );
                }, NULL, $finally );                
            } else {
                $finally();                
            }
        }
    } );
    try {
        $try();
    } catch( Exception $e ) {
        if( $catch ) {
            try {
                $catch( $e );
            } catch( Exception $e ) {}
        }
    }
    $finished = TRUE;
    $finally();
    exit();
}
2
Kendall Hopkins

J'ai rédigé un Q & A de style wiki avec une solution complète pour détecter toutes les erreurs en PHP; qui peut être vu/glané/volé/critiqué ici .

La solution comprend 5 méthodes qui englobent toutes les erreurs que PHP peut générer, qui seront éventuellement transmises à un objet typé "ErrorHandler".

J'espère que certaines personnes en tireront parti. Même si vous ne le volez pas directement, je suis sûr que la solution est au moins un bon exemple de la façon de gérer les erreurs dans PHP - à tous égards.

@Lucas Batistussi a obtenu des points pour sa créativité - je pense pouvoir aussi partager ma solution et viser des points similaires ...

1
DigitalJedi805