diff --git a/cookbook/controller/error_pages.rst b/cookbook/controller/error_pages.rst index 2e28b98a9d6..1b4ea461d07 100644 --- a/cookbook/controller/error_pages.rst +++ b/cookbook/controller/error_pages.rst @@ -5,172 +5,160 @@ How to Customize Error Pages ============================ -When an exception is thrown, the core ``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. +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. -The core TwigBundle sets up a listener for this event which will run -a configurable (but otherwise arbitrary) controller to generate the -response. The default controller used has a sensible way of -picking one out of the available set of error templates. +In the `development environment`_, Symfony catches all the exceptions and displays +a special **exception page** with lots of debug information to help you quickly +discover the root problem: -Thus, error pages can be customized in different ways, depending on how -much control you need: +.. image:: /images/cookbook/controller/error_pages/exceptions-in-dev-environment.png + :alt: A typical exception page in the development environment -#. :ref:`Use the default ExceptionController and create a few - templates that allow you to customize how your different error - pages look (easy); ` +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**: -#. :ref:`Replace the default exception controller with your own - (intermediate). ` +.. image:: /images/cookbook/controller/error_pages/errors-in-prod-environment.png + :alt: A typical error page in the production environment -#. :ref:`Use the kernel.exception event to come up with your own - handling (advanced). ` +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 default error templates `; + +#. If you also want to tweak the logic used by Symfony to generate error pages, + :ref:`override the default exception controller `; + +#. If you need total control of exception handling to execute your own logic + :ref:`use the kernel.exception event `. .. _use-default-exception-controller: +.. _using-the-default-exceptioncontroller: -Using the Default ExceptionController -------------------------------------- +Overriding the Default Error Templates +-------------------------------------- By default, the ``showAction()`` method of the -:class:`Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController` -will be called when an exception occurs. +:class:`Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController` is called +whenever an exception occurs, thanks to an event listener configured by the TwigBundle. -This controller will either display an -*exception* or *error* page, depending on the setting of the ``kernel.debug`` -flag. While *exception* pages give you a lot of helpful -information during development, *error* pages are meant to be -shown to the user in production. +Then, the controller selects one of the templates defined in the +``Resources/views/Exception`` directory of the TwigBundle to render the error +page. If you browse that directory (usually located in +``vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle``) you'll find a lot of +templates defined for different types of errors and content formats +(``error.*.twig`` templates are used in the production environment whereas +``exception.*.twig`` templates are used in the development environment). -.. sidebar:: Testing Error Pages during Development +.. _cookbook-error-pages-by-status-code: - You should not set ``kernel.debug`` to ``false`` in order to see your - *error* pages during development. This will also stop - Symfony from recompiling your twig templates, among other things. +The logic followed by the ``ExceptionController`` to pick one of the available +templates is based on the HTTP status code and the request format: - The third-party `WebfactoryExceptionsBundle`_ provides a special - test controller that allows you to display your custom error - pages for arbitrary HTTP status codes even with - ``kernel.debug`` set to ``true``. +#. Look for a template for the given format and status code (like ``error404.json.twig`` + or ``error500.xml.twig``); -.. _`WebfactoryExceptionsBundle`: https://github.com/webfactory/exceptions-bundle +#. If the previous template doesn't exist, discard the status code and look for + a generic template for the given format (like ``error.json.twig`` or + ``error.xml.twig``); -.. _cookbook-error-pages-by-status-code: +#. If none of the previous template exist, fall back to the generic HTML template + (``error.html.twig``). -How the Template for the Error and Exception Pages Is Selected -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. _overriding-or-adding-templates: -The TwigBundle contains some default templates for error and -exception pages in its ``Resources/views/Exception`` directory. +To override these templates, simply rely on the standard Symfony method for +:ref:`overriding templates that live inside a bundle `. +For example, to override the 404 error template for HTML pages, create a new +``error404.html.twig`` template located at ``app/Resources/TwigBundle/views/Exception/``: -.. tip:: +.. code-block:: html+jinja - In a standard Symfony installation, the TwigBundle can be found at - ``vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle``. In addition - to the standard HTML error page, it also provides a default - error page for many of the most common response formats, including - JSON (``error.json.twig``), XML (``error.xml.twig``) and even - JavaScript (``error.js.twig``), to name a few. + {# app/Resources/TwigBundle/views/Exception/error404.html.twig #} + {% extends 'base.html.twig' %} -Here is how the ``ExceptionController`` will pick one of the -available templates based on the HTTP status code and request format: + {% block body %} +

Page not found

-* For *error* pages, it first looks for a template for the given format - and status code (like ``error404.json.twig``); +

+ The requested page couldn't be located. Checkout for any URL + misspelling or return to the homepage. +

+ {% endblock %} -* If that does not exist or apply, it looks for a general template for - the given format (like ``error.json.twig`` or - ``exception.json.twig``); +Commonly, Symfony applications redefine the ``error404.html.twig`` template, the +``error500.html.twig`` template for internal server errors and the generic +``error.html.twig`` template to catch any other error different from 404 and 500. -* Finally, it ignores the format and falls back to the HTML template - (like ``error.html.twig`` or ``exception.html.twig``). +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:: - If the exception being handled implements the - :class:`Symfony\\Component\\HttpKernel\\Exception\\HttpExceptionInterface`, - the ``getStatusCode()`` method will be - called to obtain the HTTP status code to use. Otherwise, - the status code will be "500". + 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``. -Overriding or Adding Templates -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. note:: -To override these templates, simply rely on the standard method for -overriding templates that live inside a bundle. For more information, -see :ref:`overriding-bundle-templates`. + 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. -For example, to override the default error template, create a new -template located at -``app/Resources/TwigBundle/views/Exception/error.html.twig``: +Avoiding Exceptions when Using Security Functions in Error Templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. code-block:: html+jinja +One of the common pitfalls when designing custom error pages is to use the +``is_granted()`` function in the error template (or in any parent template +inherited by the error template). If you do that, you'll see an exception thrown +by Symfony. - - - - - An Error Occurred: {{ status_text }} - - -

Oops! An Error Occurred

-

The server returned a "{{ status_code }} {{ status_text }}".

- - - -.. caution:: - - You **must not** use ``is_granted`` in your error pages (or layout used - by your error pages), because the router runs before the firewall. If - the router throws an exception (for instance, when the route does not - match), then using ``is_granted`` will throw a further exception. You - can use ``is_granted`` safely by saying ``{% if app.user and is_granted('...') %}``. +The cause of this problem is that routing is done before security. If a 404 error +occurs, the security layer isn't loaded and thus, the ``is_granted()`` function +is undefined. The solution is to add the following check before using this function: -.. tip:: +.. code-block:: twig - If you're not familiar with Twig, don't worry. Twig is a simple, - powerful and optional templating engine that integrates with - Symfony. For more information about Twig see :doc:`/book/templating`. + {% if app.user and is_granted('...') %} + {# ... #} + {% endif %} -This works not only to replace the default templates, but also to add -new ones. +Testing Error Pages during Development +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -For instance, create an ``app/Resources/TwigBundle/views/Exception/error404.html.twig`` -template to display a special page for 404 (page not found) errors. -Refer to the previous section for the order in which the -``ExceptionController`` tries different template names. +One of the biggest hurdles of testing how do custom error pages look in your +application is the fact that Symfony ignores them in the development environment +and displays the default exception pages instead. -.. tip:: - - Often, the easiest way to customize an error page is to copy it from - the TwigBundle into ``app/Resources/TwigBundle/views/Exception`` and - then modify it. - -.. note:: +You may be tempted to set the ``kernel.debug`` parameter to ``false`` to disable +the debug mode in the development environment. However, this practice is not +recommended because it will also stop Symfony from recompiling your Twig templates, +among many other things. - The debug-friendly exception pages shown to the developer can even be - customized in the same way by creating templates such as - ``exception.html.twig`` for the standard HTML exception page or - ``exception.json.twig`` for the JSON exception page. +The recommended solution is to use a third-party bundle called `WebfactoryExceptionsBundle`_. +This bundle provides a special test controller that allows you to easily display +custom error pages for arbitrary HTTP status codes even when ``kernel.debug`` is +set to ``true``. .. _custom-exception-controller: +.. _replacing-the-default-exceptioncontroller: -Replacing the Default ExceptionController +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. +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. -.. caution:: - - Make sure you don't lose the exception pages that render the helpful - error messages during development. - -To do this, simply create a new controller and set the -:ref:`twig.exception_controller ` option -to point to it. +To do this, simply create a new controller anywhere in your application and set +the :ref:`twig.exception_controller ` +configuration option to point to it: .. configuration-block:: @@ -205,86 +193,63 @@ to point to it. // ... )); -.. tip:: - - You can also set up your controller as a service. - - The default value of ``twig.controller.exception:showAction`` refers - to the ``showAction`` method of the ``ExceptionController`` - described previously, which is registered in the DIC as the - ``twig.controller.exception`` service. - -Your controller will be passed two parameters: ``exception``, -which is a :class:`\\Symfony\\Component\\Debug\\Exception\\FlattenException` -instance created from the exception being handled, and ``logger``, -an instance of :class:`\\Symfony\\Component\\HttpKernel\\Log\\DebugLoggerInterface` -(which may be ``null``). - -.. tip:: - - The Request that will be dispatched to your controller is created - in the :class:`Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener`. - This event listener is set up by the TwigBundle. +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: -You can, of course, also extend the previously described -: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. +``exception`` + A :class:`\\Symfony\\Component\\Debug\\Exception\\FlattenException` + instance created from the exception being handled. -.. caution:: +``logger`` + A :class:`\\Symfony\\Component\\HttpKernel\\Log\\DebugLoggerInterface` + instance which may be ``null`` in some circumstances. - As of writing, the ``ExceptionController`` is *not* part of the - Symfony API, so be aware that it might change in following releases. +Instead of creating a new exception controller from scratch you can, of course, +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. .. _use-kernel-exception-event: -Working with the kernel.exception Event ------------------------------------------ - -As mentioned in the beginning, the ``kernel.exception`` event is -dispatched whenever the Symfony Kernel needs to -handle an exception. For more information on that, see :ref:`kernel-kernel.exception`. - -Working with this event is actually much more powerful than what has -been explained before but also requires a thorough understanding of -Symfony internals. +Working with the ``kernel.exception`` Event +------------------------------------------- -To give one example, assume your application throws -specialized exceptions with a particular meaning to your domain. +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. -In that case, all the default ``ExceptionListener`` and -``ExceptionController`` could do for you was trying to figure out the -right HTTP status code and display your nice-looking error page. +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 ` -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. +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\\GetResponseForExceptionEvent`, - event propagation will be stopped and the response will be sent to + 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 again and again, you can have just one (or -several) listeners deal with them. +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:: - To see an example, have a look at the `ExceptionListener`_ in the - Security Component. - - It handles various security-related exceptions that are thrown in + 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. - -Good luck! + and takes measures like redirecting the user to the login page, logging them + out and other things. -.. _`ExceptionListener`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php +.. _`development environment`: http://symfony.com/doc/current/cookbook/configuration/environments.html +.. _`WebfactoryExceptionsBundle`: https://github.com/webfactory/exceptions-bundle diff --git a/images/cookbook/controller/error_pages/errors-in-prod-environment.png b/images/cookbook/controller/error_pages/errors-in-prod-environment.png new file mode 100644 index 00000000000..79fe5341b47 Binary files /dev/null and b/images/cookbook/controller/error_pages/errors-in-prod-environment.png differ diff --git a/images/cookbook/controller/error_pages/exceptions-in-dev-environment.png b/images/cookbook/controller/error_pages/exceptions-in-dev-environment.png new file mode 100644 index 00000000000..dffd1460b9b Binary files /dev/null and b/images/cookbook/controller/error_pages/exceptions-in-dev-environment.png differ