Перехват нескольких типов исключений в одном блоке перехвата


244

Я хотел бы более чистый способ получить следующую функциональность, чтобы поймать AErrorи BErrorв одном блоке:

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

Есть какой-либо способ сделать это? Или я должен ловить их отдельно?

AErrorи Berrorимеют общий базовый класс, но они также делятся им с другими типами, к которым я хотел бы перейти handler2, поэтому я не могу просто поймать базовый класс.


7
Просто чтобы добавить это как примечание: RFC был подан для перехвата нескольких исключений. Давайте посмотрим, сможет ли эта функция проникнуть в язык PHP ... wiki.php.net/rfc/multiple-catch
SimonSimCity

10
^ Эта функция была реализована в PHP 7.1
Subin

Ответы:


353

Обновить:

Начиная с PHP 7.1 это доступно.

Синтаксис:

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

Документы: https://www.php.net/manual/en/language.exceptions.php#example-287.

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

Фиксация: https://github.com/php/php-src/commit/0aed2cc2a440e7be17552cc669d71fdd24d1204a


Для PHP до 7.1:

Несмотря на то, что говорят эти другие ответы, вы можете поймать AErrorи BErrorв одном и том же блоке (это несколько проще, если вы тот, кто определяет исключения). Даже с учетом того, что есть исключения, которые вы хотите «провалить», вы все равно сможете определить иерархию, соответствующую вашим потребностям.

abstract class MyExceptions extends Exception {}

abstract class LetterError extends MyExceptions {}

class AError extends LetterError {}

class BError extends LetterError {}

Затем:

catch(LetterError $e){
    //voodoo
}

Как вы можете видеть здесь и здесь , даже SPLисключения по умолчанию имеют иерархию, которую вы можете использовать. Кроме того, как указано в руководстве по PHP :

Когда генерируется исключение, код, следующий за оператором, не будет выполнен, и PHP попытается найти первый соответствующий блок catch.

Это означает, что вы также можете иметь

class CError extends LetterError {}

который вы должны обрабатывать иначе, чем AErrorили BError, поэтому ваш оператор catch будет выглядеть так:

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

Если у вас был случай, когда было двадцать или более исключений, которые на законных основаниях принадлежали одному и тому же суперклассу, и вам нужно было обработать пять (или любую большую группу) из них одним способом, а остальные - другим, вы можете ВСЕ ЕЩЕ сделать это.

interface Group1 {}

class AError extends LetterError implements Group1 {}

class BError extends LetterError implements Group1 {}

А потом:

catch (Group1 $e) {}

Использование ООП, когда дело доходит до исключений, очень эффективно. Использование таких вещей, как get_classили instanceofявляются взломами, и следует избегать, если это возможно.

Еще одно решение, которое я хотел бы добавить, - добавить функциональность обработки исключений в свой собственный метод.

Вы могли бы иметь

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

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

Предполагая, что нет абсолютно никакого способа, которым вы можете управлять иерархиями или интерфейсами классов исключений (и почти всегда будет путь), вы можете сделать следующее:

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

Таким образом, у вас все еще есть единственное местоположение кода, которое вы должны изменить, если ваш механизм обработки исключений должен измениться, и вы работаете в общих конструкциях ООП.


4
Вот еще один голос за это как правильный ответ. К сожалению, такие вещи, как то, что сказано в принятом ответе, и тот факт, что он принят в качестве правильного ответа, делают PHP тем безумием, которым оно является.
воскресенье,

Это должен быть принятый ответ. Хотя это предполагает, что вы можете изменять файлы. AErrorможет быть реализовано в библиотеке / файле, который обновляется третьей стороной.
Кайла

@ WaffleStealer654 Вы по-прежнему можете создавать подклассы для файлов и заставлять их реализовывать вашу группу, даже если вы не можете редактировать файлы напрямую. Это предполагает, что вы можете выбросить исключения, но вы можете просто обернуть механизм самого базового уровня, где будет выбрасывать исключение, а затем перехватить его и выбросить упакованное исключение.
MirroredFate

3
Это не принятый ответ, потому что вы не можете сделать это, когда используете стороннюю библиотеку.
Денис V

@DenisV Смотрите мой комментарий над вашим. Это делается все время в программном обеспечении предприятия. Инкапсуляция отличная.
MirroredFate

229

В PHP> = 7.1 это возможно. Смотрите ответ ниже.


Если вы можете изменить исключения, используйте этот ответ .

Если вы не можете, вы можете попробовать поймать все с Exceptionи затем проверить, какое исключение было сгенерировано 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;
    }
}

Но , вероятно, было бы лучше использовать несколько блоков catch, как описано в вышеупомянутом ответе .

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

6
Вот чего я боялся. Было бы неплохо собрать их вместе и протестировать тип, если бы было много типов ошибок, которые нужно было обрабатывать вместе, но только для двух, как в моем случае, захват их отдельно, вероятно, более чистый. Спасибо!
Доминик Гурто

3
@DominicGurto: Да, я бы тоже с этим согласился :) Меня больше волновало бы отношение PHP к finallyутверждению. ;)
Алекс

7
Но не забывайте, что это ловит ВСЕ исключения, поэтому должно быть что-то вроде этого, ... } else { throw($e); }если оно не соответствует двум. Извините за неправильный синтаксис, не видел php некоторое время.
Далибор Филус

11
Если вы прочтете первый абзац здесь: php.net/manual/en/language.exceptions.php, вы увидите, что несколько блоков catch являются возможными и совершенно корректным решением. ОП, однако, по ошибке поместил два класса исключений в одну инструкцию catch. Я думаю, что будет лучше обновить ваш ответ другим примером с несколькими блоками catch.
Харалан Добрев

4
Предложение решения, которое съедает все ваши другие исключения, не должно было быть принято вообще ...
Стивни

88

В PHP 7.1 появилась возможность ловить несколько типов.

Так что это:

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

и

<?php
try {

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

функционально эквивалентны.


45

Начиная с PHP 7.1,

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

Интересно, что вы также можете:

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

и в более ранних версиях 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

В этой статье рассматривается вопрос electrictoolbox.com/php-catch-multiple-exception-types . Содержание поста скопировано прямо из статьи:

Пример исключений

Вот некоторые примеры исключений, которые были определены для целей этого примера:

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

Обработка нескольких исключений

Это очень просто - для каждого типа исключения может быть блок catch:

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
}

Если выдается исключение, которое не обрабатывается ни одним из других операторов catch, оно будет обрабатываться блоком catch (Exception $ e). Это не обязательно должно быть последним.


3
Проблема с этим методом возникает, когда вам нужно выполнить один и тот же код для двух или более разных исключений.
Парзифаль

Это было получено из Electric Toolbox . Редактирование поста, чтобы дать кредит.
Кайла

С PHP 7.x вам нужно catch (Throwable $e)отлавливать все исключения. Смотрите также: php.net/manual/en/class.throwable.php
Микко Ранталайнен,

21

В качестве дополнения к принятому ответу вы можете переключить тип исключения, в результате чего будет получен шаблон, похожий на оригинальный пример:

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
используйте несколько уловов вместо этого решения.
Алехандро Морено

5

Вот разумная альтернатива, если у вас нет контроля над определением исключений. Используйте имя переменной исключения, чтобы классифицировать исключения, когда они перехвачены. Затем проверьте наличие переменной исключения после блока 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);
}

Этот несколько странный подход, вероятно, имеет смысл только в том случае, если между реализациями catch-блоков много дублирования.


3

Помимо провала, также можно перешагнуть, используя goto . Это очень полезно, если вы хотите увидеть, как мир горит.

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

Отличный способ заключается в использовании set_exception_handler.

Предупреждение!!! с PHP 7 вы можете получить белый экран смерти за фатальные ошибки. Например, если вы вызываете метод для необъекта, вы обычно получаете его Fatal error: Call to a member function your_method() on nullи ожидаете увидеть это, если включено сообщение об ошибке.

Вышеуказанная ошибка НЕ ​​будет обнаружена catch(Exception $e). Вышеуказанная ошибка НЕ ​​вызовет какой-либо пользовательский обработчик ошибок, установленный set_error_handler.

Вы должны использовать catch(Error $e){ }для отлова ошибок в PHP7. , Это может помочь:

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

1
... или вы могли бы просто написать catch (Throwable $e) { ... }и покончить с этим. Смотрите также: php.net/manual/en/class.throwable.php
Микко Ранталайнен,

0

Другой вариант, не указанный здесь, - использовать codeатрибут исключения, так что вы можете сделать что-то вроде этого:

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

Я не понизил голос, но, может быть, пуристы ООП недовольны тем, что вы не создали новые классы исключений, используя extends \Exception?
клавиатураSmasher

Понял. В этом весь смысл моего решения: вам не нужно создавать произвольные классы только для того, чтобы установить пространство имен для выдачи конкретного исключения. Я уверен, что именно поэтому они добавили возможность указать код.
Майк Перселл,

Я тоже не понизил голос, но я думаю, что даунвотеры считают, что это не отвечает на вопрос. Я бы предложил начать ответ с чего-то, что давало бы понять читателю, что вы поняли вопрос и по-прежнему хотите предложить совершенно другой способ для потока кода. Этот ответ на самом деле не отвечает «как поймать несколько типов исключений », а скорее «как обработать несколько разных причин для исключения».
Микко Ранталайнен

0

Хм, есть много решений, написанных для php версии ниже 7.1.

Вот еще один простой способ для тех, кто не хочет перехватывать все исключения и не может создавать общие интерфейсы:

<?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!
}
?>
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.