diff --git a/components/debug.rst b/components/debug.rst index 6f116e25af6..ceeaf505cce 100644 --- a/components/debug.rst +++ b/components/debug.rst @@ -12,7 +12,7 @@ Installation .. code-block:: terminal - $ composer require symfony/debug + $ composer require --dev symfony/debug .. include:: /components/require_autoload.rst.inc @@ -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 `. - -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 ` 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 `. diff --git a/components/error_handler.rst b/components/error_handler.rst new file mode 100644 index 00000000000..e394ece0f4f --- /dev/null +++ b/components/error_handler.rst @@ -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 `. + +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 ` 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 ` 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 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +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 diff --git a/components/error_renderer.rst b/components/error_renderer.rst new file mode 100644 index 00000000000..c62fd39c117 --- /dev/null +++ b/components/error_renderer.rst @@ -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 ` and +:doc:`tag it ` 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 + + + + + + + + %kernel.debug% + + + + + + .. 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 diff --git a/components/http_kernel.rst b/components/http_kernel.rst index 2b2ebe3b5fc..93da3f6156e 100644 --- a/components/http_kernel.rst +++ b/components/http_kernel.rst @@ -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. diff --git a/configuration/front_controllers_and_kernel.rst b/configuration/front_controllers_and_kernel.rst index d986d7471b7..bdeac5ed93d 100644 --- a/configuration/front_controllers_and_kernel.rst +++ b/configuration/front_controllers_and_kernel.rst @@ -123,6 +123,8 @@ new kernel. .. index:: single: Configuration; Debug mode +.. _debug-mode: + Debug Mode ~~~~~~~~~~ diff --git a/controller/error_pages.rst b/controller/error_pages.rst index a4026b75a66..482ba6edd95 100644 --- a/controller/error_pages.rst +++ b/controller/error_pages.rst @@ -247,7 +247,7 @@ the request that will be dispatched to your controller. In addition, your contro will be passed two parameters: ``exception`` - A :class:`\\Symfony\\Component\\Debug\\Exception\\FlattenException` + A :class:`\\Symfony\\Component\\ErrorRenderer\\Exception\\FlattenException` instance created from the exception being handled. ``logger`` diff --git a/create_framework/http_kernel_httpkernel_class.rst b/create_framework/http_kernel_httpkernel_class.rst index f9f8f16932f..5845f4219dc 100644 --- a/create_framework/http_kernel_httpkernel_class.rst +++ b/create_framework/http_kernel_httpkernel_class.rst @@ -69,7 +69,7 @@ Our code is now much more concise and surprisingly more robust and more powerful than ever. For instance, use the built-in ``ExceptionListener`` to make your error management configurable:: - $errorHandler = function (Symfony\Component\Debug\Exception\FlattenException $exception) { + $errorHandler = function (Symfony\Component\ErrorRenderer\Exception\FlattenException $exception) { $msg = 'Something went wrong! ('.$exception->getMessage().')'; return new Response($msg, $exception->getStatusCode()); @@ -91,7 +91,7 @@ The error controller reads as follows:: // example.com/src/Calendar/Controller/ErrorController.php namespace Calendar\Controller; - use Symfony\Component\Debug\Exception\FlattenException; + use Symfony\Component\ErrorRenderer\Exception\FlattenException; use Symfony\Component\HttpFoundation\Response; class ErrorController