В PHP, начиная с версии 5.5, а в JavaScript с редакции 1.4 есть поддержка обработки с исключений с помощью конструкций try…catch…finally.
Для многих остается загадкой, в чем же функция этого «finally«. В документации написано, что код в блоке finally будет выполнен независимо от того, было ли вызвано исключение в блоке try. Напомню, что код в блоке try выполняется до конца блока или до тех пор, пока не будет вызвано исключение, а в этом случае выполняется код в блоке catch. То есть, на первый взгляд блок finally избыточен, ведь если мы хотим, чтобы код выполнился в любом случае после try и catch, достаточно просто разместить этот код после них. Во многих случаях это действительно так, но есть несколько случаев, когда finally может быть полезен.
Важно пояснить, что finally выполнится в любом случае перед выходом из конструкции try/catch/finally, то есть это пригодится для обработки ситуаций, когда блок catch не отработал, а исключение возникло. Например, если блока catch нет вообще или в подобной ситуации, когда ожидается исключение определенного вида, а вызывается другое:
try { echo "В try вызвано исключение\n"; throw new StrangeException(); } catch (SomeException $e) { echo "Вызвано исключение SomeException \n"; } catch (AnotherException $e) { echo "Вызвано исключение AnotherException \n"; } finally { echo "Этот блок кода выполнится всегда\n"; }
То есть мы можем как-то среагировать на такую кажущуюся «неуправляемой» ситуацию перед тем, как исключение уйдет на верхний уровень.
В случае вложенных исключений, если блоки finally в них присутствуют, то код будет выполнен во всех таких блоках последовательно, начиная с самого «глубокого» уровня. Например:
function canThrowException() { try { echo "В try вызвано исключение\n"; throw new StrangeException("Что-то случилось!"); } catch (SomeException $e) { echo "Exception: " . $e->getMessage() . "\n"; } finally { echo "Этот блок в функции canThrowException отработает сначала!\n"; } } try { canThrowException(); } catch (StrangeException $e) { echo "Внешнее исключение: " . $e->getMessage() . "\n"; } finally { echo "Это блок кода также отработает, но в конце\n"; }
Блоки finally могут быть полезны при соединении с базой данных или со сторонним API, когда, например, у вас может быть многоэтапная обработка исключений, но при этом важно при любом исходе закрыть соединение или какой-то ресурс — такие процедуры безусловного «завершения» или «очистки» как раз удобно размещать в finally.
Особое внимание при работе с try/catch/finally надо уделить оператору return внутри блоков. Поскольку finally выполнится в любом случае до выхода из try/catch, то любые return в этих блоках выполнятся тоже только после finally, и если finally также будет содержать return, то окончательно возвращаемое значение будет именно тем, что указано после return в finally. При этом, если выход из try/catch/finally произойдет по return, то исключение не передастся дальше, а продолжится нормальный ход выполнения программы, как если бы исключение было успешно обработано.
Как уже было отмечено, в finally можно поместить операции «завершения» чего-либо. Например, это очень удобно делать для закрытия файловых потоков или «подчистку» после обращений к внешним API.
Например, для закрытия потоков хороший пример написан в статье Frank de Jonge:
Код без с использования finally:
function flupper($location) { $handle = fopen($location, 'r+'); $firstLine = fgets($handle, 1024); if ($firstLine === false) { fclose($firstLine); return false; } rewind($handle); $uppedFirstLine = strtoupper($firstLine); if (false === fwrite($handle, $uppedFirstLine)) { fclose($firstLine); return false; } fclose($firstLine); return $uppedFirstLine; }
Код с использованием finally:
function flupper($location) { try { $handle = fopen($location, 'r+'); $firstLine = fgets($handle, 1024); if ($firstLine === false) { return false; } rewind($handle); $uppedFirstLine = strtoupper($firstLine); if (false === fwrite($handle, $uppedFirstLine)) { return false; } return $uppedFirstLine; } finally { fclose($handle); } }
То есть мы избавились от копипасты закрытия потока, и проводим эту рутинную операцию в одном месте.
Вдогонку… Понял. Блок catch (\Throwable $e) исполнится только при условии выброса исключения. А finally — в любом случае.
А вариант написать нужный нам код просто под низом конструкции try/catch не будет выполняться, если было выброшено исключение. Таким образом, если нам нужно что-то сделать как при успешном выполнении try-кода, так и при выбросе исключения, единственный выход, чтобы сделать это красиво — блок finally.
как-то так, да )
не совсем так: если сделать catch(Throwable), и при этом будет выброшено любое исключение, то код под блоком выполнится и в этом случае finally можно не использовать. Или наоборот, использовать finally без catch.
а вот если нужно поймать какое-то определенное исключение и впихнуть особенную логику его обработки, а в конце выполнить какое-то действие (запись в бд или, как у автора, закрыть соединение), то finally будет полезно.
Вместо
«`php
function some() {
throw new \DomainException(‘Specified exception’);
}
try {
some();
$model->status = ‘successful’;
$model->save();
} catch (\DomainException $exception) {
$model->status = ‘failed’;
$model->message = $exception->getMessage();
$model->save();
}
«`
Можно использовать такую конструкцию:
«`php
try {
some();
$model->status = ‘successful’;
} catch (\DomainException $exception) {
$model->status = ‘failed’;
$model->message = $exception->getMessage();
} finally {
$model->save();
}
«`
В первом варианте мы сохраняем модель только в случае если не было исключений или поймали исключение DomainException.
А во втором варианте мы сохраняем модель в любом случае.
Еще стоит учитывать нюанс поимки исключений. Не помню с какой версии php они разделились на глобальный Throwable и Exception
«`php
try {
$result = 2 / 0;
} catch (\Exception $exception) {
echo ‘Деление на ноль’;
}
«`
эта штука НЕ сработает, будет выведен warning. А вот если вместо Exception написать Throwable, то исключение поймается
«то есть это пригодится для обработки ситуаций, когда блок catch не отработал, а исключение возникло»
Почему нельзя finally заменить на catch (\Throwable $e) ? Который точно подхватит все возможно оставшиеся не перехваченными исключения в предыдущих блоках catch.
finally отрабатывает даже если исключения не было