Использование исключений в Symfony 2

в 9:53, , рубрики: exception, php, symfony, symfony 2

Не так давно общался с коллегой на тему использования исключений в Symfony. Краткий обзор информации в интернете и оф. сайте фреймворка показал, что тема в документации раскрыта не слишком глубоко и ряд возможностей системы остается за кадром. Этим постом я решил немного восполнить этот пробел и поделиться тем, что удалось найти, покопавшись в коде фреймворка.

Как работает «отлов» исключений

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

В этом посте нет никаких откровений для тех, кто копался в коде symfony 2.х и его компонентов, по этому таким читателям можно смело не пропускать этот пост.

Кто «ловит» исключения

Если разработчик не стал ловить исключения сам, то их поймает HttpKernel, а именно метод handleException. По сути, этот метод только запускает через диспетчер событие, оповещающий все заинтересованные сервисы о наступлении исключительной ситуации. Дальше мы посмотрим, как фреймворк обрабатывает некоторые виды исключений.

Что происходит когда исключения случаются в консольных командах? примерно то же самое. Исключение ловится в классе Application, после чего отправляются 2 события: ConsoleEvents::EXCEPTION и ConsoleEvents::TERMINATE. Принцип обработки тот же, что и в web. Исключения порождают события, диспетчер их передает, слушатели делают свое дело. Например можно откатить транзакции, в случае ошибки в процессе выполнения команды и не смешивать обработку ошибок с основным кодом команды.

Встроенные обработчики исключений

Из коробки Symfony 2 умеет обрабатывать исключения безопасности ( AuthenticationException, AccessDeniedException, LogoutException ) и, исключения, реализовавшие HttpExceptionInterface.

Подробнее об системных исключениях

HttpExceptionInterface-совместимые — исключения позволяют скорректировать код статуса http-ответа и его заголовки, это может быть полезно, чтобы в исключительной ситуации сделать редирект или уточнить код ошибки.

Интересно, что обработка этого исключения делается не через EventDispatcher, а зашита непосредственно в HttpKernel…

        $response = $event->getResponse();

        // the developer asked for a specific status code
        if ($response->headers->has('X-Status-Code')) {
            $response->setStatusCode($response->headers->get('X-Status-Code'));

            $response->headers->remove('X-Status-Code');
        } elseif (!$response->isClientError() && !$response->isServerError() && !$response->isRedirect()) {
            // ensure that we actually have an error response
            if ($e instanceof HttpExceptionInterface) {
                // keep the HTTP status code and headers
                $response->setStatusCode($e->getStatusCode());
                $response->headers->add($e->getHeaders());
            } else {
                $response->setStatusCode(500);
            }
        }

… таким образом, в случае если вы бросите такое исключение, то вместо кода 500 вы можете передать клиенту что-нибудь типа 402, что может быть полезно при написании различных API, так же можно позволяет инициировать редирект практически из любой точки кода, что конечно удобно, но не всегда хорошо и правильно. Вряд ли кто-то оценит инициирование редиректа, скажем из TwigExtension или бизнес-логики в модели. Важно что на содержание ответа сервера данный тип исключений повлиять не может, только на заголовок.

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

AuthenticationException — родитель множества других исключений разного типа, обрабатываемых в SecurityBundle и Security Component. Выбрасывать это исключение следует, если вы обнаружите, что действия, доступные только зарегистрированным пользователям пытается выполнить пользователь, не вошедший в систему.

Обработчик исключения реагирует на него попыткой инициировать аутентификацию. В самом распространенном случае — отдаст пользователю форму авторизации, вместо запрошенной страницы.
Стоит отметить, что если в вашем проекте правильно настроен слой безопасности, то самомтоятельно генерировать это исключение или его потомков вам вряд-ли придется. SecurityBundle справится сама и не пустит кого-не-надо куда-не-надо.

AccessDeniedException — это исключение похоже на AuthenticationException, но оно должно случаться в том случае, если права пользователя не соответсвуют требованиям. Например непривилегированный пользователь пытается попасть в панель администрирования. Если исключение случится — пользователь увидит страницу с сообщением о недостатке прав(автор надеется, что не родную — симфонийскую). Так же по средствам этого исключения можно отправить пользователя на повторную авторизацию, если он, например, авторизован через remember-cookie и вы хотите лишний раз убедиться, что он это он.
Как и предыдущее исключение, это вам вряд-ли придется выбрасывать самому, лучше доверить это симфони, но все же мне кажется, что случаи, когда разработчику может пригодится такой эксепшен, более распространены.

LogoutException — это исключение во время логаута. Например, неправильный токен формы или какие-либо проблемы с сессией. Обработчик этого исключения не делает ничего, только пишет в лог.

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

Вместо заключения

Разработчик, как всегда, может очень многое. Добавляя собственные типы исключений и слушателей для них, можно добиться практически любого желаемого поведения. Например ошибки в бизнес логике, которые в определенном окружении просто будут записаны в лог, в другом могут привести к переброске пользователя, скажем, в панель логирования, или к редактору объекта, с целью устранения в нем каких-либо недостатков.
Автор такого, если честно, пока не практиковал, но задачи, где это может потребоваться волне может представить.

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

Автор: mihard

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js