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

Исключения – один из немногих примеров удачного использования наследования. В этом уроке мы научимся создавать свои исключения и перехватывать их.

Обычно исключения используются так. Ближе к началу программы стоит конструкция try/catch, которая ловит исключения и показывает пользователю адекватное сообщение:

<?php

try {
  doSomethingDangerous();
} catch (\Exception $e) {
  echo 'Something happens';
}

Но как понять что случилось? Иногда это важно. Разные ошибки могут приводить к разному поведению программы. Кроме того, не все ошибки требуют обработки в текущем месте программы.

Разделять ошибки можно с помощью разных классов (или интерфейсов) наследующихся от класса Exception, который в свою очередь реализует интерфейс Throwable. По техническим причинам, реализовать этот интерфейс напрямую нельзя, поэтому остается только один путь – наследование.

<?php

namespace App;

class MyException extends \Exception {}

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

Теперь посмотрим как этим можно воспользоваться:

<?php

try {
    // какой-то код
} catch (MyException $e) { // Проверка instanceof
    // делаем что-нибудь одно
} catch (\Exception $e) {
    // делаем что-нибудь другое
}

Конструкция try/catch работает очень похоже на switch, но только для исключений. С ее помощью можно описать обработку каждого типа исключений добавив новый блок catch. Во время срабатывания исключения, PHP проверяет каждый блок catch на instanceof начиная с верхнего. Поэтому порядок блоков catch имеет важное значение.

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

interface Throwable
  |- Error implements Throwable
      |- ArithmeticError extends Error
          |- DivisionByZeroError extends ArithmeticError
      |- AssertionError extends Error
      |- ParseError extends Error
      |- TypeError extends Error
          |- ArgumentCountError extends TypeError
  |- Exception implements Throwable
      |- ClosedGeneratorException extends Exception
      |- DOMException extends Exception
      |- ErrorException extends Exception
      |- IntlException extends Exception
      |- LogicException extends Exception
          |- BadFunctionCallException extends LogicException
              |- BadMethodCallException extends BadFunctionCallException
          |- DomainException extends LogicException
          |- InvalidArgumentException extends LogicException
          |- LengthException extends LogicException
          |- OutOfRangeException extends LogicException
      |- PharException extends Exception
      |- ReflectionException extends Exception
      |- RuntimeException extends Exception
          |- OutOfBoundsException extends RuntimeException
          |- OverflowException extends RuntimeException
          |- PDOException extends RuntimeException
          |- RangeException extends RuntimeException
          |- UnderflowException extends RuntimeException
          |- UnexpectedValueException extends RuntimeException

Перехват любого базового исключения, автоматически влечет за собой перехват всех наследников текущего класса. Например если использовать в catch RuntimeException, то этот блок catch поймает все ошибки, входящие в иерархию RuntimeException.

В PHP есть негласное правило о том, как работать с иерархиями исключений. Любая программа, должна определять свое собственное высокоуровневое исключение, которое наследуется от RuntimeException. Все остальные исключения библиотеки наследуются от него. Такой подход позволяет изолировать обработку ошибок конкретной библиотеки буквально одним блоком catch. Например в http-клиенте guzzle, базовое исключение TransferException. Это значит что мы можем вычленить среди всех ошибок, ошибки этого клиента:

<?php

try {
    $client = new \GuzzleHttp\Client();
    $response = $client->get('https://ru.hexlet.io');
} catch (Guzzle\TransferException) {
    // Сюда попадут все ошибки библиотеки guzzle
    }

Finally

В некоторых ситуациях бывает нужно выполнять обработку независимо от того, возникло исключение или нет. Используя только try/catch, эту задачу нельзя выполнить без дублирования. Придётся размещать код как после всей конструкции try/catch, так и в каждом блоке catch.

Это привело к тому, что саму конструкцию расширили добавив в нее блок finally. Этот блок вызывается в самом конце и в любом случае:

<?php

try {
    // какой-то код
} catch (MyException $e) {
    // делаем что-нибудь одно
} finally {
    // вызовется в любом случае
}
Мы учим программированию с нуля до стажировки и работы. Попробуйте наш бесплатный курс «Введение в программирование» или полные программы обучения по Node, PHP, Python и Java.

Хекслет

Подробнее о том, почему наше обучение работает →