Cattura di più tipi di eccezione in un blocco catch


244

Vorrei un modo più pulito per ottenere le seguenti funzionalità, da catturare AErrore BErrorin un blocco:

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

C'è un modo per fare questo? O devo catturarli separatamente?

AErrore Berrorhanno una classe base condivisa, ma la condividono anche con altri tipi a cui mi piacerebbe passare handler2, quindi non posso semplicemente catturare la classe base.


7
Solo per aggiungere questo come nota a margine: è stato archiviato un RFC per rilevare più eccezioni. Vediamo se questa funzionalità arriva nel linguaggio PHP ... wiki.php.net/rfc/multiple-catch
SimonSimCity

10
^ Questa funzione è stata implementata in PHP 7.1
Subin

Risposte:


353

Aggiornare:

A partire da PHP 7.1, questo è disponibile.

La sintassi è:

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

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

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

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


Per PHP prima della 7.1:

Nonostante ciò che dicono queste altre risposte, puoi catturare AErrore BErrornello stesso blocco (è un po 'più semplice se sei tu a definire le eccezioni). Anche dato che ci sono delle eccezioni che vuoi "superare", dovresti comunque essere in grado di definire una gerarchia in base alle tue esigenze.

abstract class MyExceptions extends Exception {}

abstract class LetterError extends MyExceptions {}

class AError extends LetterError {}

class BError extends LetterError {}

Poi:

catch(LetterError $e){
    //voodoo
}

Come puoi vedere qui e qui , anche le SPLeccezioni predefinite hanno una gerarchia che puoi sfruttare. Inoltre, come indicato nel manuale di PHP :

Quando viene generata un'eccezione, il codice che segue l'istruzione non verrà eseguito e PHP tenterà di trovare il primo blocco catch corrispondente.

Questo significa che potresti anche avere

class CError extends LetterError {}

che devi gestire in modo diverso da AErroro BError, quindi la tua dichiarazione catch dovrebbe apparire così:

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

Se hai avuto il caso in cui c'erano venti o più eccezioni che appartenevano legittimamente alla stessa superclasse, e dovevi gestirne cinque (o qualunque gruppo di grandi dimensioni) in un modo e il resto nell'altro, puoi ANCORA farlo.

interface Group1 {}

class AError extends LetterError implements Group1 {}

class BError extends LetterError implements Group1 {}

E poi:

catch (Group1 $e) {}

L'uso di OOP quando si tratta di eccezioni è molto potente. Usare cose come get_classo instanceofsono hack e, se possibile, dovrebbe essere evitato.

Un'altra soluzione che vorrei aggiungere è quella di mettere la funzionalità di gestione delle eccezioni nel suo metodo.

Potresti avere

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

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

Supponendo che non sia assolutamente possibile controllare le gerarchie o le interfacce delle classi di eccezioni (e quasi sempre ci sarà un modo), è possibile effettuare le seguenti operazioni:

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

In questo modo, hai ancora un'unica posizione del codice che devi modificare se il meccanismo di gestione delle eccezioni deve cambiare e stai lavorando all'interno dei costrutti generali di OOP.


4
Ecco un altro voto per questo come la risposta corretta. Purtroppo cose come ciò che viene detto nella risposta accettata e il fatto che sia accettata come risposta corretta, è ciò che rende PHP la follia che è.
borfast,

Questa dovrebbe essere la risposta accettata. Tuttavia, presuppone che sia possibile modificare i file. AErrorpotrebbe essere implementato in una libreria / file che viene aggiornato da una terza parte.
Kayla,

@ WaffleStealer654 Puoi comunque sottoclassare i file e farli implementare il tuo gruppo, anche se non puoi modificare direttamente i file. Ciò presumerebbe che puoi lanciare le eccezioni, ma potresti semplicemente avvolgere il meccanismo di livello più basso in cui verrebbe generata l'eccezione e quindi catturarlo e lanciare la tua eccezione incartata.
MirroredFate

3
Questa non è la risposta accettata, perché non è possibile farlo quando si utilizza una libreria di terze parti.
Denis V,

@DenisV Vedi il mio commento sopra il tuo. Viene eseguito continuamente nel software aziendale. L'incapsulamento è eccezionale.
MirroredFate

229

In PHP> = 7.1 questo è possibile. Vedi la risposta sotto.


Se puoi modificare le eccezioni, usa questa risposta .

Se non ci riesci, puoi provare a intercettare tutto Exceptione quindi verificare con quale eccezione è stata generata 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;
    }
}

Ma probabilmente sarebbe meglio usare più blocchi catch come descritto nella risposta di cui sopra .

try
{
    /* something */
}
catch( AError $e )
{
   handler1( $e );
}
catch ( BError $b )
{
   handler2( $e );
}

6
Questo è ciò di cui avevo paura. Catturarli insieme e testare il tipo sarebbe positivo se ci fossero molti tipi di errore che dovevano essere gestiti insieme, ma solo per 2, come nel mio caso, catturarli separatamente è probabilmente più pulito. Grazie!
Dominic Gurto,

3
@DominicGurto: Sì, ci andrei anch'io :) Sarei più preoccupato dell'atteggiamento di PHP nei confronti di una finallydichiarazione. ;)
alex

7
Ma non dimenticare che questo coglie TUTTE le eccezioni, quindi dovrebbe esserci qualcosa di simile ... } else { throw($e); }se non corrisponde ai due. Scusate la sintassi forse sbagliata, non vedo php da un po '.
Dalibor Filus,

11
Se leggi il primo paragrafo qui: php.net/manual/en/language.exceptions.php vedrai che sono possibili più blocchi di cattura e una soluzione perfettamente valida. L'OP, tuttavia, aveva erroneamente inserito due classi di eccezione in una dichiarazione catch. Penso che sarà meglio aggiornare la tua risposta con un altro esempio con più blocchi di cattura.
Haralan Dobrev,

4
Suggerire una soluzione che mangia tutte le altre eccezioni, non avrebbe dovuto essere accettato affatto ...
Stivni

88

Venire in PHP 7.1 è la capacità di catturare più tipi.

In modo che questo:

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

e

<?php
try {

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

sono funzionalmente equivalenti.


45

A partire da PHP 7.1,

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

è interessante notare che puoi anche:

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

e nelle versioni precedenti di 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
    }
}

25

Questo articolo copre la domanda electrictoolbox.com/php-catch-multiple-exception-types . Contenuto del post copiato direttamente dall'articolo:

Eccezioni di esempio

Ecco alcune eccezioni di esempio che sono state definite ai fini di questo esempio:

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

Gestire più eccezioni

È molto semplice: può essere presente un blocco catch per ogni tipo di eccezione che può essere generato:

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 viene generata un'eccezione che non viene gestita da nessuna delle altre dichiarazioni catch, verrà gestita dal blocco catch (Exception $ e). Non deve essere necessariamente l'ultimo.


3
Il problema con questo metodo si presenta quando è necessario eseguire lo stesso codice per due o più Eccezioni diverse.
Parziphal,

Questo è stato recuperato da Electric Toolbox . Modifica post per dare credito.
Kayla,

Con PHP 7.x, devi catch (Throwable $e)catturare tutte le eccezioni. Vedi anche: php.net/manual/en/class.throwable.php
Mikko Rantalainen

21

Come estensione della risposta accettata, è possibile cambiare il tipo di eccezione risultando in un modello che è in qualche modo simile all'esempio originale:

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;

    }

}

6
utilizzare più catture invece di questa soluzione.
Alejandro Moreno,

5

Ecco un'alternativa ragionevole se non hai il controllo sulla definizione delle eccezioni. Utilizzare il nome della variabile di eccezione per classificare le eccezioni quando vengono rilevate. Quindi controlla la variabile di eccezione dopo il blocco 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);
}

Questo approccio un po 'strano probabilmente vale la pena solo se ci sono molte duplicazioni tra le implementazioni del blocco catch.


3

Oltre al fall-through, è anche possibile scavalcare usando goto . È molto utile se vuoi vedere il mondo bruciare.

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


1

Un ottimo modo è usare set_exception_handler.

Avvertimento!!! con PHP 7, potresti ottenere una schermata bianca della morte per errori fatali. Ad esempio, se si chiama un metodo su un non-oggetto, si otterrebbe normalmente Fatal error: Call to a member function your_method() on nulle ci si aspetterebbe di vederlo se la segnalazione degli errori è attiva.

L'errore sopra riportato NON verrà rilevato catch(Exception $e). L'errore sopra riportato NON attiverà alcun gestore di errori personalizzato impostato da set_error_handler.

È necessario utilizzare catch(Error $e){ }per rilevare errori in PHP7. . Questo potrebbe aiutare:

class ErrorHandler{
    public static function excep_handler($e)
    {
        print_r($e);
    }
}
set_exception_handler(array('ErrorHandler','excep_handler'));

1
... o potresti semplicemente scrivere catch (Throwable $e) { ... }e finire. Vedi anche: php.net/manual/en/class.throwable.php
Mikko Rantalainen

0

Un'altra opzione non elencata qui è utilizzare l' codeattributo di un'eccezione, quindi puoi fare qualcosa del genere:

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

Non ho votato a fondo, ma forse i puristi di OOP sono arrabbiati che non hai creato nuove classi di eccezioni usando extends \Exception?
KeyboardSmasher

Fatto. Questo è il punto centrale della mia soluzione, non è necessario creare classi arbitrarie solo per stabilire uno spazio dei nomi per generare un'eccezione specifica. Sono certo che è il motivo per cui hanno aggiunto la possibilità di specificare un codice.
Mike Purcell,

Nemmeno io ho votato a favore, ma credo che i downvoter credano che questo non risponda alla domanda. Suggerirei di iniziare la risposta con qualcosa che chiarisca al lettore che hai capito la domanda e vuoi comunque suggerire un modo completamente diverso per il flusso di codice. Questa risposta in realtà non risponde "come catturare più tipi di eccezione " ma piuttosto "come gestire più cause diverse per un'eccezione".
Mikko Rantalainen,

0

Hmm, ci sono molte soluzioni scritte per la versione php precedente alla 7.1.

Ecco un altro semplice per coloro che non vogliono catturare tutte le eccezioni e non possono creare interfacce comuni:

<?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!
}
?>
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.