PHP: eccezioni vs errori?


116

Forse mi manca da qualche parte nel manuale PHP, ma qual è esattamente la differenza tra un errore e un'eccezione? L'unica differenza che posso vedere è che gli errori e le eccezioni vengono gestiti in modo diverso. Ma cosa causa un'eccezione e cosa causa un errore?

Risposte:


87

Vengono lanciate eccezioni : sono destinate a essere catturate. Gli errori sono generalmente irrecuperabili. Diciamo per esempio: hai un blocco di codice che inserirà una riga in un database. È possibile che questa chiamata fallisca (ID duplicato) - vorrai avere un "Errore" che in questo caso è una "Eccezione". Quando inserisci queste righe, puoi fare qualcosa di simile

try {
  $row->insert();
  $inserted = true;
} catch (Exception $e) {
  echo "There was an error inserting the row - ".$e->getMessage();
  $inserted = false;
}

echo "Some more stuff";

L'esecuzione del programma continuerà, perché hai "catturato" l'eccezione. Un'eccezione verrà considerata un errore a meno che non venga rilevata. Ti consentirà di continuare l'esecuzione del programma anche dopo che è fallito.


29
Errors are generally unrecoverable<- in realtà, questo non è proprio vero. E_ERRORe E_PARSEsono i due errori irreversibili più comuni (ce ne sono un paio di altri) ma la stragrande maggioranza degli errori che vedrai in dev sono recuperabili ( E_NOTICE, E_WARNINGet al). Sfortunatamente la gestione degli errori di PHP è un disastro completo: tutti i tipi di cose innescano errori inutilmente (la stragrande maggioranza delle funzioni del file system, per esempio). In generale le eccezioni sono "il modo OOP", ma sfortunatamente alcune delle API OOP native di PHP utilizzano errori invece di eccezioni :-(
DaveRandom

1
@DaveRandom E_NOTICE, E_WARNING non sono "errori" per definizione, vero? Li ho sempre considerati come "messaggi" visualizzati da PHP per avvisare il programmatore che qualcosa potrebbe essere sbagliato nel codice che ha scritto.
slhsen

2
@slhsen il problema è davvero una terminologia scadente, tutte le forme di questi messaggi passano attraverso il "sistema di gestione degli errori" in PHP, semanticamente tutti questi eventi sono "errori", anche se semanticamente avviso / avvertimento non è sicuramente la stessa cosa di un " errore "in quel contesto. Per fortuna il prossimo PHP7 ha almeno spianato la strada per risolvere questo pasticcio trasformando la maggior parte di queste cose in eccezioni catturabili (tramite una nuova Throwableinterfaccia), dando un modo molto più espressivo e assoluto per distinguere e correttamente a mano entrambi i reali problemi e messaggi di avviso
DaveRandom

"L'esecuzione del programma continuerà" è cambiato suppongo? Poiché PHP dice "Quando viene generata un'eccezione, il codice che segue l'istruzione non verrà eseguito" ( php.net/manual/en/language.exceptions.php )
Robert Sinclair

1
Penso che il significato dell'OP fosse più sulla differenza tra i discendenti di ErrorVS i discendenti di Exception.
XedinUnknown

55

Di solito mi rivolgo set_error_handlera una funzione che accetta l'errore e genera un'eccezione in modo che qualunque cosa accada avrò solo eccezioni da affrontare. Non più @file_get_contentssolo una bella e ordinata prova / cattura.

Nelle situazioni di debug ho anche un gestore di eccezioni che genera una pagina simile a asp.net. Lo sto postando per strada ma, se richiesto, posterò la fonte di esempio più tardi.

modificare:

Inoltre, come promesso, ho tagliato e incollato parte del mio codice per creare un campione. Ho salvato quanto segue in un file sulla mia workstation, NON puoi PIÙ vedere i risultati qui (perché il collegamento è interrotto).

<?php

define( 'DEBUG', true );

class ErrorOrWarningException extends Exception
{
    protected $_Context = null;
    public function getContext()
    {
        return $this->_Context;
    }
    public function setContext( $value )
    {
        $this->_Context = $value;
    }

    public function __construct( $code, $message, $file, $line, $context )
    {
        parent::__construct( $message, $code );

        $this->file = $file;
        $this->line = $line;
        $this->setContext( $context );
    }
}

/**
 * Inspire to write perfect code. everything is an exception, even minor warnings.
 **/
function error_to_exception( $code, $message, $file, $line, $context )
{
    throw new ErrorOrWarningException( $code, $message, $file, $line, $context );
}
set_error_handler( 'error_to_exception' );

function global_exception_handler( $ex )
{
    ob_start();
    dump_exception( $ex );
    $dump = ob_get_clean();
    // send email of dump to administrator?...

    // if we are in debug mode we are allowed to dump exceptions to the browser.
    if ( defined( 'DEBUG' ) && DEBUG == true )
    {
        echo $dump;
    }
    else // if we are in production we give our visitor a nice message without all the details.
    {
        echo file_get_contents( 'static/errors/fatalexception.html' );
    }
    exit;
}

function dump_exception( Exception $ex )
{
    $file = $ex->getFile();
    $line = $ex->getLine();

    if ( file_exists( $file ) )
    {
        $lines = file( $file );
    }

?><html>
    <head>
        <title><?= $ex->getMessage(); ?></title>
        <style type="text/css">
            body {
                width : 800px;
                margin : auto;
            }

            ul.code {
                border : inset 1px;
            }
            ul.code li {
                white-space: pre ;
                list-style-type : none;
                font-family : monospace;
            }
            ul.code li.line {
                color : red;
            }

            table.trace {
                width : 100%;
                border-collapse : collapse;
                border : solid 1px black;
            }
            table.thead tr {
                background : rgb(240,240,240);
            }
            table.trace tr.odd {
                background : white;
            }
            table.trace tr.even {
                background : rgb(250,250,250);
            }
            table.trace td {
                padding : 2px 4px 2px 4px;
            }
        </style>
    </head>
    <body>
        <h1>Uncaught <?= get_class( $ex ); ?></h1>
        <h2><?= $ex->getMessage(); ?></h2>
        <p>
            An uncaught <?= get_class( $ex ); ?> was thrown on line <?= $line; ?> of file <?= basename( $file ); ?> that prevented further execution of this request.
        </p>
        <h2>Where it happened:</h2>
        <? if ( isset($lines) ) : ?>
        <code><?= $file; ?></code>
        <ul class="code">
            <? for( $i = $line - 3; $i < $line + 3; $i ++ ) : ?>
                <? if ( $i > 0 && $i < count( $lines ) ) : ?>
                    <? if ( $i == $line-1 ) : ?>
                        <li class="line"><?= str_replace( "\n", "", $lines[$i] ); ?></li>
                    <? else : ?>
                        <li><?= str_replace( "\n", "", $lines[$i] ); ?></li>
                    <? endif; ?>
                <? endif; ?>
            <? endfor; ?>
        </ul>
        <? endif; ?>

        <? if ( is_array( $ex->getTrace() ) ) : ?>
        <h2>Stack trace:</h2>
            <table class="trace">
                <thead>
                    <tr>
                        <td>File</td>
                        <td>Line</td>
                        <td>Class</td>
                        <td>Function</td>
                        <td>Arguments</td>
                    </tr>
                </thead>
                <tbody>
                <? foreach ( $ex->getTrace() as $i => $trace ) : ?>
                    <tr class="<?= $i % 2 == 0 ? 'even' : 'odd'; ?>">
                        <td><?= isset($trace[ 'file' ]) ? basename($trace[ 'file' ]) : ''; ?></td>
                        <td><?= isset($trace[ 'line' ]) ? $trace[ 'line' ] : ''; ?></td>
                        <td><?= isset($trace[ 'class' ]) ? $trace[ 'class' ] : ''; ?></td>
                        <td><?= isset($trace[ 'function' ]) ? $trace[ 'function' ] : ''; ?></td>
                        <td>
                            <? if( isset($trace[ 'args' ]) ) : ?>
                                <? foreach ( $trace[ 'args' ] as $i => $arg ) : ?>
                                    <span title="<?= var_export( $arg, true ); ?>"><?= gettype( $arg ); ?></span>
                                    <?= $i < count( $trace['args'] ) -1 ? ',' : ''; ?> 
                                <? endforeach; ?>
                            <? else : ?>
                            NULL
                            <? endif; ?>
                        </td>
                    </tr>
                <? endforeach;?>
                </tbody>
            </table>
        <? else : ?>
            <pre><?= $ex->getTraceAsString(); ?></pre>
        <? endif; ?>
    </body>
</html><? // back in php
}
set_exception_handler( 'global_exception_handler' );

class X
{
    function __construct()
    {
        trigger_error( 'Whoops!', E_USER_NOTICE );      
    }
}

$x = new X();

throw new Exception( 'Execution will never get here' );

?>

Sarebbe utile. Qualunque cosa per alleggerire i tempi che ho dovuto affrontare con PHP aiuterà. :-)
Jason Baker

Bel codice, grazie. Tuttavia non capisco da dove viene la classe X, e qual è il suo scopo?
Alec

tutto sotto "set_exception_handler ('global_exception_handler');" è solo una demo, non ne avrai bisogno, è solo per mostrare cosa accadrebbe in una situazione di errore normalmente non eccezionale.
Kris

Lo standard PHP definisce ErrorException specificamente da lanciare da un gestore di errori generale. Mi permetteresti di modificare e aggiornare il tuo post?
Tiberiu-Ionuț Stan

@ Tiberiu-IonuțStan: certo, ma l'esempio funzionante non sarà sincronizzato. Inoltre, al giorno d'oggi probabilmente indicherei le persone a github.com/theredhead/red.web/blob/master/src/lib/bootstrap.php da private-void.com .
Kris,

21

La risposta merita di parlare dell'elefante nella stanza

Gli errori sono il vecchio modo di gestire una condizione di errore in fase di esecuzione. In genere il codice effettua una chiamata a qualcosa di simile set_error_handlerprima di eseguire del codice. Seguendo la tradizione delle interruzioni in linguaggio assembly. Ecco come apparirebbe un codice BASIC.

on error :divide_error

print 1/0
print "this won't print"

:divide_error

if errcode = X
   print "divide by zero error"

Era difficile assicurarsi che set_error_handlersarebbe stato chiamato con il valore giusto. E ancora peggio, potrebbe essere effettuata una chiamata a una procedura separata che cambierebbe il gestore degli errori. Inoltre molte volte le chiamate sono state intervallate da set_error_handlerchiamate e gestori. È stato facile per il codice perdere rapidamente il controllo. La gestione delle eccezioni è venuta in soccorso formalizzando la sintassi e la semantica di ciò che un buon codice stava realmente facendo.

try {
   print 1/0;
   print "this won't print";
} catch (DivideByZeroException $e) {
   print "divide by zero error";
}

Nessuna funzione separata o rischio di chiamare il gestore degli errori sbagliato. Il codice ora è garantito per essere nello stesso posto. Inoltre otteniamo messaggi di errore migliori.

PHP aveva solo la gestione degli errori, quando molti altri linguaggi si erano già evoluti al modello di gestione delle eccezioni preferibile. Alla fine i creatori di PHP hanno implementato la gestione delle eccezioni. Ma probabilmente per supportare il vecchio codice, hanno mantenuto la gestione degli errori e hanno fornito un modo per rendere la gestione degli errori simile alla gestione delle eccezioni. Tranne che, non vi è alcuna garanzia che alcuni codici potrebbero non resettare il gestore degli errori che era esattamente ciò che la gestione delle eccezioni doveva fornire.

Risposta finale

Gli errori che sono stati codificati prima dell'implementazione della gestione delle eccezioni sono probabilmente ancora errori. I nuovi errori sono probabili eccezioni. Ma non esiste un disegno o una logica a cui siano errori e quali siano eccezioni. Si basa solo su ciò che era disponibile al momento in cui è stato codificato e sulla preferenza del programmatore che lo codifica.


3
Questo è il vero motivo per cui coesistono eccezioni ed errori. Se progettato da zero, php dovrebbe includere solo l'uno o l'altro.
Tomas Zubiri

1
È la risposta migliore secondo me, poiché è la più dettagliata ed esplicativa.
Robert Kusznier

8

Una cosa da aggiungere qui riguarda la gestione delle eccezioni e degli errori. Ai fini dello sviluppatore dell'applicazione, sia gli errori che le eccezioni sono "cose ​​cattive" che si desidera registrare per conoscere i problemi che presenta la propria applicazione, in modo che i clienti abbiano una migliore esperienza a lungo termine.

Quindi ha senso scrivere un gestore degli errori che fa la stessa cosa di quello che fai per le eccezioni.


Grazie per aver fornito il collegamento!
Mike Moore

@Alex Weinstein: il collegamento si è interrotto
Marco Demaio

7

Come affermato in altre risposte, l'impostazione del gestore degli errori su lanciatore di eccezioni è il modo migliore per gestire gli errori in PHP. Uso una configurazione un po 'più semplice:

set_error_handler(function ($errno, $errstr, $errfile, $errline ) {
        if (error_reporting()) {
                throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
        }
});

Si prega di notare il error_reporting()controllo per mantenere l' @operatore in funzione. Inoltre, non è necessario definire un'eccezione personalizzata, PHP ha una bella classe per questo.

Il grande vantaggio di generare eccezioni è che l'eccezione ha una traccia dello stack associata, quindi è facile trovare dove si trova il problema.


5

Ri: "ma qual è esattamente la differenza tra un errore e un'eccezione?"

Ci sono molte buone risposte sulle differenze qui. Aggiungerò solo qualcosa di cui non si è ancora parlato: le prestazioni. In particolare, questo è per la differenza tra il lancio / la gestione delle eccezioni e la gestione di un codice di ritorno (successo o errore). Di solito, in php, questo significa restituire falseo null, ma possono essere più dettagliati come con il caricamento di file: http://php.net/manual/en/features.file-upload.errors.php Potresti anche restituire un oggetto Exception !

Ho eseguito alcune prestazioni in diversi linguaggi / sistemi. In generale, la gestione delle eccezioni è circa 10.000 volte più lenta rispetto al controllo di un codice di ritorno di errore.

Quindi, se assolutamente, positivamente deve finire di essere eseguito prima ancora di iniziare, beh, sei sfortunato perché il viaggio nel tempo non esiste. Senza viaggio nel tempo, i codici di ritorno sono l'opzione più veloce disponibile.

Modificare:

PHP è altamente ottimizzato per la gestione delle eccezioni. I test del mondo reale mostrano che lanciare un'eccezione è solo 2-10 volte più lento della restituzione di un valore.


3
Certo, ma la quantità di cicli persi per lanciare eccezioni è più che compensata dai poteri descrittivi extra che ottieni con le eccezioni. È possibile generare tipi specifici di eccezioni, persino aggiungere dati all'eccezione per contenere i codici di errore. Dubito seriamente anche della tua richiesta di 10.000 *. Anche se hai ragione sulla differenza di fuso orario, il tempo speso a fare ritorno e se rispetto a una nuova esecuzione, lancio, cattura in qualsiasi scenario del mondo reale è probabilmente così minuscolo rispetto al codice eseguito che questa è sicuramente un'ottimizzazione prematura. Lancia le eccezioni, sono più piacevoli da affrontare il 90% delle volte.
gnarf

1
1. 10.000x è accurato, con qualche variazione in base al linguaggio e alle opzioni del compilatore. 2. Non è necessario restituire null / false. Puoi restituire un numero, fino a MAX_ULONG codici di ritorno direttamente lì. In alternativa, puoi restituire una stringa di errore e controllare solo una stringa di successo o int o null. 3. Negli scenari del mondo reale ogni ciclo di clock conta. Facebook ha 552 milioni di utenti attivi ogni giorno. Supponendo che le eccezioni siano solo 2x e che il controllo di utente / passaggio richieda 0,001 s, significa risparmiare 153 ore di tempo di elaborazione ogni giorno. A 10.000 volte risparmia 175 anni. Solo per controllare i tentativi di accesso, ogni giorno.
evan

@evan: FYI, qui hanno testato codice con eccezioni e non sembra essere più lento: stackoverflow.com/a/445094/260080
Marco Demaio

@MarcoDemaio Questa domanda copre solo il blocco try / catch senza lanciare un'eccezione. Un test migliore sarebbe restituire un valore in noexcept () e lanciare un'eccezione in tranne (). Inoltre, dovrebbe emergere attraverso più funzioni. stackoverflow.com/a/104375/505172 afferma che la differenza in PHP è effettivamente 54x. Ho eseguito il mio test in tempo reale e sembra essere 2-10 volte più lento. È tutto molto meglio del previsto.
evan

@evan: non sarei preoccupato allora, uso le eccezioni solo per tenere traccia di errori imprevisti / irrecuperabili, quindi anche se fosse 100 volte più lento non mi importerebbe. Le mie preoccupazioni erano di rendere il codice più lento semplicemente aggiungendo blocchi try / catch.
Marco Demaio

4

Penso che la risposta che stai cercando sia quella;

Gli errori sono le cose standard a cui sei abituato, come l'eco di una variabile $ che non esiste.
Le eccezioni sono solo da PHP 5 in poi e vengono quando si tratta di oggetti.

Per mantenerlo semplice:

Le eccezioni sono gli errori che si verificano quando si tratta di oggetti. L'istruzione try / catch ti consente di fare qualcosa al riguardo, ed è usata in modo molto simile all'istruzione if / else. Prova a farlo, se il problema non ha importanza, fallo.

Se non "intercetti" un'eccezione, si trasforma in un errore standard.

Gli errori sono gli errori fondamentali di php che di solito interrompono lo script.

Try / catch viene spesso utilizzato per stabilire connessioni al database come PDO, il che va bene se si desidera reindirizzare lo script o fare qualcos'altro se la connessione non funziona. Ma se vuoi solo visualizzare il messaggio di errore e interrompere lo script, non ne hai bisogno, l'eccezione non rilevata si trasforma in un errore fatale. Oppure puoi utilizzare anche un'impostazione di gestione degli errori a livello di sito.

spero che aiuti


3
Le eccezioni possono essere utilizzate altrettanto bene con il codice procedurale in PHP.
Tiberiu-Ionuț Stan

2

In PHP 7.1 e versioni successive, un blocco catch può specificare più eccezioni utilizzando il carattere barra verticale (|). Ciò è utile quando vengono gestite allo stesso modo eccezioni diverse da gerarchie di classi diverse.

try {
  // do something
} catch (Error | Exception $e) {
  echo $e->getMessage();
}

1

Le eccezioni vengono lanciate intenzionalmente dal codice usando un lancio, errori ... non così tanto.

Gli errori derivano da qualcosa che non viene gestito normalmente. (Errori IO, errori TCP / IP, errori di riferimento nulli)


1
Questo non è necessariamente vero. In molti casi gli errori vengono controllati e i codici di ritorno vengono restituiti intenzionalmente come appropriato. In effetti, questo è il caso di ogni linguaggio non orientato agli oggetti. Le eccezioni sono anche queste, eccezioni alla regola. In entrambi i casi, qualcosa va storto, viene notato e deve essere gestito. Il caricamento di file PHP è un esempio di gestione intenzionale degli errori tramite codici di ritorno: php.net/manual/en/features.file-upload.errors.php
evan

1

Ho intenzione di darvi una discussione molto insolita sul controllo degli errori.

Ho costruito un ottimo gestore degli errori in una lingua anni fa e, sebbene alcuni dei nomi siano cambiati, i principi dell'elaborazione degli errori sono gli stessi oggi. Avevo un sistema operativo multi-tasking personalizzato e dovevo essere in grado di recuperare da errori di dati a tutti i livelli senza perdite di memoria, crescita dello stack o arresti anomali. Quindi quello che segue è la mia comprensione di come devono funzionare gli errori e le eccezioni e come differiscono. Dirò solo che non ho una comprensione di come funzionano gli interni di try catch, quindi immagino in una certa misura.

La prima cosa che accade sotto le coperte per l'elaborazione degli errori è passare da uno stato del programma a un altro. Come si fa? Ci arrivo io.

Storicamente, gli errori sono più vecchi e più semplici e le eccezioni sono più recenti e un po 'più complesse e capaci. Gli errori funzionano bene fino a quando non è necessario riempirli di bolle, il che equivale a passare un problema difficile al tuo supervisore.

Gli errori possono essere numeri, come numeri di errore e talvolta con una o più stringhe associate. Ad esempio, se si verifica un errore di lettura del file, potresti essere in grado di segnalare di cosa si tratta e possibilmente fallire. (Hay, è un passo avanti rispetto allo schianto come ai vecchi tempi.)

Quello che non viene detto spesso sulle eccezioni è che le eccezioni sono oggetti stratificati su uno speciale stack di eccezioni. È come uno stack di ritorno per il flusso del programma, ma mantiene uno stato di ritorno solo per i tentativi di errore e le catture. (Li chiamavo ePush ed ePop, e? Abort era un lancio condizionale che avrebbe ePop e avrebbe recuperato a quel livello, mentre Abort era un dado completo o un'uscita.)

In fondo allo stack ci sono le informazioni sul chiamante iniziale, l'oggetto che conosce lo stato in cui è stato avviato il tentativo esterno, che spesso è quando è stato avviato il programma. Inoltre, o il livello successivo della pila, con in alto i figli e in basso i genitori, è l'oggetto eccezione del successivo blocco try / catch interno.

Se metti una prova all'interno di una prova, stai impilando la prova interna sopra quella esterna. Quando si verifica un errore nel tentativo interno e il catch interno non può gestirlo o l'errore viene lanciato al tentativo esterno, il controllo viene passato al blocco catch esterno (oggetto) per vedere se è in grado di gestire l'errore, ad es. il tuo supervisore.

Quindi ciò che fa questo stack di errori è essere in grado di contrassegnare e ripristinare il flusso del programma e lo stato del sistema, in altre parole, consente a un programma di non bloccare lo stack di ritorno e rovinare le cose per gli altri (dati) quando le cose vanno male. Quindi salva anche lo stato di qualsiasi altra risorsa come i pool di allocazione della memoria e quindi può ripulirli quando il rilevamento è terminato. In generale questa può essere una cosa molto complicata ed è per questo che la gestione delle eccezioni è spesso lenta. In generale un bel po 'di stato deve entrare in questi blocchi di eccezioni.

Quindi una sorta di blocco try / catch imposta uno stato a cui poter tornare se tutto il resto viene incasinato. È come un genitore. Quando le nostre vite si incasinano, possiamo ricadere in grembo ai nostri genitori e loro rimetteranno tutto a posto.

Spero di non averti deluso.


1

Puoi aggiungere questo commento

function doSomething()
{
   /** @noinspection PhpUnhandledExceptionInspection */
   throw new Exception();
}

0

Una volta definito set_error_handler (), il gestore degli errori è simile a quello di Exception. Vedere il codice di seguito:

 <?php
 function handleErrors( $e_code ) {
   echo "error code: " . $e_code . "<br>";
 }

 set_error_handler( "handleErrors" ); 

 trigger_error( "trigger a fatal error", E_USER_ERROR);
 echo "after error."; //it would run if set_error_handler is defined, otherwise, it wouldn't show
?>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.