Skip to content

Documenting ErrorRenderer and ErrorHandler components #12187

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Sep 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 5 additions & 56 deletions components/debug.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Installation

.. code-block:: terminal

$ composer require symfony/debug
$ composer require --dev symfony/debug

.. include:: /components/require_autoload.rst.inc

Expand All @@ -26,64 +26,13 @@ Enable all of them by calling this method::

Debug::enable();

The :method:`Symfony\\Component\\Debug\\Debug::enable` method registers an
error handler, an exception handler and
:ref:`a special class loader <component-debug-class-loader>`.

Read the following sections for more information about the different available
tools.

.. caution::

You should never enable the debug tools, except for the error handler, in a
production environment as they might disclose sensitive information to the user.

Enabling the Error Handler
--------------------------

The :class:`Symfony\\Component\\Debug\\ErrorHandler` class catches PHP errors
and converts them to exceptions (of class :phpclass:`ErrorException` or
:class:`Symfony\\Component\\Debug\\Exception\\FatalErrorException` for PHP
fatal errors)::

use Symfony\Component\Debug\ErrorHandler;

ErrorHandler::register();

This error handler is enabled by default in the production environment when the
application uses the FrameworkBundle because it generates better error logs.

Enabling the Exception Handler
------------------------------

The :class:`Symfony\\Component\\Debug\\ExceptionHandler` class catches
uncaught PHP exceptions and converts them to a nice PHP response. It is useful
in debug mode to replace the default PHP/XDebug output with something prettier
and more useful::

use Symfony\Component\Debug\ExceptionHandler;

ExceptionHandler::register();

.. note::

If the :doc:`HttpFoundation component </components/http_foundation>` is
available, the handler uses a Symfony Response object; if not, it falls
back to a regular PHP response.

.. _component-debug-class-loader:

Debugging a Class Loader
------------------------

The :class:`Symfony\\Component\\Debug\\DebugClassLoader` attempts to
throw more helpful exceptions when a class isn't found by the registered
autoloaders. All autoloaders that implement a ``findFile()`` method are replaced
with a ``DebugClassLoader`` wrapper.

Using the ``DebugClassLoader`` is done by calling its static
:method:`Symfony\\Component\\Debug\\DebugClassLoader::enable` method::

use Symfony\Component\Debug\DebugClassLoader;
.. deprecated:: 4.4

DebugClassLoader::enable();
In Symfony versions before 4.4, this component also provided error and
exception handlers. In Symfony 4.4 they were deprecated in favor of their
equivalent handlers included in the new :doc:`ErrorHandler component </components/error_handler>`.
152 changes: 152 additions & 0 deletions components/error_handler.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
.. index::
single: Debug
single: Error
single: Exception
single: Components; ErrorHandler

The ErrorHandler Component
==========================

The ErrorHandler component provides tools to manage errors and ease debugging PHP code.

Installation
------------

.. code-block:: terminal

$ composer require symfony/error-handler

.. include:: /components/require_autoload.rst.inc

Usage
-----

The ErrorHandler component provides several tools to help you debug PHP code.
Enable all of them by calling this method::

use Symfony\Component\ErrorHandler\Debug;

Debug::enable();

The :method:`Symfony\\Component\\ErrorHandler\\Debug::enable` method registers an
error handler, an exception handler and
:ref:`a special class loader <component-debug-class-loader>`.

Read the following sections for more information about the different available
tools.

.. caution::

You should never enable the debug tools, except for the error handler, in a
production environment as they might disclose sensitive information to the user.

Handling PHP Errors and Exceptions
----------------------------------

Enabling the Error Handler
~~~~~~~~~~~~~~~~~~~~~~~~~~

The :class:`Symfony\\Component\\ErrorHandler\\ErrorHandler` class catches PHP
errors and converts them to exceptions (of class :phpclass:`ErrorException` or
:class:`Symfony\\Component\\ErrorHandler\\Exception\\FatalErrorException` for
PHP fatal errors)::

use Symfony\Component\ErrorHandler\ErrorHandler;

ErrorHandler::register();

This error handler is enabled by default in the production environment when the
application uses the FrameworkBundle because it generates better error logs.

Enabling the Exception Handler
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The :class:`Symfony\\Component\\ErrorHandler\\ExceptionHandler` class catches
uncaught PHP exceptions and converts them to a nice PHP response. It is useful
in :ref:`debug mode <debug-mode>` to replace the default PHP/XDebug output with
something prettier and more useful::

use Symfony\Component\ErrorHandler\ExceptionHandler;

ExceptionHandler::register();

.. note::

If the :doc:`HttpFoundation component </components/http_foundation>` is
available, the handler uses a Symfony Response object; if not, it falls
back to a regular PHP response.

Catches PHP errors and turn them into exceptions
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, documenting symfony/symfony#33155, close #12193

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Most PHP core functions were written before exception handling was introduced to the
language and most of this functions do not throw an exception on failure. Instead,
they return ``false`` in case of error.

Let's take the following code example::

$data = json_decode(file_get_contents($filename), true);
$data['read_at'] = date($datetimeFormat);
file_put_contents($filename, json_encode($data));

All these functions ``file_get_contents``, ``json_decode``, ``date``, ``json_encode``
and ``file_put_contents`` will return ``false`` or ``null`` on error, having to
deal with those failures manually::

$content = @file_get_contents($filename);
if (false === $content) {
throw new \RuntimeException('Could not load file.');
}
$data = @json_decode($content, true);
if (null === $data) {
throw new \RuntimeException('File does not contain valid JSON.');
}
$datetime = @date($datetimeFormat);
if (false === $datetime) {
throw new \RuntimeException('Invalid datetime format.');
}
// ...

.. note::

Since PHP 7.3 `json_decode`_ function will accept a new ``JSON_THROW_ON_ERROR`` option
that will let ``json_decode`` throw an exception instead of returning ``null`` on error.
However, it is not enabled by default, so you will need to explicitly configure it.

To simplify this behavior the :class:`Symfony\\Component\\ErrorHandler\\ErrorHandler` class
provides a :method:`Symfony\\Component\\ErrorHandler\\ErrorHandler::call` method that will
automatically throw an exception when such a failure occurs. This method will accept a ``callable``
parameter and then the arguments needed to call it, returning back the result::

$content = ErrorHandler::call('file_get_contents', $filename);

This way, you could use a ``\Closure`` function to wrap a portion of code and be sure that it
breaks even if the `@-silencing operator`_ is used::

$data = ErrorHandler::call(static function () use ($filename, $datetimeFormat) {
$data = json_decode(file_get_contents($filename), true);
$data['read_at'] = date($datetimeFormat);
file_put_contents($filename, json_encode($data));

return $data;
});

.. _component-debug-class-loader:

Debugging a Class Loader
------------------------

The :class:`Symfony\\Component\\ErrorHandler\\DebugClassLoader` attempts to
throw more helpful exceptions when a class isn't found by the registered
autoloaders. All autoloaders that implement a ``findFile()`` method are replaced
with a ``DebugClassLoader`` wrapper.

Using the ``DebugClassLoader`` is done by calling its static
:method:`Symfony\\Component\\ErrorHandler\\DebugClassLoader::enable` method::

use Symfony\Component\ErrorHandler\DebugClassLoader;

DebugClassLoader::enable();

.. _`@-silencing operator`: https://php.net/manual/en/function.json-decode.php
.. _`json_decode`: https://php.net/manual/en/language.operators.errorcontrol.php
159 changes: 159 additions & 0 deletions components/error_renderer.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
.. index::
single: Error
single: Exception
single: Components; ErrorRenderer

The ErrorRenderer Component
===========================

The ErrorRenderer component converts PHP errors and exceptions into other
formats such as JSON and HTML and renders them.

Installation
------------

.. code-block:: terminal

$ composer require symfony/error-renderer

.. include:: /components/require_autoload.rst.inc

Usage
-----

The ErrorRenderer component provides several renderers to convert PHP errors and
exceptions into other formats such as JSON and HTML easier to debug when working
with HTTP applications::

use Symfony\Component\ErrorRenderer\ErrorRenderer;
use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;
use Symfony\Component\HttpFoundation\Response;

$renderers = [
new HtmlErrorRenderer(),
new JsonErrorRenderer(),
// ...
];
$errorRenderer = new ErrorRenderer($renderers);

try {
// ...
} catch (\Throwable $e) {
$e = FlattenException::createFromThrowable($e);

return new Response($errorRenderer->render($e, 'json'), 500, ['Content-Type' => 'application/json']);
}

Built-in Error Renderers
~~~~~~~~~~~~~~~~~~~~~~~~

This component provides error renderers for the most common needs:

* :class:`Symfony\\Component\\ErrorRenderer\\ErrorRenderer\\HtmlErrorRenderer`
renders errors in HTML format;
* :class:`Symfony\\Component\\ErrorRenderer\\ErrorRenderer\\JsonErrorRenderer`
renders errors in JSON format and it's compliant with the `RFC 7807`_ standard;
* :class:`Symfony\\Component\\ErrorRenderer\\ErrorRenderer\\XmlErrorRenderer`
renders errors in XML and Atom formats. It's compliant with the `RFC 7807`_
standard;
* :class:`Symfony\\Component\\ErrorRenderer\\ErrorRenderer\\TxtErrorRenderer`
renders errors in plain text format.

Adding a Custom Error Renderer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Error renderers are PHP classes that implement the
:class:`Symfony\\Component\\ErrorRenderer\\ErrorRenderer\\ErrorRendererInterface`.
For example, if you need to render errors in `JSON-LD format`_, create this
class anywhere in your project::

namespace App\ErrorRenderer;

use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface;
use Symfony\Component\ErrorRenderer\Exception\FlattenException;

class JsonLdErrorRenderer implements ErrorRendererInterface
{
private $debug;

public function __construct(bool $debug = true)
{
$this->debug = $debug;
}

public static function getFormat(): string
{
return 'jsonld';
}

public function render(FlattenException $exception): string
{
$content = [
'@id' => 'https://example.com',
'@type' => 'error',
'@context' => [
'title' => $exception->getTitle(),
'code' => $exception->getStatusCode(),
'message' => $exception->getMessage(),
],
];

if ($this->debug) {
$content['@context']['exceptions'] = $exception->toArray();
}

return (string) json_encode($content);
}
}

.. tip::

If the ``getFormat()`` method of your error renderer matches one of formats
supported by the built-in renderers, the built-in renderer is replaced by
your custom renderer.

To enable the new error renderer in the application,
:ref:`register it as a service <service-container-creating-service>` and
:doc:`tag it </service_container/tags>` with the ``error_renderer.renderer``
tag.

.. configuration-block::

.. code-block:: yaml

# config/services.yaml
services:
App\ErrorRenderer\JsonLdErrorRenderer:
arguments: ['%kernel.debug%']
tags: ['error_renderer.renderer']

.. 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>
<service id="App\ErrorRenderer\JsonLdErrorRenderer">
<argument>%kernel.debug%</argument>
<tag name="error_renderer.renderer"/>
</service>
</services>
</container>

.. code-block:: php

// config/services.php
use App\ErrorRenderer\JsonLdErrorRenderer;

$container->register(JsonLdErrorRenderer::class)
->setArguments([$container->getParameter('kernel.debug')]);
->addTag('error_renderer.renderer');

.. _`RFC 7807`: https://tools.ietf.org/html/rfc7807
.. _`JSON-LD format`: https://en.wikipedia.org/wiki/JSON-LD
2 changes: 1 addition & 1 deletion components/http_kernel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ below for more details).
The listener has several goals:

1) The thrown exception is converted into a
:class:`Symfony\\Component\\Debug\\Exception\\FlattenException`
:class:`Symfony\\Component\\ErrorRenderer\\Exception\\FlattenException`
object, which contains all the information about the request, but which
can be printed and serialized.

Expand Down
Loading