Captura de vários tipos de exceção em um bloco de captura

243

Eu gostaria de uma maneira mais limpa de obter a seguinte funcionalidade, pegar AErrore BErrorem um bloco:

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

Há alguma maneira de fazer isso? Ou tenho que pegá-los separadamente?

AErrore Berrortêm uma classe base compartilhada, mas eles também a compartilham com outros tipos que eu gostaria de abordar handler2, então não posso simplesmente pegar a classe base.

Dominic Gurto
fonte
7
Apenas para adicionar isso como uma observação lateral: Um RFC foi arquivado para capturar várias exceções. Vamos ver se esse recurso get é forma, é para a linguagem PHP ... wiki.php.net/rfc/multiple-catch
SimonSimCity
10
^ Esse recurso foi implementado no PHP 7.1
Subin 31/01

Respostas:

351

Atualizar:

A partir do PHP 7.1, isso está disponível.

A sintaxe é:

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

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


Para PHP anterior à 7.1:

Apesar do que essas outras respostas dizem, você pode capturar AErrore BErrorno mesmo bloco (é um pouco mais fácil se você é quem define as exceções). Mesmo considerando que há exceções que você deseja "adotar", você ainda deve poder definir uma hierarquia para atender às suas necessidades.

abstract class MyExceptions extends Exception {}

abstract class LetterError extends MyExceptions {}

class AError extends LetterError {}

class BError extends LetterError {}

Então:

catch(LetterError $e){
    //voodoo
}

Como você pode ver aqui e aqui , até as SPLexceções padrão têm uma hierarquia que você pode aproveitar. Além disso, conforme declarado no Manual do PHP :

Quando uma exceção é lançada, o código após a instrução não será executado, e o PHP tentará encontrar o primeiro bloco catch correspondente.

Isso significa que você também pode ter

class CError extends LetterError {}

que você precisa manipular de maneira diferente de AErrorou BError, portanto, sua declaração de captura ficaria assim:

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

Se você teve o caso em que havia vinte ou mais exceções que pertenciam legitimamente à mesma superclasse e precisou lidar com cinco (ou qualquer grupo grande) deles de uma maneira e o restante da outra, você ainda pode fazer isso.

interface Group1 {}

class AError extends LetterError implements Group1 {}

class BError extends LetterError implements Group1 {}

E depois:

catch (Group1 $e) {}

Usar OOP quando se trata de exceções é muito poderoso. Usar coisas como get_classou instanceofsão hacks e deve ser evitado, se possível.

Outra solução que eu gostaria de adicionar é colocar a funcionalidade de tratamento de exceções em seu próprio método.

Você pode ter

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

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

Supondo que não haja absolutamente nenhuma maneira de controlar hierarquias ou interfaces de classes de exceção (e quase sempre haverá uma maneira), você pode fazer o seguinte:

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);
}

Dessa forma, você ainda terá um único local de código que precisará modificar se o mecanismo de tratamento de exceções precisar mudar e estiver trabalhando nas construções gerais do OOP.

MirroredFate
fonte
4
Aqui está outro voto para isso como a resposta correta. Infelizmente, coisas como o que é dito na resposta aceita e o fato de ser aceita como resposta correta é o que faz do PHP a loucura que é.
borfast
Essa deve ser a resposta aceita. Embora, pressuponha que você possa modificar os arquivos. AErrorpode ser implementado em uma biblioteca / arquivo que é atualizado por terceiros.
21414 Kayla
@ WaffleStealer654 Você ainda pode subclassificar os arquivos e fazer com que eles implementem seu grupo, mesmo que não possa editar os arquivos diretamente. Isso presumiria que você pode lançar as exceções, mas você pode simplesmente agrupar o mecanismo de nível mais básico onde a exceção seria lançada e, em seguida, capturá-la e lançar sua exceção agrupada.
MirroredFate
3
Esta não é a resposta aceita, porque você não pode fazer isso quando usa uma biblioteca de terceiros.
Denis V
@ DenisV Veja meu comentário acima do seu. Isso é feito o tempo todo no software corporativo. Encapsulamento é ótimo.
MirroredFate
229

No PHP> = 7.1, isso é possível. Veja a resposta abaixo.


Se você pode modificar as exceções, use esta resposta .

Se não puder, você pode tentar descobrir tudo Exceptione verificar com qual exceção foi lançada 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;
    }
}

Mas provavelmente seria melhor usar vários bloqueios de captura, conforme descrito na resposta acima mencionada .

try
{
    /* something */
}
catch( AError $e )
{
   handler1( $e );
}
catch ( BError $b )
{
   handler2( $e );
}
alex
fonte
6
Era disso que eu tinha medo. Reuni-los e testar o tipo seria bom se houvesse muitos tipos de erro que precisassem ser tratados juntos, mas, para apenas 2, como no meu caso, pegá-los separadamente é provavelmente mais limpo. Obrigado!
Dominic Gurto
3
@DominicGurto: Sim, eu também aceitaria isso :) Eu ficaria mais preocupado com a atitude do PHP em relação a uma finallydeclaração. ;)
alex
7
Mas não esqueça que isso captura TODAS as exceções; portanto, deve haver algo como ... } else { throw($e); }se não corresponder às duas. Desculpe por talvez sintaxe errada, não vi php por um tempo.
Dalibor Filus
11
Se você ler o primeiro parágrafo aqui: php.net/manual/en/language.exceptions.php , verá vários blocos de captura possíveis e uma solução perfeitamente válida. O OP, por engano, colocou duas classes de exceção em uma instrução catch. Eu acho que será melhor atualizar sua resposta com outro exemplo com vários blocos de captura.
Haralan Dobrev 7/08/13
4
Sugerindo uma solução que come todas as suas outras exceções, não deveria ter sido aceite em todos os ...
Stivni
88

Chegando no PHP 7.1 é a capacidade de pegar vários tipos.

Para que isso:

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

e

<?php
try {

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

são funcionalmente equivalentes.

Joe Watkins
fonte
45

A partir do PHP 7.1,

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

Curiosamente, você também pode:

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

e nas versões anteriores do 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
fonte
25

Este artigo aborda a questão electrictoolbox.com/php-catch-multiple-exception-types . Conteúdo da postagem copiada diretamente do artigo:

Exceções de exemplo

Aqui estão alguns exemplos de exceções que foram definidas para os fins deste exemplo:

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
  }
}

Manipulando Várias Exceções

É muito simples - pode haver um bloco catch para cada tipo de exceção que pode ser lançado:

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
}

Se for lançada uma exceção que não seja tratada por nenhuma das outras instruções catch, ela será tratada pelo bloco catch (Exception $ e). Não precisa necessariamente ser o último.

user1983902
fonte
3
O problema com esse método ocorre quando você precisa executar o mesmo código para duas ou mais exceções diferentes.
Parziphal 14/05
Isso foi recuperado da Electric Toolbox . Editando postagem para dar crédito.
Kayla
Com o PHP 7.x, você precisa catch (Throwable $e)capturar todas as exceções. Veja também: php.net/manual/en/class.throwable.php #
Mikko Rantalainen
21

Como uma extensão da resposta aceita, você pode alternar o tipo de exceção, resultando em um padrão semelhante ao exemplo 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
fonte
6
use várias capturas em vez desta solução.
Alejandro Moreno
5

Aqui está uma alternativa razoável se você não tiver controle sobre a definição das exceções. Use o nome da variável de exceção para categorizar as exceções quando elas são capturadas. Em seguida, verifique a variável de exceção após o bloco 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);
}

Essa abordagem de aparência um tanto estranha provavelmente só vale a pena se houver muita duplicação entre implementações de blocos de captura.

dellsala
fonte
3

Além do fall-through, também é possível avançar usando goto . É muito útil se você quiser ver o mundo queimar.

<?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_
fonte
1

Uma ótima maneira é usar set_exception_handler.

Aviso!!! com o PHP 7, você pode obter uma tela branca da morte por erros fatais. Por exemplo, se você chamar um método em um não-objeto, normalmente obteria Fatal error: Call to a member function your_method() on nulle esperaria ver isso se o relatório de erros estiver ativado.

O erro acima NÃO será detectado catch(Exception $e). O erro acima NÃO acionará nenhum manipulador de erro personalizado definido por set_error_handler.

Você deve usar catch(Error $e){ }para capturar erros no PHP7. . Isso pode ajudar:

class ErrorHandler{
    public static function excep_handler($e)
    {
        print_r($e);
    }
}
set_exception_handler(array('ErrorHandler','excep_handler'));
Frank Forte
fonte
1
... ou você pode simplesmente escrever catch (Throwable $e) { ... }e acabar com isso. Veja também: php.net/manual/en/class.throwable.php #
Mikko Rantalainen
0

Outra opção não listada aqui é usar o codeatributo de uma exceção, para que você possa fazer algo assim:

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
fonte
Não diminuí o voto, mas talvez os puristas do OOP estejam bravos por você não ter criado novas classes de exceção usando extends \Exception?
keyboardSmasher
Entendi. Esse é o ponto principal da minha solução: você não precisa criar classes arbitrárias apenas para estabelecer um espaço para nome para lançar uma exceção específica. Estou certo de que é por isso que eles adicionaram a capacidade de especificar um código.
Mike Purcell
Também não diminuí a votação, mas acho que os votantes negativos acreditam que isso não responde à pergunta. Sugiro começar a resposta com algo que deixe claro para o leitor que você entendeu a pergunta e ainda deseja sugerir uma maneira totalmente diferente para o fluxo de código. Esta resposta realmente não responde "como capturar vários tipos de exceção ", mas "como lidar com várias causas diferentes para uma exceção".
Mikko Rantalainen
0

Hmm, existem muitas soluções escritas para a versão php abaixo da 7.1.

Aqui está outro exemplo simples para quem não deseja capturar todas as exceções e não pode criar interfaces comuns:

<?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.
fonte