Capturando múltiples tipos de excepción en un bloque de captura

243

Me gustaría una forma más limpia de obtener la siguiente funcionalidad, para capturar AErrory BErroren un bloque:

try
{
    /* something */
}
catch( AError, BError $e )
{
    handler1( $e )
}
catch( Exception $e )
{
    handler2( $e )
}

¿Hay alguna forma de hacer esto? ¿O tengo que atraparlos por separado?

AErrory Berrortienen una clase base compartida, pero también la comparten con otros tipos a los que me gustaría acceder handler2, por lo que no puedo atrapar la clase base.

Dominic Gurto
fuente
77
Solo para agregar esto como nota al margen: se ha archivado un RFC para detectar múltiples excepciones. Veamos si esta característica llega al lenguaje PHP ... wiki.php.net/rfc/multiple-catch
SimonSimCity
10
^ Esta característica se ha implementado en PHP 7.1
Subin

Respuestas:

351

Actualizar:

A partir de PHP 7.1, esto está disponible.

La sintaxis es:

try
{
    // Some code...
}
catch(AError | BError $e)
{
    // Handle exceptions
}
catch(Exception $e)
{
    // Handle the general case
}

Documentos: https://www.php.net/manual/en/language.exceptions.php#example-287

RFC: https://wiki.php.net/rfc/multiple-catch

Comprometerse: https://github.com/php/php-src/commit/0aed2cc2a440e7be17552cc669d71fdd24d1204a


Para PHP anterior a 7.1:

A pesar de lo que dicen estas otras respuestas, puede atrapar AErrory BErroren el mismo bloque (es algo más fácil si usted es quien define las excepciones). Incluso teniendo en cuenta que hay excepciones que desea "incumplir", debe poder definir una jerarquía que satisfaga sus necesidades.

abstract class MyExceptions extends Exception {}

abstract class LetterError extends MyExceptions {}

class AError extends LetterError {}

class BError extends LetterError {}

Luego:

catch(LetterError $e){
    //voodoo
}

Como puede ver aquí y aquí , incluso las SPLexcepciones predeterminadas tienen una jerarquía que puede aprovechar. Además, como se indica en el Manual de PHP :

Cuando se produce una excepción, el código que sigue a la instrucción no se ejecutará y PHP intentará encontrar el primer bloque catch coincidente.

Esto significa que también podrías tener

class CError extends LetterError {}

que debe manejar de manera diferente que AErroro BError, por lo que su declaración catch se vería así:

catch(CError $e){
    //voodoo
}
catch(LetterError $e){
    //voodoo
}

Si tuvo el caso en el que hubo veinte o más excepciones que pertenecían legítimamente a la misma superclase, y tuvo que manejar cinco (o cualquier grupo grande) de una manera y el resto de la otra, TODAVÍA puede hacer esto.

interface Group1 {}

class AError extends LetterError implements Group1 {}

class BError extends LetterError implements Group1 {}

Y entonces:

catch (Group1 $e) {}

Usar OOP cuando se trata de excepciones es muy poderoso. Usar cosas como get_classo instanceofson hacks, y debe evitarse si es posible.

Otra solución que me gustaría agregar es poner la funcionalidad de manejo de excepciones en su propio método.

Podrías tener

function handleExceptionMethod1(Exception $e)
{
    //voodoo
}

function handleExceptionMethod2(Exception $e)
{
    //voodoo
}

Asumiendo que no hay absolutamente ninguna manera de controlar las jerarquías o interfaces de clases de excepción (y casi siempre habrá una forma), puede hacer lo siguiente:

try
{
    stuff()
}
catch(ExceptionA $e)
{
    $this->handleExceptionMethod1($e);
}
catch(ExceptionB $e)
{
    $this->handleExceptionMethod1($e);
}
catch(ExceptionC $e)
{
    $this->handleExceptionMethod1($e);
}
catch(Exception $e)
{
    $this->handleExceptionMethod2($e);
}

De esta manera, todavía tiene una única ubicación de código único que debe modificar si su mecanismo de manejo de excepciones necesita cambiar, y está trabajando dentro de las construcciones generales de OOP.

Destino Espejado
fuente
44
Aquí hay otro voto para esto como la respuesta correcta. Desafortunadamente, cosas como lo que se dice en la respuesta aceptada y el hecho de que se acepta como la respuesta correcta es lo que hace que PHP sea una locura.
Borfast
Esta debería ser la respuesta aceptada. Sin embargo, se supone que puede modificar los archivos. AErrorpodría implementarse en una biblioteca / archivo actualizado por un tercero.
Kayla
@ WaffleStealer654 Todavía puede subclasificar los archivos y hacer que implementen su grupo, incluso si no puede editar los archivos directamente. Eso supondría que puede lanzar las excepciones, pero podría envolver el mecanismo de nivel más básico donde la excepción sería lanzar y luego atraparlo y lanzar su excepción envuelta.
MirroredFate
3
Esta no es la respuesta aceptada, porque no puede hacerlo cuando usa una biblioteca de terceros.
Denis V
@DenisV Vea mi comentario sobre el suyo. Se realiza todo el tiempo en software empresarial. La encapsulación es genial.
MirroredFate
229

En PHP> = 7.1 esto es posible. Vea la respuesta a continuación.


Si puede modificar las excepciones, use esta respuesta .

Si no puede, puede intentar atraparlos todos Exceptiony luego verificar con qué excepción se produjo instanceof.

try
{
    /* something */
}
catch( Exception $e )
{
    if ($e instanceof AError OR $e instanceof BError) {
       // It's either an A or B exception.
    } else {
        // Keep throwing it.
        throw $e;
    }
}

Pero probablemente sería mejor usar múltiples bloques catch como se describe en la respuesta mencionada anteriormente .

try
{
    /* something */
}
catch( AError $e )
{
   handler1( $e );
}
catch ( BError $b )
{
   handler2( $e );
}
alex
fuente
66
De eso tenía miedo. Reunirlos y probar el tipo sería bueno si hubiera muchos tipos de errores que debían manejarse juntos, pero solo para 2, como en mi caso, atraparlos por separado probablemente sea más limpio. ¡Gracias!
Dominic Gurto
3
@DominicGurto: Sí, yo también iría con eso :) Me preocuparía más la actitud de PHP hacia una finallydeclaración. ;)
alex
77
Pero no olvide que esto atrapa TODAS las excepciones, por lo que debería haber algo así como ... } else { throw($e); }si no coincide con los dos. Perdón por la sintaxis quizás incorrecta, no vi php por un tiempo.
Dalibor Filus
11
Si lee el primer párrafo aquí: php.net/manual/en/language.exceptions.php , verá que son posibles múltiples bloques catch y una solución perfectamente válida. Sin embargo, el OP había puesto por error dos clases de excepción en una declaración catch. Creo que será mejor actualizar su respuesta con otro ejemplo con múltiples bloques catch.
Haralan Dobrev
44
Sugerir una solución que se coma todas las demás Excepciones, no debería haber sido aceptado en absoluto ...
Stivni
88

En PHP 7.1 está la capacidad de atrapar múltiples tipos.

Para que esto:

<?php
try {
    /* ... */
} catch (FirstException $ex) {
    $this->manageException($ex);
} catch (SecondException $ex) {
    $this->manageException($ex);
}
?>

y

<?php
try {

} catch (FirstException | SecondException $ex) {
    $this->manageException($ex);
}
?>

son funcionalmente equivalentes

Joe Watkins
fuente
45

A partir de PHP 7.1,

catch( AError | BError $e )
{
    handler1( $e )
}

Curiosamente, también puedes:

catch( AError | BError $e )
{
    handler1( $e )
} catch (CError $e){
    handler2($e);
} catch(Exception $e){
    handler3($e);
}

y en versiones anteriores de PHP:

catch(Exception $ex){
    if($ex instanceof AError){
        //handle a AError
    } elseif($ex instanceof BError){
        //handle a BError
    } else {
       throw $ex;//an unknown exception occured, throw it further
    }
}
Hanshenrik
fuente
25

Este artículo cubre la pregunta electrictoolbox.com/php-catch-multiple-exception-types . Contenido de la publicación copiada directamente del artículo:

Excepciones de ejemplo

Aquí hay algunas excepciones de ejemplo que se han definido para los propósitos de este ejemplo:

class FooException extends Exception 
{
  public function __construct($message = null, $code = 0) 
  {
    // do something
  }
}

class BarException extends Exception 
{
  public function __construct($message = null, $code = 0) 
  {
    // do something
  }
}

class BazException extends Exception 
{
  public function __construct($message = null, $code = 0) 
  {
    // do something
  }
}

Manejo de múltiples excepciones

Es muy simple: puede haber un bloque catch para cada tipo de excepción que se puede generar:

try 
{
  // some code that might trigger a Foo/Bar/Baz/Exception
}

catch(FooException $e) 
{
  // we caught a foo exception
}

catch(BarException $e) 
{
  // we caught a bar exception
}

catch(BazException $e) 
{
  // we caught a baz exception
}

catch(Exception $e) 
{
  // we caught a normal exception
  // or an exception that wasn't handled by any of the above
}

Si se lanza una excepción que no es manejada por ninguna de las otras declaraciones catch, será manejada por el bloque catch (Excepción $ e). No necesariamente tiene que ser el último.

usuario1983902
fuente
3
El problema con este método surge cuando tiene que ejecutar el mismo código para dos o más Excepciones diferentes.
Parziphal
Esto fue recuperado de Electric Toolbox . Edición de publicaciones para dar crédito.
Kayla
Con PHP 7.x, debe catch (Throwable $e)capturar todas las excepciones. Ver también: php.net/manual/en/class.throwable.php
Mikko Rantalainen
21

Como una extensión de la respuesta aceptada, puede cambiar el tipo de Excepción que resulta en un patrón que es algo así como el ejemplo original:

try {

    // Try something

} catch (Exception $e) {

    switch (get_class($e)) {

        case 'AError':
        case 'BError':
            // Handle A or B
            break;

        case 'CError':
            // Handle C
            break;

        case default:
            // Rethrow the Exception
            throw $e;

    }

}
smix96
fuente
66
use múltiples capturas en lugar de esta solución.
Alejandro Moreno
5

Aquí hay una alternativa razonable si no tiene control sobre la definición de las excepciones. Use el nombre de la variable de excepción para clasificar las excepciones cuando se detectan. Luego verifique la variable de excepción después del bloque try / catch.

$ABError = null;
try {
    // something
} catch (AError $ABError) {  // let the exception fall through
} catch (BError $ABError) {  // let the exception fall through
} catch (Exception $e) {
    handler2($e);
}
if ($ABError) {
    handler1($ABError);
}

Este enfoque de aspecto un tanto extraño probablemente solo valga la pena si hay mucha duplicación entre las implementaciones de bloque de captura.

dellsala
fuente
3

Además de la caída, también es posible dar un paso adelante usando goto . Es muy útil si quieres ver arder el mundo.

<?php

class A_Error extends Exception {}
class B_Error extends Exception {}
class C_Error extends Exception {}

try {
    throw new A_Error();
} 
catch (A_Error $e) { goto abc; }
catch (B_Error $e) { goto abc; }
catch (C_Error $e) {
abc:
    var_dump(get_class($e));
    echo "Gotta Catch 'Em All\n";
}

3v4l.org

ml_
fuente
1

Una excelente manera es usarlo set_exception_handler.

¡¡¡Advertencia!!! con PHP 7, puede obtener una pantalla blanca de la muerte por errores fatales. Por ejemplo, si llama a un método en un no objeto, normalmente lo obtendrá Fatal error: Call to a member function your_method() on nully esperaría ver esto si el informe de errores está activado.

El error anterior NO será atrapado con catch(Exception $e). El error anterior NO activará ningún controlador de errores personalizado establecido por set_error_handler.

Debe usar catch(Error $e){ }para detectar errores en PHP7. . Esto podría ayudar:

class ErrorHandler{
    public static function excep_handler($e)
    {
        print_r($e);
    }
}
set_exception_handler(array('ErrorHandler','excep_handler'));
Frank Forte
fuente
1
... o simplemente podrías escribir catch (Throwable $e) { ... }y terminar con eso. Ver también: php.net/manual/en/class.throwable.php
Mikko Rantalainen
0

Otra opción que no se enumera aquí es usar el codeatributo de una excepción, para que pueda hacer algo como esto:

try {

    if (1 === $foo) {

         throw new Exception(sprintf('Invalid foo: %s', serialize($foo)), 1);
    }

    if (2 === $bar) {
        throw new Exception(sprintf('Invalid bar: %s', serialize($foo)), 2);
    }
} catch (Exception $e) {

    switch ($e->getCode()) {

        case 1:
            // Special handling for case 1
            break;

        case 2:
            // Special handling for case 2
            break;

        default:

            // Special handling for all other cases
    }
}
Mike Purcell
fuente
¿No voté en contra, pero tal vez los puristas de OOP están enojados porque no creaste nuevas clases de excepción usando extends \Exception?
keyboardSmasher
Entendido. Ese es el objetivo de mi solución, no es necesario crear clases arbitrarias solo para establecer un espacio de nombres para lanzar una excepción específica. Estoy seguro de que es por eso que agregaron la capacidad de especificar un código.
Mike Purcell
Yo tampoco voté en contra, pero creo que los votantes en contra creen que esto no responde la pregunta. Yo sugeriría a partir de la respuesta con algo que hace que sea claro para el lector que se ha entendido la pregunta y usted todavía desea sugerir una manera totalmente diferente para el flujo de código. Esta respuesta realmente no responde "cómo atrapar varios tipos de excepción " sino más bien "cómo manejar múltiples causas diferentes para una excepción".
Mikko Rantalainen
0

Hmm, hay muchas soluciones escritas para la versión php inferior a 7.1.

Aquí hay otro simple para aquellos que no quieren atrapar todas las excepciones y no pueden hacer interfaces comunes:

<?php
$ex = NULL
try {
    /* ... */
} catch (FirstException $ex) {
    // just do nothing here
} catch (SecondException $ex) {
    // just do nothing here
}
if ($ex !== NULL) {
    // handle those exceptions here!
}
?>
GT.
fuente