PHP: ¿excepciones vs errores?

116

Tal vez me lo pierda en algún lugar del manual de PHP, pero ¿cuál es exactamente la diferencia entre un error y una excepción? La única diferencia que puedo ver es que los errores y las excepciones se manejan de manera diferente. Pero, ¿qué causa una excepción y qué causa un error?

Jason Baker
fuente

Respuestas:

87

Se lanzan excepciones , están destinadas a ser capturadas. Los errores generalmente son irrecuperables. Digamos, por ejemplo, que tiene un bloque de código que insertará una fila en una base de datos. Es posible que esta llamada falle (ID duplicado); querrá tener un "Error" que en este caso es una "Excepción". Cuando inserta estas filas, puede hacer algo como esto

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

echo "Some more stuff";

La ejecución del programa continuará, porque "captó" la excepción. Una excepción se tratará como un error a menos que se detecte. También le permitirá continuar con la ejecución del programa después de que falle.

gnarf
fuente
29
Errors are generally unrecoverable<- en realidad, esto no es realmente cierto. E_ERRORy E_PARSEson los dos errores irrecuperables más comunes (hay un par de otros) pero la gran mayoría de los errores que verá en dev son recuperables ( E_NOTICE, E_WARNINGet al). Desafortunadamente, el manejo de errores de PHP es un completo desastre: todo tipo de cosas desencadenan errores innecesariamente (la gran mayoría de las funciones del sistema de archivos, por ejemplo). En general, las excepciones son "la forma de OOP", pero desafortunadamente algunas de las API de OOP nativas de PHP usan errores en lugar de excepciones :-(
DaveRandom
1
@DaveRandom E_NOTICE, E_WARNING no son "errores" por definición, ¿verdad? Siempre los pensé como "mensajes" que muestra PHP para notificar al programador que algo podría estar mal con el código que escribió.
slhsen
2
@slhsen, el problema es realmente una terminología basura, todas las formas de estos mensajes pasan por el "sistema de manejo de errores" en PHP, semánticamente todos estos eventos son "errores", aunque semánticamente el aviso / advertencia definitivamente no es lo mismo que un " error "en ese contexto. Afortunadamente, el próximo PHP7 al menos ha allanado el camino para solucionar este desorden al convertir la mayoría de estas cosas en excepciones capturables (por medio de una nueva Throwableinterfaz), lo que brinda una forma mucho más expresiva y absoluta de distinguir y entregar correctamente ambos elementos reales. problemas y mensajes de advertencia
DaveRandom
"La ejecución del programa continuará" ha cambiado, supongo. Dado que PHP dice "Cuando se lanza una excepción, el código que sigue a la declaración no se ejecutará" ( php.net/manual/en/language.exceptions.php )
Robert Sinclair
1
Creo que lo que significaba el OP era más sobre la diferencia entre los descendientes de ErrorVS los descendientes de Exception.
XedinUnknown
55

Por lo general, set_error_handlera una función que toma el error y lanza una excepción para que, pase lo que pase, solo tenga excepciones con las que lidiar. No más@file_get_contents intentos / capturas agradables y ordenadas.

En situaciones de depuración, también tengo un controlador de excepciones que genera una página similar a asp.net. Estoy publicando esto en la carretera, pero si me lo solicitan, publicaré la fuente de ejemplo más tarde.

editar:

Además, según lo prometido, corté y pegué parte de mi código para hacer una muestra. He guardado lo siguiente para archivarlo en mi estación de trabajo, YA NO puede ver los resultados aquí (porque el enlace está roto).

<?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' );

?>
Kris
fuente
Eso sería de gran ayuda. Cualquier cosa para aliviar las veces que estoy hecho para lidiar con PHP ayudará. :-)
Jason Baker
Buen código, gracias. Sin embargo, no entiendo de dónde viene la clase X y cuál es su propósito.
Alec
todo lo que aparece debajo de "set_exception_handler ('global_exception_handler');" es solo una demostración, no la necesitará, es solo para mostrar lo que sucedería en una situación de error que normalmente no es una excepción.
Kris
PHP estándar define ErrorException específicamente para ser lanzado desde un controlador de errores general. ¿Me permitirías editar y actualizar tu publicación?
Tiberiu-Ionuț Stan
@ Tiberiu-IonuțStan: claro, pero el ejemplo de trabajo no estará sincronizado. Además, hoy en día probablemente señalaría a la gente a github.com/theredhead/red.web/blob/master/src/lib/bootstrap.php de private-void.com .
Kris
21

La respuesta merece hablar del elefante en la habitación.

Los errores son la forma antigua de manejar una condición de error en tiempo de ejecución. Normalmente, el código haría una llamada a algo como set_error_handlerantes de ejecutar algún código. Siguiendo la tradición del lenguaje ensamblador se interrumpe. Así es como se vería un código BÁSICO.

on error :divide_error

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

:divide_error

if errcode = X
   print "divide by zero error"

Era difícil asegurarse de que set_error_handlerse llamaría con el valor correcto. Y lo que es peor, se podría realizar una llamada a un procedimiento separado que cambiaría el controlador de errores. Además, muchas veces las llamadas se intercalaron con set_error_handlerllamadas y controladores. Fue fácil que el código se saliera de control rápidamente. El manejo de excepciones vino al rescate formalizando la sintaxis y la semántica de lo que realmente estaba haciendo el buen código.

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

Sin función separada o riesgo de llamar al manejador de errores incorrecto. Ahora se garantiza que el código estará en el mismo lugar. Además, obtenemos mejores mensajes de error.

PHP solía tener solo manejo de errores, cuando muchos otros lenguajes ya habían evolucionado al modelo preferido de manejo de excepciones. Finalmente, los creadores de PHP implementaron el manejo de excepciones. Pero probablemente admitieran código antiguo, mantuvieron el manejo de errores y proporcionaron una forma de hacer que el manejo de errores pareciera un manejo de excepciones. Excepto que, no hay garantía de que algún código no restablezca el controlador de errores, que era precisamente lo que debía proporcionar el manejo de excepciones.

Respuesta final

Los errores que se codificaron antes de que se implementara el manejo de excepciones probablemente sigan siendo errores. Los nuevos errores son probables excepciones. Pero no hay diseño ni lógica para los que son errores y cuáles son excepciones. Simplemente se basa en lo que estaba disponible en el momento en que se codificó y en la preferencia del programador que lo codificó.

Arturo Hernandez
fuente
3
Ésta es la verdadera razón por la que coexisten excepciones y errores. Si está diseñado desde cero, php solo debería incluir uno u otro.
Tomas Zubiri
1
Es la mejor respuesta en mi opinión, ya que es la más detallada y explicativa.
Robert Kusznier
8

Una cosa para agregar aquí es sobre el manejo de excepciones y errores. Para el desarrollador de la aplicación, tanto los errores como las excepciones son "cosas malas" que desea registrar para conocer los problemas que tiene su aplicación, para que sus clientes tengan una mejor experiencia a largo plazo.

Por lo tanto, tiene sentido escribir un controlador de errores que haga lo mismo que usted para las excepciones.

Alex Weinstein
fuente
¡Gracias por proporcionar el enlace!
Mike Moore
@Alex Weinstein: el vínculo está roto
Marco Demaio
7

Como se indica en otras respuestas, configurar el controlador de errores en lanzador de excepciones es la mejor manera de manejar errores en PHP. Yo uso una configuración un poco más simple:

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

Tenga en cuenta la error_reporting()verificación para que el @operador siga funcionando. Además, no es necesario definir una excepción personalizada, PHP tiene una buena clase para eso.

El gran beneficio de lanzar excepciones es que la excepción tiene un seguimiento de pila asociado, por lo que es fácil encontrar dónde está el problema.

Josef Kufner
fuente
5

Re: "pero, ¿cuál es exactamente la diferencia entre un error y una excepción?"

Aquí hay muchas buenas respuestas sobre las diferencias. Solo agregaré algo de lo que aún no se ha hablado: rendimiento. Específicamente, esto es por la diferencia entre lanzar / manejar excepciones y manejar un código de retorno (ya sea exitoso o algún error). Por lo general, en php, esto significa regresar falseo null, pero pueden ser más detallados, como con la carga de archivos: http://php.net/manual/en/features.file-upload.errors.php Incluso podría devolver un objeto de excepción !

He realizado algunas ejecuciones de rendimiento en diferentes lenguajes / sistemas. En términos generales, el manejo de excepciones es aproximadamente 10,000 veces más lento que la verificación de un código de retorno de error.

Entonces, si absolutamente, positivamente, necesita terminar de ejecutarse incluso antes de comenzar, bueno, no tiene suerte porque el viaje en el tiempo no existe. Sin viajes en el tiempo, los códigos de retorno son la opción más rápida disponible.

Editar:

PHP está altamente optimizado para el manejo de excepciones. Las pruebas del mundo real muestran que lanzar una excepción es solo 2-10 veces más lento que devolver un valor.

evan
fuente
3
Claro, pero la cantidad de ciclos perdidos al lanzar Excepciones está más que compensada por los poderes descriptivos adicionales que obtienes con Excepciones. Puede lanzar tipos específicos de excepciones, incluso agregar datos a la excepción para contener los códigos de error. También dudo seriamente de su reclamo de 10,000 *. Incluso si tiene razón sobre la diferencia de tiempo, el tiempo dedicado a hacer return & if vs new Execption, throw, catch en cualquier escenario del mundo real es probablemente tan minúsculo en comparación con el código ejecutado que esta es definitivamente una optimización prematura. Lanza excepciones, son más agradables para lidiar con el 90% del tiempo.
gnarf
1
1. 10,000x es precisa, con algunas variaciones basadas en el lenguaje y las opciones del compilador. 2. No tiene que devolver nulo / falso. Puede devolver un número, hasta MAX_ULONG códigos de retorno allí mismo. Alternativamente, puede devolver una cadena de error y simplemente verificar una cadena de éxito o int o null. 3. En escenarios del mundo real, cada ciclo de reloj cuenta. Facebook tiene 552 millones de usuarios activos diarios. Suponiendo que las excepciones son solo el doble y que la verificación de usuario / pase toma .001s, eso significa ahorrar 153 horas de tiempo de procesamiento todos los días. A 10,000x ahorra 175 años. Solo para verificar los intentos de inicio de sesión, todos los días.
evan
@evan: FYI, aquí probaron el código con excepciones y no parece ser más lento: stackoverflow.com/a/445094/260080
Marco Demaio
@MarcoDemaio Esa pregunta solo cubre el bloque try / catch sin lanzar una excepción. Una mejor prueba sería devolver un valor en noexcept () y lanzar una excepción en except (). Además, debería surgir a través de múltiples funciones. stackoverflow.com/a/104375/505172 establece que la diferencia en PHP es en realidad 54x. Ejecuté mi propia prueba en tiempo real y parece ser de 2 a 10 veces más lento. Todo esto es mucho mejor de lo esperado.
evan
@evan: No me preocuparía entonces, uso excepciones solo para rastrear errores inesperados / irrecuperables, así que incluso si fuera 100 veces más lento, no me importaría. Mis preocupaciones eran hacer el código más lento simplemente agregando bloques try / catch.
Marco Demaio
4

Creo que la respuesta que estás buscando es esa;

Los errores son las cosas estándar a las que está acostumbrado, como hacer eco de una variable $ que no existe.
Las excepciones son solo desde PHP 5 en adelante y vienen cuando se trata de objetos.

Para mantenerlo simple:

Las excepciones son los errores que obtiene al tratar con objetos. Sin embargo, la declaración try / catch le permite hacer algo al respecto y se usa de manera muy similar a la declaración if / else. Intente hacer esto, si el problema no importa, hágalo.

Si no "detecta" una excepción, se convierte en un error estándar.

Los errores son los errores fundamentales de php que normalmente detienen su script.

Try / catch se usa a menudo para establecer conexiones de bases de datos como PDO, lo cual está bien si desea redirigir el script o hacer algo más si la conexión no funciona. Pero si solo desea mostrar el mensaje de error y detener el script, entonces no lo necesita, la excepción no detectada se convierte en un error fatal. O también puede usar una configuración de manejo de errores en todo el sitio.

Espero que ayude

Lan
fuente
3
Las excepciones también se pueden usar con código de procedimiento en PHP.
Tiberiu-Ionuț Stan
2

En PHP 7.1 y posterior, un bloque de captura puede especificar múltiples excepciones usando el carácter de barra vertical (|). Esto es útil para cuando diferentes excepciones de diferentes jerarquías de clases se manejan de la misma manera.

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

Las excepciones son lanzadas intencionalmente por código usando un lanzamiento, errores ... no tanto.

Los errores surgen como resultado de algo que no se maneja normalmente. (Errores de E / S, errores de TCP / IP, errores de referencia nula)

cgp
fuente
1
Esto no necesariamente es cierto. En muchos casos, se comprueban los errores y los códigos de retorno se devuelven intencionalmente según corresponda. De hecho, ese es el caso de todos los lenguajes no orientados a objetos. Las excepciones son solo eso, también, excepciones a la regla. En ambos casos, algo sale mal, se nota y debe manejarse. La carga de archivos PHP es un ejemplo de manejo intencional de errores a través de códigos de retorno - php.net/manual/en/features.file-upload.errors.php
evan
1

Tengo la intención de brindarles una discusión inusual sobre el control de errores.

Construí un muy buen controlador de errores en un lenguaje hace años, y aunque algunos de los nombres han cambiado, los principios del procesamiento de errores son los mismos hoy. Tenía un sistema operativo multitarea personalizado y tenía que poder recuperarme de errores de datos en todos los niveles sin fugas de memoria, crecimiento de pila o bloqueos. Entonces, lo que sigue es mi comprensión de cómo deben operar los errores y las excepciones y en qué se diferencian. Solo diré que no entiendo cómo funciona el funcionamiento interno de try catch, así que supongo que hasta cierto punto.

Lo primero que sucede bajo las cubiertas para el procesamiento de errores es saltar de un estado de programa a otro. ¿Cómo se hace eso? Llegaré a eso.

Históricamente, los errores son más antiguos y simples, y las excepciones son más nuevas y un poco más complejas y capaces. Los errores funcionan bien hasta que necesita hacerlos burbujear, lo que equivale a entregar un problema difícil a su supervisor.

Los errores pueden ser números, como números de error y, a veces, con una o más cadenas asociadas. Por ejemplo, si se produce un error de lectura de archivo, es posible que pueda informar qué es y posiblemente fallar correctamente. (Hay, es un paso adelante de simplemente chocar como en los viejos tiempos).

Lo que no se dice a menudo sobre las excepciones es que las excepciones son objetos en capas en una pila de excepciones especial. Es como una pila de retorno para el flujo del programa, pero tiene un estado de retorno solo para intentos y capturas de errores. (Solía ​​llamarlos ePush y ePop, y? Abort era un lanzamiento condicional que haría ePop y se recuperaría a ese nivel, mientras que Abort era un dado completo o una salida).

En la parte inferior de la pila está la información sobre el llamador inicial, el objeto que conoce el estado cuando se inició el intento externo, que suele ser cuando se inició el programa. Encima de eso, o la siguiente capa en la pila, con arriba siendo los niños y abajo los padres, es el objeto de excepción del siguiente bloque interno try / catch.

Si pones un intento dentro de un intento, estás apilando el intento interior encima del intento exterior. Cuando ocurre un error en el intento interno y la captura interna no puede manejarlo o el error se lanza al intento externo, entonces el control se pasa al bloque de captura externo (objeto) para ver si puede manejar el error, es decir su supervisor.

Entonces, lo que realmente hace esta pila de errores es poder marcar y restaurar el flujo del programa y el estado del sistema, en otras palabras, permite que un programa no bloquee la pila de devolución y arruine las cosas para otros (datos) cuando las cosas van mal. Por lo tanto, también guarda el estado de cualquier otro recurso, como los grupos de asignación de memoria, y puede limpiarlos cuando se realiza la captura. En general, esto puede ser algo muy complicado y es por eso que el manejo de excepciones suele ser lento. En general, es necesario que haya bastante estado en estos bloques de excepción.

Entonces, un bloque try / catch establece un estado al que poder regresar si todo lo demás se estropea. Es como un padre. Cuando nuestras vidas se estropean, podemos volver a caer en el regazo de nuestros padres y ellos lo arreglarán nuevamente.

Espero no haberte decepcionado.

Vista elíptica
fuente
1

Puede agregar este comentario

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

Una vez que se define set_error_handler (), el controlador de errores es similar al de Exception. Ver código a continuación:

 <?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
?>
N Zhang
fuente