Abfangen mehrerer Ausnahmetypen in einem Fangblock

243

Ich möchte einen saubereren Weg, um die folgenden Funktionen zu erhalten, zu fangen AErrorund BErrorin einem Block:

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

Gibt es eine Möglichkeit, dies zu tun? Oder muss ich sie separat fangen?

AErrorund Berrorhaben eine gemeinsame Basisklasse, aber sie teilen sie auch mit anderen Typen, zu denen ich gerne durchfallen würde handler2, sodass ich die Basisklasse nicht einfach abfangen kann.

Dominic Gurto
quelle
7
Nur um dies als Randnotiz hinzuzufügen: Ein RFC wurde eingereicht, um mehrere Ausnahmen abzufangen. Mal sehen, ob diese Funktion in die PHP-Sprache gelangt
SimonSimCity
10
^ Diese Funktion wurde in PHP 7.1
Subin

Antworten:

350

Aktualisieren:

Ab PHP 7.1 ist dies verfügbar.

Die Syntax lautet:

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

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

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

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


Für PHP vor 7.1:

Ungeachtet dessen, was diese anderen Antworten sagen, können Sie AErrorund BErrorim selben Block fangen (es ist etwas einfacher, wenn Sie derjenige sind, der die Ausnahmen definiert). Selbst wenn es Ausnahmen gibt, durch die Sie "fallen" möchten, sollten Sie dennoch in der Lage sein, eine Hierarchie zu definieren, die Ihren Anforderungen entspricht.

abstract class MyExceptions extends Exception {}

abstract class LetterError extends MyExceptions {}

class AError extends LetterError {}

class BError extends LetterError {}

Dann:

catch(LetterError $e){
    //voodoo
}

Wie Sie hier und hier sehen können , haben selbst die SPLStandardausnahmen eine Hierarchie, die Sie nutzen können. Zusätzlich, wie im PHP-Handbuch angegeben :

Wenn eine Ausnahme ausgelöst wird, wird der auf die Anweisung folgende Code nicht ausgeführt, und PHP versucht, den ersten übereinstimmenden catch-Block zu finden.

Das heißt, Sie könnten auch haben

class CError extends LetterError {}

die Sie anders als AErroroder behandeln müssen BError, damit Ihre catch-Anweisung folgendermaßen aussehen würde:

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

Wenn Sie den Fall hatten, dass es zwanzig oder mehr Ausnahmen gab, die rechtmäßig zu derselben Oberklasse gehörten, und Sie fünf (oder eine große Gruppe) davon auf die eine und den Rest auf die andere Weise behandeln mussten, können Sie dies NOCH tun.

interface Group1 {}

class AError extends LetterError implements Group1 {}

class BError extends LetterError implements Group1 {}

Und dann:

catch (Group1 $e) {}

Die Verwendung von OOP bei Ausnahmen ist sehr leistungsfähig. Die Verwendung von Dingen wie get_classoder instanceofsind Hacks und sollte nach Möglichkeit vermieden werden.

Eine andere Lösung, die ich hinzufügen möchte, besteht darin, die Ausnahmebehandlungsfunktion in eine eigene Methode zu integrieren.

Du könntest haben

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

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

Unter der Annahme , es gibt absolut keine Möglichkeit, Ausnahmeklassenhierarchien oder Schnittstellen steuern kann (und fast immer wird ein Weg sein), können Sie Folgendes tun:

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

Auf diese Weise haben Sie immer noch einen einzigen Code-Speicherort, den Sie ändern müssen, wenn sich Ihr Ausnahmebehandlungsmechanismus ändern muss, und Sie arbeiten innerhalb der allgemeinen Konstrukte von OOP.

MirroredFate
quelle
4
Hier ist eine weitere Abstimmung dafür als die richtige Antwort. Leider machen Dinge wie das, was in der akzeptierten Antwort gesagt wird und die Tatsache, dass es als die richtige Antwort akzeptiert wird, PHP zu dem Wahnsinn, der es ist.
Borfast
Dies sollte die akzeptierte Antwort sein. Es wird jedoch davon ausgegangen, dass Sie die Dateien ändern können. AErrorkönnte in einer Bibliothek / Datei implementiert werden, die von einem Dritten aktualisiert wird.
Kayla
@ WaffleStealer654 Sie können die Dateien weiterhin in Unterklassen unterteilen und diese Ihre Gruppe implementieren lassen, auch wenn Sie die Dateien nicht direkt bearbeiten können. Das würde voraussetzen, dass Sie die Ausnahmen auslösen können, aber Sie könnten einfach den Mechanismus auf der Basisebene umschließen, bei dem die Ausnahme ausgelöst wird, und sie dann abfangen und Ihre umschlossene Ausnahme auslösen.
MirroredFate
3
Dies ist nicht die akzeptierte Antwort, da Sie dies nicht tun können, wenn Sie eine Bibliothek eines Drittanbieters verwenden.
Denis V
@DenisV Siehe meinen Kommentar über deinem. Dies geschieht ständig in Unternehmenssoftware. Die Kapselung ist großartig.
MirroredFate
229

In PHP> = 7.1 ist dies möglich. Siehe die Antwort unten.


Wenn Sie die Ausnahmen ändern können, verwenden Sie diese Antwort .

Wenn Sie nicht können, können Sie versuchen, alle mit zu fangen Exceptionund dann zu überprüfen, mit welcher Ausnahme ausgelöst wurde 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;
    }
}

Aber es wäre wahrscheinlich besser, mehrere Fangblöcke zu verwenden, wie in der oben genannten Antwort beschrieben .

try
{
    /* something */
}
catch( AError $e )
{
   handler1( $e );
}
catch ( BError $b )
{
   handler2( $e );
}
Alex
quelle
6
Davor hatte ich Angst. Sie zusammen zu fangen und den Typ zu testen wäre gut, wenn es viele Fehlertypen gäbe, die zusammen behandelt werden müssten, aber für nur 2, wie in meinem Fall, ist es wahrscheinlich sauberer, sie separat zu fangen. Vielen Dank!
Dominic Gurto
3
@ DominicGurto: Ja, das würde ich auch machen :) Ich würde mich mehr um die Einstellung von PHP zu einer finallyAussage kümmern . ;)
Alex
7
Aber vergessen Sie nicht, dass dies ALLE Ausnahmen abfängt, also sollte es so etwas geben, ... } else { throw($e); }wenn es nicht zu den beiden passt. Entschuldigung für die möglicherweise falsche Syntax, habe PHP eine Weile nicht gesehen.
Dalibor Filus
11
Wenn Sie den ersten Absatz hier lesen: php.net/manual/en/language.exceptions.php, werden Sie sehen, dass mehrere Fangblöcke möglich und eine absolut gültige Lösung sind. Das OP hatte jedoch fälschlicherweise zwei Ausnahmeklassen in eine catch-Anweisung eingefügt. Ich denke, es ist besser, Ihre Antwort mit einem anderen Beispiel mit mehreren Fangblöcken zu aktualisieren.
Haralan Dobrev
4
Eine Lösung
vorzuschlagen
88

Kommt in PHP 7.1 mehrere Typen abgefangen werden.

Damit das:

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

und

<?php
try {

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

sind funktional gleichwertig.

Joe Watkins
quelle
45

Ab PHP 7.1,

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

Interessanterweise können Sie auch:

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

und in früheren Versionen von 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
quelle
25

Dieser Artikel behandelt die Frage elektrictoolbox.com/php-catch-multiple-exception-types . Inhalt des Beitrags direkt aus dem Artikel kopiert:

Beispiel Ausnahmen

Hier sind einige Beispielausnahmen, die für die Zwecke dieses Beispiels definiert wurden:

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

Behandlung mehrerer Ausnahmen

Es ist sehr einfach - es kann einen Fangblock für jeden Ausnahmetyp geben, der ausgelöst werden kann:

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
}

Wenn eine Ausnahme ausgelöst wird, die von keiner der anderen catch-Anweisungen behandelt wird, wird sie vom catch-Block (Exception $ e) behandelt. Es muss nicht unbedingt der letzte sein.

user1983902
quelle
3
Das Problem bei dieser Methode tritt auf, wenn Sie denselben Code für zwei oder mehr verschiedene Ausnahmen ausführen müssen.
Parziphal
Dies wurde aus der Electric Toolbox abgerufen . Beitrag bearbeiten, um Kredit zu geben.
Kayla
Mit PHP 7.x müssen Sie catch (Throwable $e)alle Ausnahmen abfangen. Siehe auch: php.net/manual/en/class.throwable.php
Mikko Rantalainen
21

Als Erweiterung der akzeptierten Antwort können Sie den Ausnahmetyp ändern, was zu einem Muster führt, das dem ursprünglichen Beispiel ähnelt:

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
quelle
6
Verwenden Sie anstelle dieser Lösung mehrere Fänge.
Alejandro Moreno
5

Hier ist eine sinnvolle Alternative, wenn Sie keine Kontrolle über die Definition der Ausnahmen haben. Verwenden Sie den Namen der Ausnahmevariablen, um die Ausnahmen zu kategorisieren, wenn sie abgefangen werden. Suchen Sie dann nach dem try / catch-Block nach der Ausnahmevariablen.

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

Dieser etwas seltsam aussehende Ansatz lohnt sich wahrscheinlich nur, wenn zwischen den Catch-Block-Implementierungen viele Überschneidungen bestehen.

dellsala
quelle
3

Neben dem Fall-Through ist es auch möglich, mit goto überzugehen . Es ist sehr nützlich, wenn Sie die Welt brennen sehen möchten.

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

Ein guter Weg ist zu verwenden set_exception_handler.

Warnung!!! Mit PHP 7 wird möglicherweise ein weißer Todesbildschirm für schwerwiegende Fehler angezeigt. Wenn Sie beispielsweise eine Methode für ein Nicht-Objekt aufrufen, erhalten Sie diese normalerweise Fatal error: Call to a member function your_method() on nullund Sie würden dies erwarten, wenn die Fehlerberichterstattung aktiviert ist.

Der obige Fehler wird NICHT abgefangen catch(Exception $e). Der obige Fehler löst KEINEN benutzerdefinierten Fehlerbehandler aus, der von festgelegt wurde set_error_handler.

Sie müssen verwenden catch(Error $e){ }, um Fehler in PHP7 abzufangen. . Dies könnte helfen:

class ErrorHandler{
    public static function excep_handler($e)
    {
        print_r($e);
    }
}
set_exception_handler(array('ErrorHandler','excep_handler'));
Frank Forte
quelle
1
... oder Sie könnten einfach schreiben catch (Throwable $e) { ... }und damit fertig sein. Siehe auch: php.net/manual/en/class.throwable.php
Mikko Rantalainen
0

Eine andere Option, die hier nicht aufgeführt ist, besteht darin, das codeAttribut einer Ausnahme zu verwenden, sodass Sie Folgendes tun können:

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
quelle
Ich habe nicht abgelehnt, aber vielleicht sind die OOP-Puristen wütend, dass Sie keine neuen Ausnahmeklassen mit erstellt haben extends \Exception?
KeyboardSmasher
Verstanden. Das ist der springende Punkt meiner Lösung. Sie müssen keine beliebigen Klassen erstellen, nur um einen Namespace einzurichten und eine bestimmte Ausnahme auszulösen. Ich bin mir sicher, dass sie deshalb die Möglichkeit hinzugefügt haben, einen Code anzugeben.
Mike Purcell
Ich habe auch nicht herabgestimmt, aber ich denke, Downvoter glauben, dass dies die Frage nicht beantwortet. Ich würde vorschlagen, die Antwort mit etwas zu beginnen, das dem Leser klar macht, dass Sie die Frage verstanden haben und dennoch einen völlig anderen Weg für den Codefluss vorschlagen möchten. Diese Antwort beantwortet wirklich nicht "wie man mehrere Ausnahmetypen abfängt ", sondern "wie man mehrere verschiedene Ursachen für eine Ausnahme behandelt".
Mikko Rantalainen
0

Hmm, es gibt viele Lösungen für PHP-Versionen unter 7.1.

Hier ist eine andere einfache für diejenigen, die nicht alle Ausnahmen abfangen möchten und keine gemeinsamen Schnittstellen erstellen können:

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