Łapanie wielu typów wyjątków w jednym bloku catch

244

Chciałbym czystszego sposobu na uzyskanie następujących funkcji, aby złapać AErrori BErrorw jednym bloku:

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

Czy jest na to sposób? Czy też muszę je łapać osobno?

AErrori Berrormają wspólną klasę podstawową, ale dzielą ją także z innymi typami, do których chciałbym się zaliczyć handler2, więc nie mogę po prostu złapać klasy podstawowej.

Dominic Gurto
źródło
7
Wystarczy dodać to jako notatkę dodatkową: Złożono RFC w celu wychwycenia wielu wyjątków. Zobaczmy, czy ta funkcja dostanie się do języka PHP ... wiki.php.net/rfc/multiple-catch
SimonSimCity 22.04.16
10
^ Ta funkcja została zaimplementowana w PHP 7.1
Subin

Odpowiedzi:

353

Aktualizacja:

Od PHP 7.1 jest to dostępne.

Składnia jest następująca:

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

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

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

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


Dla PHP przed 7.1:

Pomimo tego, co mówią te inne odpowiedzi, możesz złapać AErrori BErrorw tym samym bloku (jest to nieco łatwiejsze, jeśli to Ty definiujesz wyjątki). Nawet biorąc pod uwagę, że istnieją wyjątki, przez które chcesz „upaść”, nadal powinieneś być w stanie zdefiniować hierarchię odpowiadającą twoim potrzebom.

abstract class MyExceptions extends Exception {}

abstract class LetterError extends MyExceptions {}

class AError extends LetterError {}

class BError extends LetterError {}

Następnie:

catch(LetterError $e){
    //voodoo
}

Jak widać tu i tutaj , nawet SPLdomyślne wyjątki mają hierarchię, którą można wykorzystać. Dodatkowo, jak stwierdzono w podręczniku PHP :

Po zgłoszeniu wyjątku kod następujący po instrukcji nie zostanie wykonany, a PHP spróbuje znaleźć pierwszy pasujący blok catch.

Oznacza to, że Ty też możesz mieć

class CError extends LetterError {}

które musisz obsługiwać inaczej niż AErrorlub BError, więc instrukcja catch wyglądałaby następująco:

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

Jeśli miałeś przypadek, w którym istniało dwadzieścia lub więcej wyjątków, które legalnie należały do ​​tej samej nadklasy, i musiałeś poradzić sobie z pięcioma (lub jakąkolwiek dużą grupą) z nich w jedną stronę, a reszta w drugiej, nadal możesz to zrobić.

interface Group1 {}

class AError extends LetterError implements Group1 {}

class BError extends LetterError implements Group1 {}

I wtedy:

catch (Group1 $e) {}

Używanie OOP w przypadku wyjątków jest bardzo skuteczne. Używanie rzeczy takich jak hack get_classlub instanceofsą hackami i należy ich unikać, jeśli to możliwe.

Innym rozwiązaniem, które chciałbym dodać, jest umieszczenie funkcji obsługi wyjątków we własnej metodzie.

Mógłbyś

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

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

Zakładając, że absolutnie nie ma możliwości kontrolowania hierarchii klas interfejsów lub interfejsów wyjątków (i prawie zawsze będzie sposób), możesz wykonać następujące czynności:

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

W ten sposób nadal masz tylko jedną lokalizację kodu, którą musisz zmodyfikować, jeśli twój mechanizm obsługi wyjątków wymaga zmiany, i pracujesz w ogólnych konstrukcjach OOP.

MirroredFate
źródło
4
Oto kolejny głos na to jako poprawną odpowiedź. Niestety rzeczy takie jak to, co zostało powiedziane w zaakceptowanej odpowiedzi i fakt, że jest ona zaakceptowana jako poprawna odpowiedź, sprawia, że ​​PHP jest szaleństwem.
borfast
To powinna być zaakceptowana odpowiedź. Zakłada się jednak, że możesz modyfikować pliki. AErrormoże być zaimplementowany w bibliotece / pliku, który jest aktualizowany przez stronę trzecią.
Kayla,
@ WaffleStealer654 Nadal możesz podklasować pliki i wprowadzać je w grupie, nawet jeśli nie możesz bezpośrednio edytować plików. Zakładałoby to, że możesz wyrzucić wyjątki, ale możesz po prostu owinąć mechanizm na poziomie podstawowym, w którym wyjątek byłby wyrzucony, a następnie złapać go i wyrzucić zapakowany wyjątek.
MirroredFate
3
To nie jest zaakceptowana odpowiedź, ponieważ nie możesz tego zrobić, gdy używasz biblioteki innej firmy.
Denis V,
@DenisV Zobacz mój komentarz powyżej twojego. Odbywa się to cały czas w oprogramowaniu dla przedsiębiorstw. Kapsułkowanie jest świetne.
MirroredFate
229

W PHP> = 7.1 jest to możliwe. Zobacz odpowiedź poniżej.


Jeśli możesz zmodyfikować wyjątki, skorzystaj z tej odpowiedzi .

Jeśli nie możesz, możesz spróbować złapać wszystko, Exceptiona następnie sprawdzić, który wyjątek został zgłoszony 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;
    }
}

Ale prawdopodobnie lepiej byłoby użyć wielu bloków catch, jak opisano we wspomnianej odpowiedzi .

try
{
    /* something */
}
catch( AError $e )
{
   handler1( $e );
}
catch ( BError $b )
{
   handler2( $e );
}
alex
źródło
6
Tego się bałem. Złapanie ich razem i przetestowanie typu byłoby dobre, gdyby istniało wiele rodzajów błędów, które należało poradzić sobie razem, ale tylko dla 2, takich jak w moim przypadku, złapanie ich osobno jest prawdopodobnie czystsze. Dzięki!
Dominic Gurto,
3
@DominicGurto: Tak, też bym z tym poszedł :) Bardziej martwi mnie stosunek PHP do finallywypowiedzi. ;)
alex
7
Ale nie zapominaj, że to wychwytuje WSZYSTKIE wyjątki, więc powinno być coś takiego, ... } else { throw($e); }jeśli nie pasuje do dwóch. Przepraszam za być może złą składnię, przez jakiś czas nie widziałem php.
Dalibor Filus,
11
Jeśli przeczytasz pierwszy akapit tutaj: php.net/manual/en/language.exceptions.php zobaczysz, że możliwych jest wiele bloków catch i doskonale poprawne rozwiązanie. Jednak OP błędnie umieścił dwie klasy wyjątków w jednej instrukcji catch. Myślę, że lepiej będzie zaktualizować twoją odpowiedź innym przykładem z wieloma blokami catch.
Haralan Dobrev
4
Sugerowanie rozwiązania, które zjada wszystkie pozostałe wyjątki, w ogóle nie powinno zostać zaakceptowane ...
Stivni
88

W PHP 7.1 można łapać wiele typów.

Aby to:

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

i

<?php
try {

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

są funkcjonalnie równoważne.

Joe Watkins
źródło
45

Od PHP 7.1

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

co ciekawe, możesz także:

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

oraz we wcześniejszych wersjach 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
źródło
25

Ten artykuł obejmuje pytanie electrictoolbox.com/php-catch-multiple-exception-types . Treść postu skopiowana bezpośrednio z artykułu:

Przykładowe wyjątki

Oto kilka przykładowych wyjątków, które zostały zdefiniowane na potrzeby tego przykładu:

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

Obsługa wielu wyjątków

To bardzo proste - dla każdego typu wyjątku, który może zostać zgłoszony, może istnieć blok przechwytujący:

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
}

Jeśli zgłoszony zostanie wyjątek, który nie jest obsługiwany przez żadną inną instrukcję catch, zostanie on obsłużony przez blok catch (wyjątek $ e). To niekoniecznie musi być ostatni.

użytkownik1983902
źródło
3
Problem z tą metodą pojawia się, gdy musisz wykonać ten sam kod dla dwóch lub więcej różnych wyjątków.
Parziphal
Zostało to pobrane z Electric Toolbox . Edytowanie postu w celu uznania.
Kayla,
W PHP 7.x musisz catch (Throwable $e)wychwycić wszystkie wyjątki. Zobacz także: php.net/manual/en/class.throwable.php
Mikko Rantalainen
21

Jako rozszerzenie zaakceptowanej odpowiedzi można zmienić typ wyjątku, co spowoduje, że wzorzec będzie podobny do oryginalnego przykładu:

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
źródło
6
użyj wielu zaczepów zamiast tego rozwiązania.
Alejandro Moreno
5

Oto rozsądna alternatywa, jeśli nie masz kontroli nad definiowaniem wyjątków. Użyj nazwy zmiennej wyjątku, aby kategoryzować wyjątki, gdy zostaną złapane. Następnie sprawdź zmienną wyjątku po bloku 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);
}

To nieco dziwnie wyglądające podejście jest prawdopodobnie warte zachodu tylko wtedy, gdy istnieje wiele duplikacji między implementacjami bloku catch.

dellsala
źródło
3

Oprócz upadku można również przejść na drugą stronę, używając goto . Jest to bardzo przydatne, jeśli chcesz zobaczyć, jak płonie świat.

<?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_
źródło
1

Świetnym sposobem jest użycie set_exception_handler.

Ostrzeżenie!!! z PHP 7 możesz dostać biały ekran śmierci za fatalne błędy. Na przykład, jeśli wywołasz metodę na obiekcie niebędącym obiektem, normalnie byś ją otrzymał Fatal error: Call to a member function your_method() on nulli spodziewałbyś się tego, jeśli raportowanie błędów jest włączone.

Powyższy błąd NIE zostanie złapany catch(Exception $e). Powyższy błąd NIE wyzwoli żadnej niestandardowej procedury obsługi błędów ustawionej przez set_error_handler.

Musisz użyć, catch(Error $e){ }aby wyłapać błędy w PHP7. . To może pomóc:

class ErrorHandler{
    public static function excep_handler($e)
    {
        print_r($e);
    }
}
set_exception_handler(array('ErrorHandler','excep_handler'));
Frank Forte
źródło
1
... lub możesz po prostu napisać catch (Throwable $e) { ... }i gotowe. Zobacz także: php.net/manual/en/class.throwable.php
Mikko Rantalainen
0

Inną niewymienioną tutaj opcją jest użycie codeatrybutu wyjątku, abyś mógł zrobić coś takiego:

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
źródło
Nie przegłosowałem, ale może purystycy OOP są źli, że nie stworzyliście nowych klas wyjątków extends \Exception?
keyboardSmasher
Rozumiem. To jest sedno mojego rozwiązania, nie musisz tworzyć dowolnych klas, aby ustanowić przestrzeń nazw, aby wygenerować określony wyjątek. Jestem pewien, że właśnie dlatego dodali możliwość określenia kodu.
Mike Purcell,
Ja też nie głosowałem, ale myślę, że zwolennicy uważają, że to nie odpowiada na pytanie. Sugeruję rozpoczęcie odpowiedzi od czegoś, co wyjaśnia czytelnikowi, że zrozumiałeś pytanie i nadal chcesz zaproponować zupełnie inny sposób przepływu kodu. Ta odpowiedź tak naprawdę nie odpowiada „jak złapać wiele typów wyjątków ”, ale „jak obsłużyć wiele różnych przyczyn wyjątku”.
Mikko Rantalainen,
0

Hmm, istnieje wiele rozwiązań napisanych dla wersji php niższej niż 7.1.

Oto inny prosty dla tych, którzy nie chcą wychwytywać wszystkich wyjątków i nie mogą tworzyć wspólnych interfejsów:

<?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.
źródło