Ловля кількох типів винятків в одному блоці вилову


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 спробує знайти перший відповідний блок лову.

Це означає, що ви могли також мати

class CError extends LetterError {}

з якою вам потрібно поводитися інакше, ніж AErrorабо BError, щоб ваша заява на вилов виглядала б так:

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

Використання OOP, коли мова йде про винятки, дуже потужне. Використовуючи речі , як 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);
}

Таким чином, у вас все ще є єдине місце розташування коду, яке ви повинні змінити, якщо ваш механізм обробки винятків потребує змін, і ви працюєте в загальних конструкціях OOP.


4
Ось ще один голос за це як правильну відповідь. На жаль, такі речі, як сказане у прийнятій відповіді, і той факт, що він прийнятий як правильна відповідь, - це те, що робить PHP безумством, яким воно є.
borfast

Це має бути прийнятою відповіддю. Хоча, це передбачає, що ви можете змінювати файли. 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;
    }
}

Але , мабуть, було б краще використовувати декілька блоків лову, як описано у вищезгаданій відповіді .

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

6
Ось чого я боявся. Збирати їх разом і тестувати тип було б добре, якби було багато типів помилок, з якими потрібно було працювати разом, але лише для 2-х, наприклад, у моєму випадку, ловити їх окремо, мабуть, чистіше. Дякую!
Домінік Гурто

3
@DominicGurto: Так, я б пішов і з цим :) Я б більше переймався ставленням PHP до finallyзаяви. ;)
alex

7
Але не забувайте, що це ловить ВСІ винятки, тому має бути щось на зразок, ... } else { throw($e); }якщо воно не відповідає двом. Вибачте за можливо неправильний синтаксис, не бачив php деякий час.
Далібор Філус

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

4
Пропонувати рішення, яке їсть усі ваші інші Винятки, взагалі не слід було приймати ...
Stivni

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

Поводження з кількома винятками

Це дуже просто - може бути блок вилову для кожного типу винятків, який можна кинути:

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 (Виняток $ e). Це не обов'язково має бути останнім.


3
Проблема з цим методом виникає, коли вам доведеться виконати один і той же код для двох або більше різних винятків.
Парцифал

Це було отримано з Електричної панелі інструментів . Редагування публікації для надання кредиту.
Кайла

З 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

Ось розумна альтернатива, якщо ви не маєте контролю над визначенням винятків. Використовуйте ім’я змінної винятку, щоб класифікувати винятки, коли вони потрапляють. Потім перевірте наявність змінної виключення після блоку спробувати.

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

Цей дещо дивний підхід, мабуть, вартий лише тоді, коли існує багато дублювання між реалізацією блоку лову.


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

Я не виступав з заявою, але, можливо, пуристи OOP розлючені, що ви не створили нові класи виключень, використовуючи extends \Exception?
клавіатураМошер

Зрозумів. У цьому і полягає суть мого рішення, вам не потрібно створювати довільні класи просто для створення простору імен, щоб викинути певний виняток. Я впевнений, що саме тому вони додали можливість вказувати код.
Майк Перселл

Я також не виступав проти, але я думаю, що користувачі вважають, що це не відповідає на питання. Я б запропонував почати відповідь з чогось, що дає зрозуміти читачеві, що ви зрозуміли питання, і ви все ще хочете запропонувати зовсім інший спосіб для потоку коду. Ця відповідь справді не відповідає "як зловити кілька типів виключень ", а "як поводитися з кількома різними причинами для виключення".
Мікко Ранталайнен

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.