Edit this page
How to Customize Error Pages
In Symfony applications, all errors are treated as exceptions, no matter if they
are a 404 Not Found error or a fatal error triggered by throwing some exception
in your code.
In the development environment,
Symfony catches all the exceptions and displays a special exception page
with lots of debug information to help you discover the root problem:
Since these pages contain a lot of sensitive internal information, Symfony won’t
display them in the production environment. Instead, it’ll show a minimal and
generic error page:
Error pages for the production environment can be customized in different ways
depending on your needs:
- If you only want to change the contents and styles of the error pages to match
the rest of your application, override the default error templates; - If you want to change the contents of non-HTML error output,
create a new normalizer; - If you also want to tweak the logic used by Symfony to generate error pages,
override the default error controller; - If you need total control of exception handling to run your own logic
use the kernel.exception event.
Overriding the Default Error Templates
You can use the built-in Twig error renderer to override the default error
templates. Both the TwigBundle and TwigBridge need to be installed for this. Run
this command to ensure both are installed:
When the error page loads, TwigErrorRenderer
is used to render a Twig template to show the user.
This renderer uses the HTTP status code and the following
logic to determine the template filename:
- Look for a template for the given status code (like
error500.html.twig
); - If the previous template doesn’t exist, discard the status code and look for
a generic error template (error.html.twig
).
To override these templates, rely on the standard Symfony method for
overriding templates that live inside a bundle and
put them in the templates/bundles/TwigBundle/Exception/
directory.
A typical project that returns HTML pages might look like this:
Example 404 Error Template
To override the 404 error template for HTML pages, create a new
error404.html.twig
template located at templates/bundles/TwigBundle/Exception/
:
In case you need them, the TwigErrorRenderer
passes some information to
the error template via the status_code
and status_text
variables that
store the HTTP status code and message respectively.
Tip
You can customize the status code of an exception by implementing
HttpExceptionInterface
and its required getStatusCode()
method. Otherwise, the status_code
will default to 500
.
Additionally you have access to the Exception with exception
, which for example
allows you to output the stack trace using {{ exception.traceAsString }}
or
access any other method on the object. You should be careful with this though,
as this is very likely to expose sensitive data.
Tip
PHP errors are turned into exceptions as well by default, so you can also
access these error details using exception
.
Security & 404 Pages
Due to the order of how routing and security are loaded, security information will
not be available on your 404 pages. This means that it will appear as if your
user is logged out on the 404 page (it will work while testing, but not on production).
Testing Error Pages during Development
While you’re in the development environment, Symfony shows the big exception
page instead of your shiny new customized error page. So, how can you see
what it looks like and debug it?
Fortunately, the default ErrorController
allows you to preview your
error pages during development.
To use this feature, you need to load some special routes provided by FrameworkBundle
(if the application uses Symfony Flex they are loaded
automatically when installing symfony/framework-bundle
):
- YAML
- XML
- PHP
With this route added, you can use URLs like these to preview the error page
for a given status code as HTML or for a given status code and format (you might
need to replace http://localhost/
by the host used in your local setup):
http://localhost/_error/{statusCode}
for HTMLhttp://localhost/_error/{statusCode}.{format}
for any other format
Overriding Error output for non-HTML formats
To override non-HTML error output, the Serializer component needs to be installed.
The Serializer component has a built-in FlattenException
normalizer
(ProblemNormalizer) and
JSON/XML/CSV/YAML encoders. When your application throws an exception, Symfony
can output it in one of those formats. If you want to change the output
contents, create a new Normalizer that supports the FlattenException
input:
Overriding the Default ErrorController
If you need a little more flexibility beyond just overriding the template,
then you can change the controller that renders the error page. For example,
you might need to pass some additional variables into your template.
To do this, create a new controller anywhere in your application and set
the framework.error_controller
configuration option to point to it:
- YAML
- XML
- PHP
The ErrorListener
class used by the FrameworkBundle as a listener of the kernel.exception
event creates
the request that will be dispatched to your controller. In addition, your controller
will be passed two parameters:
exception
- The original Throwable instance being handled.
logger
-
A DebugLoggerInterface
instance which may benull
in some circumstances.
Working with the kernel.exception
Event
When an exception is thrown, the HttpKernel
class catches it and dispatches a kernel.exception
event. This gives you the
power to convert the exception into a Response
in a few different ways.
Working with this event is actually much more powerful than what has been explained
before, but also requires a thorough understanding of Symfony internals. Suppose
that your code throws specialized exceptions with a particular meaning to your
application domain.
Writing your own event listener
for the kernel.exception
event allows you to have a closer look at the exception
and take different actions depending on it. Those actions might include logging
the exception, redirecting the user to another page or rendering specialized
error pages.
Note
If your listener calls setThrowable()
on the
ExceptionEvent
event, propagation will be stopped and the response will be sent to
the client.
This approach allows you to create centralized and layered error handling:
instead of catching (and handling) the same exceptions in various controllers
time and again, you can have just one (or several) listeners deal with them.
Tip
See ExceptionListener
class code for a real example of an advanced listener of this type. This
listener handles various security-related exceptions that are thrown in
your application (like AccessDeniedException)
and takes measures like redirecting the user to the login page, logging them
out and other things.
Как настроить страницы ошибок
Дата обновления перевода: 2023-01-18
Как настроить страницы ошибок
В приложениях Symfony все ошибки воспринимаются, как исключения, вне зависимости
от того, являются ли они простой ошибкой 404 «Не найдено» или фатальной ошибкой,
запущенной вызовом какого-то исключения в вашем коде.
В окружении разработки, Symfony ловит все
исключения и отображаеть специальную страницу исключений со множеством информации
по отладке, чтобы помочь вам быстро обнаружить основную проблему:
Так как эти страницы содержат много чувствительной внутренней информации, Symfony
не будет отображать её в окружении производства. Вместо этого, она покажет простую
и общую ошибку страница ошибки:
Страницы ошибок в окружении производства можно настроить разными способами, в
зависимости от ваших потребностей:
- Если вы просто хотите изменить содержание и стили страниц ошибок так, чтобы
они совпадали с остальным вашим приложением,
переопределите шаблоны ошибок по умолчанию ; - Если вы хотите изменить содержание вывода ошибки не в HTML,
создайте новый нормализатор ; - Если вы также хотите настроить логику, используемую Symfony для генерирования ваших
страниц ошибок, то переопределите контроллер ошибок по умолчанию ; - Если вам нужен полный контроль над работой с исключениями, выполните вашу
собственную логику — используйте событие the kernel.exception .
Переопределение шаблонов ошибок по умолчанию
Вы можете использовать встроенное средство отображения ошибок Twig, чтобы переопределять
шаблоны ошибок по умолчанию. Для этого должны быть установлены как TwigBundle, так и TwigBridge.
Выполните эту команду, чтобы убедиться, что они оба установлены:
Когда загружается страница ошибки, для отображения шаблона twig и демонастрации
пользователю, используется TwigErrorRenderer.
Этот отображатель использует статус-код HTTP, формат запроса и следующую логику,
чтобы определить имя файла шаблона:
- Ищет шаблон для заданного статус-кода (вроде
error500.html.twig
); - Если предыдущий шаблон не существует, отбросьте статус-код и ищет общий
шаблон ошибок (error.html.twig
).
Чтобы переопределить эти шаблоны, просто положитесь на стандартный метод Symfony
для пеоепределения шаблонов, живущих внутри пакета:
поместите их в каталоге templates/bundles/TwigBundle/Exception/
.
Типичный проект, возвращающий страницы HTML и JSON, может выглядеть так:
Пример шаблона ошибки 404
Чтобы переопределить шаблон ошибки 404 для HTML-страниц, создайте новый шаблон
error404.html.twig
, находящийся в templates/bundles/TwigBundle/Exception/
:
Если они вам понадобятся, TwigErrorRenderer
передаёт некоторую информацию в шаблон
ошибок через переменные status_code
и status_text
, которые хранят HTTP статус-код
и сообщение соотвественно.
Tip
Вы можете настроить статус-код, реализовав
HttpExceptionInterface
и его обязательный метод getStatusCode()
. В обратном случае, status_code
по умолчанию будет500
.
Кроме этого у вас есть доступ к Исключениям с помощью exception
, который, к примеру,
позволяет вам выводить отслеживание стека, используя {{ exception.traceAsString }}
, или
получить доступ к любому другому методу объекта. Однако будьте аккуратны, так как это
с большой вероятностью может обнажить кофиденциальную информацию.
Tip
PHP-ошибки также по умолчанию превращаются в исключения, поэтому вы также можете
получить доступ к деталям этих ошибок, используя exception
.
Безопасность и страницы 404
В связи с порядком, в котором загружаются маршрутизация и безопасность, конфиденциальна информация
не будет доступна на ваших страницах 404. Это означает, что будет казаться, что ваш пользователь
не залогинен в систему на странице 404 (это будет работать при тестировании, но не в производстве).
Тестирование страниц ошибок во время разработки
В то время, как вы находитесь в окружении разработки, Symfony отображает
большую страницу исключений вместо вашей новой блестящей страницы ошибок.
Так как вам увидеть, как она выглядит изнутри и отладить её?
К счастью, ExceptionController
по умолчанию разрешает вам предпросмотр
вашей страницы ошибки во время разработки.
Чтобы использовать эту функцию, вам загрузить специальные маршруты, предоставленные
TwigBundle (если приложение использует Symfony Flex, то они
загружаются автоматически при установке symfony/framework-bundle
):
- YAML
- XML
- PHP
C добавлением этого маршрута, вы можете использовать такие URL для предпросмотра
страницы ошибки для заданного статус-кода в виде HTML или для заданного статус-кода
и формата (вам может понадобиться заменить http://localhost/
на хостинг, используемый
в ваших локальных настройках):
http://localhost/_error/{statusCode}
для HTMLhttp://localhost/_error/{statusCode}.{format}
для любого другого формата
Переопределение вывода ошибок для не-HTML форматов
Чтобы переопределить не-HTML вывод ошибки, необходимо установить компонент Serializer.
Компонент Serializer имеет встроенный нормализатор FlattenException
(ProblemNormalizer) и кодировщики
JSON/XML/CSV/YAML. Когда ваше приложение вызывает исключение, Symfony может вывести его
в один из этих форматов. Если вы хоите изменить содержание вывода, создайте Нормализатор,
который поддерживает ввод FlattenException
:
Переопределение ExceptionController по умолчаию
Если вам нужно немного больше гибкости кроме простого переопределения шаблона,
то вы можете изменить контроллер, отображающий странцу ошибки. Например, вам
может быть нужо передать дополнительные переменные в ваш шаблон.
Чтобы сделать это, просто создайте новый контролер где угодно в вашем приложении,
и установите опцию конфигурации framework.error_controller ,
чтобы указать на неё:
- YAML
- XML
- PHP
Класс ExceptionListener,
используемый TwigBundle в качестве слушателя события kernel.exception
, создаёт
запрос, который будет развёрнут в вашем контроллере. В дополнение, вашему контроллеру
будут переданы два параметра:
exception
- Обработка первоначального экземпляра Throwable.
logger
-
Экемпляр DebugLoggerInterface,
который может в некоторых случаях бытьnull
.
Tip
Предпросмотр страницы ошибки также работает
с вашими собственными контроллерами, нвстроенными как угодно.
Работа с событием kernel.exception
Когда вызывается исключение, класс HttpKernel
ловит его и развёртывает событие kernel.exception
. Это даёт вам возможность
конвертировать исключение в Response
несколькими разными способами.
Работа с этим событием на самом деле значительн более мощная, чем объяснялось раньше,
но также требует тщательного понимания внутренних процессов Symfony. Представьте, что
ваш код вызывает специальные исключения с конкретным значением в вашем домене приложения.
Написание вашего собственного слушателя событий для события
kernel.exception
позволяет вам ближе рассмотретьисключение и предпринять по отношению
к нему разные действия. Эти действия могут включать в себя запись лога исключения,
перенаправление пользователя на другую страницу или отображение специальных страниц ошибки.
Note
Если ваш слушатель вызывает setResponse()
в событии
ExceptionEvent,
распространение будет остановлено и клиенту будет отправлен ответ.
Этот подход позволяет вам создавать централизованную и многослойную обработку
ошибок: вместо того, чтобы ловить (и обрабатывать) одни и те же исключения в
разных контроллерах снова и снова, вы просто можете иметь одного (или нескольких)
слушателей, чтобы разбираться с ними.
Tip
Смотрите код класса ExceptionListener
для реального примера продвинутого слушателя такого типа. Этот слушатель обрабатывает
различные исключения, связанные с безопасностью, которые вызываются в вашем приложении
(как AccessDeniedException) и
предпринимает действия вроде перенаправления пользователей на страницу входа, выполняет
их вход в систему и др.
.. index:: single: Controller; Customize error pages single: Error pages
How to Customize Error Pages
In Symfony applications, all errors are treated as exceptions, no matter if they
are just a 404 Not Found error or a fatal error triggered by throwing some
exception in your code.
If your app has the TwigBundle installed, a special controller handles these
exceptions. This controller displays debug information for errors and allows to
customize error pages, so run this command to make sure the bundle is installed:
$ composer require twig
In the :ref:`development environment <configuration-environments>`,
Symfony catches all the exceptions and displays a special exception page
with lots of debug information to help you discover the root problem:
Since these pages contain a lot of sensitive internal information, Symfony won’t
display them in the production environment. Instead, it’ll show a simple and
generic error page:
Error pages for the production environment can be customized in different ways
depending on your needs:
- If you just want to change the contents and styles of the error pages to match
the rest of your application, :ref:`override the default error templates <use-default-exception-controller>`; - If you also want to tweak the logic used by Symfony to generate error pages,
:ref:`override the default exception controller <custom-exception-controller>`; - If you need total control of exception handling to execute your own logic
:ref:`use the kernel.exception event <use-kernel-exception-event>`.
Overriding the Default Error Templates
When the error page loads, an internal :class:`Symfony\Bundle\TwigBundle\Controller\ExceptionController`
is used to render a Twig template to show the user.
This controller uses the HTTP status code, the request format and the following
logic to determine the template filename:
- Look for a template for the given format and status code (like
error404.json.twig
orerror500.html.twig
); - If the previous template doesn’t exist, discard the status code and look for
a generic template for the given format (likeerror.json.twig
or
error.xml.twig
); - If none of the previous templates exist, fall back to the generic HTML template
(error.html.twig
).
To override these templates, rely on the standard Symfony method for
:ref:`overriding templates that live inside a bundle <override-templates>` and
put them in the templates/bundles/TwigBundle/Exception/
directory.
A typical project that returns HTML and JSON pages might look like this:
templates/ └─ bundles/ └─ TwigBundle/ └─ Exception/ ├─ error404.html.twig ├─ error403.html.twig ├─ error.html.twig # All other HTML errors (including 500) ├─ error404.json.twig ├─ error403.json.twig └─ error.json.twig # All other JSON errors (including 500)
Example 404 Error Template
To override the 404 error template for HTML pages, create a new
error404.html.twig
template located at templates/bundles/TwigBundle/Exception/
:
{# templates/bundles/TwigBundle/Exception/error404.html.twig #} {% extends 'base.html.twig' %} {% block body %} <h1>Page not found</h1> <p> The requested page couldn't be located. Checkout for any URL misspelling or <a href="{{ path('homepage') }}">return to the homepage</a>. </p> {% endblock %}
In case you need them, the ExceptionController
passes some information to
the error template via the status_code
and status_text
variables that
store the HTTP status code and message respectively.
Tip
You can customize the status code by implementing
:class:`Symfony\Component\HttpKernel\Exception\HttpExceptionInterface`
and its required getStatusCode()
method. Otherwise, the status_code
will default to 500
.
Note
The exception pages shown in the development environment can be customized
in the same way as error pages. Create a new exception.html.twig
template
for the standard HTML exception page or exception.json.twig
for the JSON
exception page.
Security & 404 Pages
Due to the order of how routing and security are loaded, security information will
not be available on your 404 pages. This means that it will appear as if your
user is logged out on the 404 page (it will work while testing, but not on production).
Testing Error Pages during Development
While you’re in the development environment, Symfony shows the big exception
page instead of your shiny new customized error page. So, how can you see
what it looks like and debug it?
Fortunately, the default ExceptionController
allows you to preview your
error pages during development.
To use this feature, you need to load some special routes provided by TwigBundle
(if the application uses :ref:`Symfony Flex <symfony-flex>` they are loaded
automatically when installing Twig support):
.. configuration-block:: .. code-block:: yaml # config/routes/dev/twig.yaml _errors: resource: '@TwigBundle/Resources/config/routing/errors.xml' prefix: /_error .. code-block:: xml <!-- config/routes/dev/twig.xml --> <?xml version="1.0" encoding="UTF-8" ?> <routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd"> <import resource="@TwigBundle/Resources/config/routing/errors.xml" prefix="/_error"/> </routes> .. code-block:: php // config/routes/dev/twig.php use SymfonyComponentRoutingLoaderConfiguratorRoutingConfigurator; return function (RoutingConfigurator $routes) { $routes->import('@TwigBundle/Resources/config/routing/errors.xml') ->prefix('/_error') ; };
With this route added, you can use URLs like these to preview the error page
for a given status code as HTML or for a given status code and format.
http://localhost/index.php/_error/{statusCode} http://localhost/index.php/_error/{statusCode}.{format}
Overriding the Default ExceptionController
If you need a little more flexibility beyond just overriding the template,
then you can change the controller that renders the error page. For example,
you might need to pass some additional variables into your template.
To do this, create a new controller anywhere in your application and set
the :ref:`twig.exception_controller <config-twig-exception-controller>`
configuration option to point to it:
.. configuration-block:: .. code-block:: yaml # config/packages/twig.yaml twig: exception_controller: AppControllerExceptionController::showAction .. code-block:: xml <!-- config/packages/twig.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:twig="http://symfony.com/schema/dic/twig" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/twig https://symfony.com/schema/dic/twig/twig-1.0.xsd"> <twig:config> <twig:exception-controller>AppControllerExceptionController::showAction</twig:exception-controller> </twig:config> </container> .. code-block:: php // config/packages/twig.php $container->loadFromExtension('twig', [ 'exception_controller' => 'AppControllerExceptionController::showAction', // ... ]);
The :class:`Symfony\Component\HttpKernel\EventListener\ExceptionListener`
class used by the TwigBundle as a listener of the kernel.exception
event creates
the request that will be dispatched to your controller. In addition, your controller
will be passed two parameters:
exception
- A :class:`\Symfony\Component\Debug\Exception\FlattenException`
instance created from the exception being handled. logger
- A :class:`\Symfony\Component\HttpKernel\Log\DebugLoggerInterface`
instance which may benull
in some circumstances.
Instead of creating a new exception controller from scratch you can also extend
the default :class:`Symfony\Bundle\TwigBundle\Controller\ExceptionController`.
In that case, you might want to override one or both of the showAction()
and
findTemplate()
methods. The latter one locates the template to be used.
Note
In case of extending the
:class:`Symfony\Bundle\TwigBundle\Controller\ExceptionController` you
may configure a service to pass the Twig environment and the debug
flag
to the constructor.
.. configuration-block:: .. code-block:: yaml # config/services.yaml services: _defaults: # ... be sure autowiring is enabled autowire: true # ... AppControllerCustomExceptionController: public: true arguments: $debug: '%kernel.debug%' .. code-block:: xml <!-- config/services.xml --> <?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd"> <services> <!-- ... be sure autowiring is enabled --> <defaults autowire="true"/> <!-- ... --> <service id="AppControllerCustomExceptionController" public="true"> <argument key="$debug">%kernel.debug%</argument> </service> </services> </container> .. code-block:: php // config/services.php use AppControllerCustomExceptionController; $container->autowire(CustomExceptionController::class) ->setArgument('$debug', '%kernel.debug%');
Tip
The :ref:`error page preview <testing-error-pages>` also works for
your own controllers set up this way.
Working with the kernel.exception
Event
When an exception is thrown, the :class:`Symfony\Component\HttpKernel\HttpKernel`
class catches it and dispatches a kernel.exception
event. This gives you the
power to convert the exception into a Response
in a few different ways.
Working with this event is actually much more powerful than what has been explained
before, but also requires a thorough understanding of Symfony internals. Suppose
that your code throws specialized exceptions with a particular meaning to your
application domain.
:doc:`Writing your own event listener </event_dispatcher>`
for the kernel.exception
event allows you to have a closer look at the exception
and take different actions depending on it. Those actions might include logging
the exception, redirecting the user to another page or rendering specialized
error pages.
Note
If your listener calls setResponse()
on the
:class:`Symfony\Component\HttpKernel\Event\ExceptionEvent`,
event, propagation will be stopped and the response will be sent to
the client.
This approach allows you to create centralized and layered error handling:
instead of catching (and handling) the same exceptions in various controllers
time and again, you can have just one (or several) listeners deal with them.
Tip
See :class:`Symfony\Component\Security\Http\Firewall\ExceptionListener`
class code for a real example of an advanced listener of this type. This
listener handles various security-related exceptions that are thrown in
your application (like :class:`Symfony\Component\Security\Core\Exception\AccessDeniedException`)
and takes measures like redirecting the user to the login page, logging them
out and other things.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login
Subscribe
What should the structure of a 404 response from our API look like? It’s obvious: we’ll want to return that same API Problem JSON response format. We want to return this whenever anything goes wrong.
Planning the Response
Start by planning out how the 404 should look with a new test method — test404Exception
. Let’s make a GET request to /api/programmers/fake
and assert the easy part: that the status code is 404. We also know that we want the nice application/problem+json
Content-Type
header, so assert that too:
|
… lines 1 — 5 |
class ProgrammerControllerTest extends ApiTestCase | |
{ | |
|
… lines 8 — 165 |
public function test404Exception() | |
{ | |
$response = $this->client->get(‘/api/programmers/fake’); | |
$this->assertEquals(404, $response->getStatusCode()); | |
$this->assertEquals(‘application/problem+json’, $response->getHeader(‘Content-Type’)); | |
|
… lines 172 — 173 |
} | |
} |
We know the JSON will at least have type
and title
properties. So what would be good values for those? This is a weird situation. Usually, type
conveys what happened. But in this case, the 404 status code already says everything we need to. Using some type
value like not_found
would be fine, but totally redundant.
Look back at the Problem Details Spec. Under «Pre-Defined Problem Types», it says that if the status code is enough, you can set type
to about:blank
. And when you do this, it says that we should set title
to whatever the standard text is for that status code. A 404 would be «Not Found».
Add this to the test: use $this->asserter()->assertResponsePropertyEquals()
to assert that type
is about:blank
. And do this all again to assert that title
is Not Found
:
|
… lines 1 — 165 |
public function test404Exception() | |
{ | |
$response = $this->client->get(‘/api/programmers/fake’); | |
$this->assertEquals(404, $response->getStatusCode()); | |
$this->assertEquals(‘application/problem+json’, $response->getHeader(‘Content-Type’)); | |
$this->asserter()->assertResponsePropertyEquals($response, ‘type’, ‘about:blank’); | |
$this->asserter()->assertResponsePropertyEquals($response, ‘title’, ‘Not Found’); | |
} | |
|
… lines 175 — 176 |
How 404’s Work
A 404 happens whenever we call $this->createNotFoundException()
in a controller. If you hold cmd or ctrl and click that method, you’ll see that this is just a shortcut to throw a special NotFoundHttpException
. And all of the other errors that might happen will ultimately just be different exceptions being thrown from different parts of our app.
The only thing that makes this exception special is that it extends that very-important HttpException class. That’s why throwing this causes a 404 response. But otherwise, it’s equally as exciting as any other exception.
Handling all Errors
In ApiExceptionSubscriber
, we’re only handling ApiException’s so far. But if we handled all exceptions, we could turn everything into the nice format we want.
Reverse the logic on the if
statement and set the $apiProblem
variable inside:
|
… lines 1 — 12 |
class ApiExceptionSubscriber implements EventSubscriberInterface | |
{ | |
public function onKernelException(GetResponseForExceptionEvent $event) | |
{ | |
$e = $event->getException(); | |
if ($e instanceof ApiProblemException) { | |
$apiProblem = $e->getApiProblem(); | |
} else { | |
|
… lines 22 — 26 |
} | |
|
… lines 28 — 35 |
} | |
|
… lines 37 — 43 |
} |
Add an else
. In all other cases, we’ll need to create the ApiProblem
ourselves. The first thing we need to figure out is what status code this exception should have. Create a $statusCode
variable. Here, check if $e
is an instanceof
HttpExceptionInterface
: that special interface that lets an exception control its status code. So if it is, set the status code to $e->getStatusCode()
. Otherwise, we have to assume that it’s 500:
|
… lines 1 — 14 |
public function onKernelException(GetResponseForExceptionEvent $event) | |
{ | |
|
… lines 17 — 18 |
if ($e instanceof ApiProblemException) { | |
|
… line 20 |
} else { | |
$statusCode = $e instanceof HttpExceptionInterface ? $e->getStatusCode() : 500; | |
|
… lines 23 — 26 |
} | |
|
… lines 28 — 35 |
} | |
|
… lines 37 — 45 |
Now use this to create an ApiProblem
: $apiProblem = new ApiProblem()
and pass it the $statusCode
:
|
… lines 1 — 14 |
public function onKernelException(GetResponseForExceptionEvent $event) | |
{ | |
|
… lines 17 — 18 |
if ($e instanceof ApiProblemException) { | |
|
… line 20 |
} else { | |
$statusCode = $e instanceof HttpExceptionInterface ? $e->getStatusCode() : 500; | |
$apiProblem = new ApiProblem( | |
$statusCode | |
); | |
} | |
|
… lines 28 — 35 |
} | |
|
… lines 37 — 45 |
For the type
argument, we could pass about:blank
— that is what we want. But then in ApiProblem
, we’ll need a constant for this, and that constant will need to be mapped to a title. But we actually want the title to be dynamic based on whatever the status code is: 404 is «Not Found», 403 is «Forbidden», etc. So, don’t pass anything for the type
argument. Let’s handle all of this logic inside ApiProblem
itself.
In there, start by making the $type
argument optional:
|
… lines 1 — 8 |
class ApiProblem | |
{ | |
|
… lines 11 — 26 |
public function __construct($statusCode, $type = null) | |
{ | |
|
… lines 29 — 47 |
} | |
|
… lines 49 — 75 |
} |
And if $type
is exactly null
, then set it to about:blank
. Make sure the $this->type = $type
assignment happens after all of this:
|
… lines 1 — 26 |
public function __construct($statusCode, $type = null) | |
{ | |
|
… lines 29 — 30 |
if ($type === null) { | |
$type = ‘about:blank’; | |
|
… lines 35 — 43 |
} | |
$this->type = $type; | |
|
… line 47 |
} | |
|
… lines 49 — 77 |
For $title
, we just need a map from the status code to its official description. Go to Navigate->Class — that’s cmd+o on a Mac. Look for Response
and open the one inside HttpFoundation
. It has a really handy public $statusTexts
map that’s exactly what we want:
|
… lines 1 — 11 |
namespace SymfonyComponentHttpFoundation; | |
|
… lines 13 — 20 |
class Response | |
|
… lines 22 — 124 |
public static $statusTexts = array( | |
|
… lines 126 — 150 |
403 => ‘Forbidden’, | |
404 => ‘Not Found’, | |
|
… lines 153 — 185 |
); | |
|
… lines 187 — 1274 |
} |
Set the $title
variable — but use some if
logic in case we have some weird status code for some reason. If it is in the $statusTexts
array, use it. Otherwise, well, this is kind of a weird situation. Use Unknown Status Code
with a frowny face:
|
… lines 1 — 26 |
public function __construct($statusCode, $type = null) | |
{ | |
|
… lines 29 — 30 |
if ($type === null) { | |
|
… lines 32 — 33 |
$type = ‘about:blank’; | |
$title = isset(Response::$statusTexts[$statusCode]) | |
? Response::$statusTexts[$statusCode] | |
: ‘Unknown status code :(‘; | |
|
… lines 38 — 43 |
} | |
|
… lines 45 — 47 |
} | |
|
… lines 49 — 77 |
If the $type
is set — we’re in the normal case. Move the check up there and add $title = self::$titles[$type]
. After everything, assign $this->title = $title
:
|
… lines 1 — 26 |
public function __construct($statusCode, $type = null) | |
{ | |
|
… lines 29 — 30 |
if ($type === null) { | |
|
… lines 32 — 37 |
} else { | |
if (!isset(self::$titles[$type])) { | |
throw new InvalidArgumentException(‘No title for type ‘.$type); | |
} | |
$title = self::$titles[$type]; | |
} | |
$this->type = $type; | |
$this->title = $title; | |
} | |
|
… lines 49 — 77 |
Now the code we wrote in ApiExceptionSubscriber
should work: a missing $type
tells ApiProblem
to use all the about:blank
stuff. Time to try this: copy the test method name, then run:
./bin/phpunit -c app --filter test404Exception
Aaaand that’s green. It’s so nice when things work.
What we just did is huge. If a 404 exception is thrown anywhere in the system, it’ll map to the nice Api Problem format we want. In fact, if any exception is thrown it ends up with that format. So if your database blows, an exception is thrown. Sure, that’ll map to a 500 status code, but the JSON format will be just like every other error.
В приложениях Symfony все ошибки рассматриваются как исключения Exception, независимо от того, являются ли они просто ошибкой 404 Not Found или фатальной ошибкой, вызванной возникновением некоторого исключения в вашем коде.
В среде разработки Symfony перехватывает все исключения и отображает специальную страницу исключений со множеством отладочной информации, которая поможет вам обнаружить корневую проблему:
Поскольку такие страницы содержат много конфиденциальной внутренней информации, Symfony не будет отображать их в production среде. Вместо этого он покажет простую и общую страницу ошибок:
Страницы ошибок для production среды могут быть настроены по-разному в зависимости от ваших потребностей:
- Если вы просто хотите изменить содержимое и стили страниц ошибок, чтобы они соответствовали остальной части вашего приложения, переопределите шаблоны ошибок по умолчанию;
- Если вы хотите изменить содержимое вывода ошибок, отличных от HTML, создайте новый нормализатор;
- Если вы также хотите настроить логику, используемую Symfony для генерации страниц ошибок, переопределите контроллер ошибок по умолчанию;
- Если вам нужен полный контроль над обработкой исключений для выполнения вашей собственной логики, используйте событие kernel.exception.
Как переопределить шаблон страницы ошибок по умолчанию?
Вы можете использовать встроенный обработчик ошибок Twig, чтобы переопределить шаблоны ошибок по умолчанию. Для этого должны быть установлены как TwigBundle, так и TwigBridge. Запустите эту команду, чтобы убедиться, что оба установлены:
Когда страница ошибки загружается, TwigErrorRenderer используется для визуализации шаблона Twig для отображения пользователю.
Этот рендерер использует код состояния HTTP и следующую логику для определения имени файла шаблона:
- Находится шаблон для данного кода состояния (например, error500.html.twig);
- Если предыдущий шаблон не существует, то игнорируется код состояния и находится общий шаблон ошибки (error.html.twig).
Чтобы переопределить эти шаблоны, используйте стандартный метод Symfony для переопределения шаблонов, которые находятся внутри пакета, и поместите их в каталог templates/bundles/TwigBundle/Exception/.
Типичный проект, который возвращает HTML-страницы, может выглядеть так:
templates/ └─ bundles/ └─ TwigBundle/ └─ Exception/ ├─ error404.html.twig ├─ error403.html.twig └─ error.html.twig # All other HTML errors (including 500)
Пример 404 Шаблона ошибки
Чтобы переопределить шаблон ошибки 404 для страниц HTML, создайте новый шаблон error404.html.twig, расположенный в шаблонах /bundles/TwigBundle/Exception /:
{# templates/bundles/TwigBundle/Exception/error404.html.twig #} {% extends 'base.html.twig' %} {% block body %} <h1>Page not found</h1> <p> The requested page couldn't be located. Checkout for any URL misspelling or <a href="{{ path('homepage') }}">return to the homepage</a>. </p> {% endblock %}
Если вам потребуется, TwigErrorRenderer передает некоторую информацию в шаблон ошибки через переменные status_code и status_text, которые хранят код состояния HTTP и сообщение соответственно.
Вы можете настроить код состояния исключения, реализовав HttpExceptionInterface и его обязательный метод getStatusCode(). В противном случае код_состояния по умолчанию будет равен 500.
Тестирование страниц ошибок в режиме develop.
Пока вы находитесь в среде develop, Symfony показывает большую страницу исключений вместо вашей новой блестящей настраиваемой страницы ошибок. Итак, как вы можете увидеть, как это выглядит и отладить его?
К счастью, ErrorController по умолчанию позволяет вам просматривать страницы ошибок во время разработки.
Чтобы использовать эту функцию, вам нужно загрузить некоторые специальные маршруты, предоставляемые FrameworkBundle (если приложение использует Symfony Flex, они загружаются автоматически при установке symfony/framework-bundle):
# config/routes/dev/framework.yaml _errors: resource: '@FrameworkBundle/Resources/config/routing/errors.xml' prefix: /_error
После добавления этого маршрута вы можете использовать подобные URL-адреса для предварительного просмотра страницы ошибки для заданного кода состояния в виде HTML или для заданного кода состояния и формата.
http://localhost/index.php/_error/{statusCode} http://localhost/index.php/_error/{statusCode}.{format}
Переопределение вывода ошибок для не-HTML форматов.
Чтобы переопределить вывод ошибок, в формате отличном от HTML, необходимо установить компонент Serializer.
composer require serializer
Компонент Serializer имеет встроенный нормализатор FlattenException (ProblemNormalizer) и енкодеры JSON / XML / CSV / YAML. Когда ваше приложение выдает исключение, Symfony может вывести его в одном из этих форматов. Если вы хотите изменить содержимое вывода, создайте новый нормализатор, который поддерживает вход FlattenException:
# src/App/Serializer/MyCustomProblemNormalizer.php namespace AppSerializer; use SymfonyComponentSerializerNormalizerNormalizerInterface; class MyCustomProblemNormalizer implements NormalizerInterface { public function normalize($exception, string $format = null, array $context = []) { return [ 'content' => 'This is my custom problem normalizer.', 'exception'=> [ 'message' => $exception->getMessage(), 'code' => $exception->getStatusCode(), ], ]; } public function supportsNormalization($data, string $format = null) { return $data instanceof FlattenException; } }
Переопределение стандартного ErrorController.
Если вам нужно немного больше гибкости, чем просто переопределение шаблона, вы можете изменить контроллер, отображающий страницу с ошибкой. Например, вам может потребоваться передать некоторые дополнительные переменные в ваш шаблон.
Для этого создайте новый контроллер в любом месте вашего приложения и установите параметр конфигурации framework.error_controller, чтобы он указывал на него:
# config/packages/framework.yaml framework: error_controller: AppControllerErrorController::showAction
Класс ErrorListener, используемый FrameworkBundle в качестве прослушивателя события kernel.exception, создает запрос, который будет отправлен вашему контроллеру. Кроме того, вашему контроллеру будут переданы два параметра:
- exception — Экземпляр FlattenException, созданный из обрабатываемого исключения.
- logger — Экземпляр DebugLoggerInterface, который может быть null в некоторых случаях.
Вместо создания нового контроллера исключений с нуля вы также можете расширить ExceptionController по умолчанию. В этом случае вы можете переопределить один или оба метода showAction() и findTemplate(). Последний находит шаблон для использования.
В случае расширения ExceptionController вы можете настроить сервис для передачи окружения Twig и флага отладки в конструктор.
# config/services.yaml services: _defaults: # ... be sure autowiring is enabled autowire: true # ... AppControllerCustomExceptionController: public: true arguments: $debug: '%kernel.debug%'
Работа с событием kernel.exception
Когда бросается exception, класс HttpKernel перехватывает его и отправляет событие kernel.exception. Это дает вам возможность преобразовать исключение в ответ несколькими различными способами.
Работа с этим событием на самом деле намного эффективнее, чем было объяснено ранее, но также требует глубокого понимания внутренних возможностей Symfony. Предположим, что ваш код выдает специализированные исключения с особым значением в область вашего приложения.
Написание собственного слушателя событий для события kernel.exception позволяет вам ближе рассмотреть исключение и предпринять различные действия в зависимости от него. Эти действия могут включать логирование исключения, редирект пользователя на другую страницу или отображение специализированных страниц с ошибками.
Если ваш слушатель вызывает setResponse() для ExceptionEvent, событие, дальнейшая обработка другими слушателями будет остановлено, и ответ будет отправлен клиенту.
Этот подход позволяет вам создавать централизованную и многоуровневую обработку ошибок: вместо того, чтобы снова и снова перехватывать (и обрабатывать) одни и те же исключения в различных контроллерах, вы можете иметь дело только с одним (или несколькими) слушателями.
I have already created an «AppBundle» bundle and there is a controller folder in which there will be controllers. We register the controller in services config.yml, write the settings and the path to the controller:
# This should be written in symfony 2.2 !!! and higher twig: exception_controller: my.twig.controller.exception:showAction # This should be written in symfony 2.1 version !!! twig: exception_controller: AppBundleControllerExceptionController:showAction services: my.twig.controller.exception: class: AppBundleControllerExceptionController arguments: ['@twig', '%kernel.debug%', '@service_container']
Where class we write the path to our class, the controller, which we will then create in the bundle:
— class: AppBundleControllerExceptionController
And we specify the function in the controller ExceptionController, which will work on page transition: :showAction
The controller itself is referenced in the specified twig parameters by the name of the service:
— my.twig.controller.exception
In the arguments, I wrote the input parameters to the construction of the ExceptionController class:
— arguments: [‘@twig’, ‘%kernel.debug%’, ‘@service_container’]
After we have registered the paths and settings for the controller in yml, we proceed to creating the ExceptionController itself along the path AppBundleController :
namespace FrontBundleController; use SymfonyComponentDebugExceptionFlattenException; use SymfonyComponentHttpKernelLogDebugLoggerInterface; use SymfonyComponentHttpFoundationResponse; use SymfonyBundleTwigBundleControllerExceptionController as BaseExceptionController; use SymfonyComponentHttpFoundationRequest; use TwigEnvironment; class ExceptionController extends BaseExceptionController { private $container; public function __construct(Environment $twig, $debug, $container) { parent::__construct($twig, $debug); $this->container = $container; } public function showAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null, $format = 'html') { $currentContent = $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level', -1)); $showException = $request->attributes->get('showException', $this->debug); // As opposed to an additional $code = $exception->getStatusCode(); if($code != '404' && $code == ''){ $code = '404'; } $responce = new Response($this->twig->render( '@Front/Exception/error'.$code.'.html.twig', [ 'status_code' => $code, 'status_text' => isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : '', 'exception' => $exception, 'logger' => $logger, 'currentContent' => $currentContent, ] ), 200, ['Content-Type' => $request->getMimeType($request->getRequestFormat()) ?: 'text/html']); return $responce; } }
To display a 404 error, specify the path to the template @Front/Exception/error’.$code.’.html.twig and pass the variables there
That’s all, now when you switch to not not correct page this controller fires
As I make my way through some tutorials on SymfonyCasts and begin a re-write of my SyntaxSeed website from CodeIgniter to Symfony… I occasionally encounter something that took a bit more struggle than a simple web search. This is one of those tasks.
Symfony gives you some generic built in error pages, for example when you get a 404 missing resource error or a 500 error. When working in your dev environment you don’t see these, you instead get the nice Whoops error pages with extra details. Customizing the error pages requires a few steps.
First off, I assume you are using Symfony version 5.1 or later and are using the Twig templating engine.
Begin by ensuring you have both Twig and the Twig-Pack installed in your project with Composer:
composer require twig composer require symfony/twig-pack
Bonus Side Note…. while you’re at it you might want some nice extras in Twig including filters like truncate
() or wordwrap
. You can install these with:
composer require twig/string-extra composer require twig/extra-bundle
And use them like so:
{{ summary|u.truncate(100, '...') }}
Anyway, where were we… oh yes… customising the error pages. Once you have the twig-pack installed you need to create a set of new files:
/templates/bundles/TwigBundle/Exception/ - error404.html.twig - error500.html.twig - error.html.twig
Create one for each error type you wish to customise and create error.html.twig
as a fallback for all other error types.
Previewing Your Custom Symfony Error Pages
Ok so you’ve made a nice error page… how do you check it out? As you work on your project you’re probably in the dev
environment (configured in your .env file). This means when you get errors you will probably see the helpful Symfony error page with the ghost in the upper right.
Your first option is to switch your environment in the .env file from dev
to prod
. Then make sure you clear your template cache:
php bin/console cache:clear --env=prod
Now if you visit a non-existing route, you should see your 404 page.
How do you preview these pages while still being in the dev
environment? Symfony has a special route to do so:
https://127.0.0.1:8000/index.php/_error/404
Replace the code number on the end with the type of error you want to preview. Note that your local site might be using localhost instead of 127.0.0.1:8000. I believe this only works in the dev
environment.
It’s Still Not Working!?
Ensure that you have cleared your cache a few times in the correct environment and that the path and filenames for your custom error pages are correct. Also have a look in your composer.json file and ensure you have the right composer packages installed. For me, I have (this is for a Symfony 5.2 project):
"symfony/twig-bundle": "5.2.*", "twig/extra-bundle": "^3.3", "twig/twig": "^2.12|^3.0"
To be honest, this process was a bit finicky for me. I had to re-install the twig-pack a couple times before it would work. I did not have to add any special config values, routes or service hooks or anything to get this to work. I re-installed the pack, removed any Twig code from my templates, clear the cache and it worked. I then added Twig code back into the template and it continued to work.
I hope this was helpful – thanks for reading!
На каждом проекте не забываем настраивать обработчики ошибок, а то получим вот такие сюрпризы на продакшен сервере для страниц ошибок 404 и 500:
Настройка собственного действия и шаблона ошибки 404
1) Активируем опции error_404_module и error_404_action в конфигурационном файле settings.yml
all: .actions: error_404_module: error # Указываем наш модуль error_404_action: error404 # и действие
2) Создаем наш шаблон ошибки 404 error404Success.php
<html> <head> <title>Ошибка 404</title> </head> <body> Упс! Страница не найдена! </body> </html>
3) Очищаем кеш проекта
php symfony cc
Учтите, что в dev-режиме отработает обработчик ошибки 404 Symfony, а не наш. Такой небольшой нюанс.
Выводим собственную страницу для ошибок 500
За это отвечает файл error.html.php. Оригинал файла можно найти в исходниках Symfony «/symfony-1.2/lib/exception/data/error.html.php». Файл надо создать в папке нашего проекта «config/error/error.html.php»
Настраиваем собственный шаблон страницы при отключении нашего приложения (application)
Когда это может потребоваться? Например, Вам требуется обновить Ваш сайт, и это заберет некоторое время. В этом случае отключаем наш сайт и выводим красивую страничку, мол у нас проводятся «технические работы». Весьма полезная штука.
Что надо чтобы отключить/включить наше приложение:
1) Создаем свой шаблон unavailable.php и кладем его в одну из следующих директорий:
• /site/apps/frontend/config/unavailable.php
• /site/config/unavailable.php
• /site/config/web/errors/unavailable.php
Тут есть один момент. Можно спросить: «А зачем нам сохранять файл в папке frontend?». Это весьма полезная особенность, т.к. в этом случае мы можем изменить вывод этой страницы для www и wap, что очень важно.
2) Включаем опцию check_lock в файле settings.yml. После того как закончили все работы и включили приложение, не забудьте отключить эту опцию, т.к. при каждой загрузке страницы осуществляется проверка состояния приложения.
.settings: check_lock: on
3) Отключаем наше приложение
php symfony project:disable frontend prod
4) Очищаем кеш проекта и наше приложение отключено
php symfony cc
Для включения приложения выполняем следующие команды:
php symfony project:enable frontend prod php symfony cc
Что классно, то что frontend_dev.php — не отключается, т.е. можно проверить работу сайта в отладочном режиме, или отключить и этот режим. Не забываем удалять этот файл на продакшен сервере :).
Материал по теме:
• The Definitive Guide to symfony: Chapter 6 — Inside The Controller Layer
• The Definitive Guide to symfony: Chapter 19 — Mastering Symfony’s Configuration Files
• The symfony Cookbook: How to customize Error Templates
• Completely custom Symfony error pages
Tags: Symfony
19 Aug 2015
Допустим, что нам нужно обработать исключение 404 и вместо стандартного ответа сервера (или шаблона ошибки ответить специфической страницей.
Как кастомизировать страницы подробно описано в официальной документации.
Нас будет интересовать пункт работы с эвентом kernel.exception.
Можно посмотреть один из примеров его использования.
Наш кейс: если symfony не смог обработать роут, то управление передается своему обработчику.
namespace AppBundleListener;
use SymfonyComponentHttpKernelEventGetResponseForExceptionEvent;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentHttpKernelExceptionHttpExceptionInterface;
use SymfonyBundleFrameworkBundleTemplatingEngineInterface;
use SymfonyComponentHttpKernelExceptionNotFoundHttpException;
class ExceptionListener
{
protected $templating;
protected $kernel;
public function __construct(EngineInterface $templating, $kernel)
{
$this->templating = $templating;
$this->kernel = $kernel;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
if ($event->getException() instanceof NotFoundHttpException) {
$response = new Response();
$response->setContent("Какие-то данные");
//$response->setStatusCode(200) работать не будет
$response->headers->set('X-Status-Code', 200);
$event->setResponse($response);
}
}
}
В config.yml прописывем что-то вроде
services:
kernel.listener.app_exception_listener:
class: AppBundleListenerExceptionListener
arguments: [@templating, @kernel]
tags:
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
И обратите внимание, что переопределение статуса ответа в виде
$response->setStatusCode(200)
работать не будет. Мы не сможем переопределить статус отвта на 2хх с помощью этого метода (на любой другой можно).
Для того, чтобы мы могли переопределить ответ 404 нужно использовать метод
$response->headers->set('X-Status-Code', 200);
Теги:
php
symfony2
Категории:
HowTo