diff --git a/UPGRADE-4.4.md b/UPGRADE-4.4.md index b4fb6a0a8e248..aef34c47bd61d 100644 --- a/UPGRADE-4.4.md +++ b/UPGRADE-4.4.md @@ -18,8 +18,6 @@ Console Debug ----- - * Deprecated the `Debug` class, use the one from the `ErrorRenderer` component instead - * Deprecated the `FlattenException` class, use the one from the `ErrorRenderer` component instead * Deprecated the component in favor of the `ErrorHandler` component Config @@ -306,48 +304,38 @@ TwigBundle ``` * Deprecated the `ExceptionController` and `PreviewErrorController` controllers, use `ErrorController` from the HttpKernel component instead - * Deprecated all built-in error templates, use the error renderer mechanism of the `ErrorRenderer` component + * Deprecated all built-in error templates, use the error renderer mechanism of the `ErrorHandler` component * Deprecated loading custom error templates in non-html formats. Custom HTML error pages based on Twig keep working as before: - Before (`templates/bundles/TwigBundle/Exception/error.jsonld.twig`): + Before (`templates/bundles/TwigBundle/Exception/error.json.twig`): ```twig { - "@id": "https://example.com", - "@type": "error", - "@context": { - "title": "{{ status_text }}", - "code": {{ status_code }}, - "message": "{{ exception.message }}" - } + "type": "https://example.com/error", + "title": "{{ status_text }}", + "status": {{ status_code }} } ``` - After (`App\ErrorRenderer\JsonLdErrorRenderer`): + After (`App\Serializer\ProblemJsonNormalizer`): ```php - class JsonLdErrorRenderer implements ErrorRendererInterface + class ProblemJsonNormalizer implements NormalizerInterface { - public static function getFormat(): string + public function normalize($exception, $format = null, array $context = []) { - return 'jsonld'; + return [ + 'type' => 'https://example.com/error', + 'title' => $exception->getStatusText(), + 'status' => $exception->getStatusCode(), + ]; } - public function render(FlattenException $exception): string + public function supportsNormalization($data, $format = null) { - return json_encode([ - '@id' => 'https://example.com', - '@type' => 'error', - '@context' => [ - 'title' => $exception->getTitle(), - 'code' => $exception->getStatusCode(), - 'message' => $exception->getMessage(), - ], - ]); + return 'json' === $format && $data instanceof FlattenException; } } ``` - Configure your rendering service tagging it with `error_renderer.renderer`. - Validator --------- diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md index 8fc53005b312b..ce4b127c3ebfb 100644 --- a/UPGRADE-5.0.md +++ b/UPGRADE-5.0.md @@ -57,8 +57,6 @@ Console Debug ----- - * Removed the `Debug` class, use the one from the `ErrorRenderer` component instead - * Removed the `FlattenException` class, use the one from the `ErrorRenderer` component instead * Removed the component in favor of the `ErrorHandler` component DependencyInjection diff --git a/composer.json b/composer.json index 24bcc7650f761..a432a159c07fe 100644 --- a/composer.json +++ b/composer.json @@ -48,7 +48,6 @@ "symfony/dom-crawler": "self.version", "symfony/dotenv": "self.version", "symfony/error-handler": "self.version", - "symfony/error-renderer": "self.version", "symfony/event-dispatcher": "self.version", "symfony/expression-language": "self.version", "symfony/filesystem": "self.version", diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index 4f460d298221f..71a30ae880d78 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 4.4.0 ----- + * added a new `TwigErrorRenderer` for `html` format, integrated with the `ErrorHandler` component * marked all classes extending twig as `@final` * deprecated to pass `$rootDir` and `$fileLinkFormatter` as 5th and 6th argument respectively to the `DebugCommand::__construct()` method, swap the variables position. diff --git a/src/Symfony/Bundle/TwigBundle/ErrorRenderer/TwigHtmlErrorRenderer.php b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php similarity index 63% rename from src/Symfony/Bundle/TwigBundle/ErrorRenderer/TwigHtmlErrorRenderer.php rename to src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php index b9c876a273cce..9968b8ba8f334 100644 --- a/src/Symfony/Bundle/TwigBundle/ErrorRenderer/TwigHtmlErrorRenderer.php +++ b/src/Symfony/Bridge/Twig/ErrorRenderer/TwigErrorRenderer.php @@ -9,11 +9,11 @@ * file that was distributed with this source code. */ -namespace Symfony\Bundle\TwigBundle\ErrorRenderer; +namespace Symfony\Bridge\Twig\ErrorRenderer; -use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface; -use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; +use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface; +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\ErrorHandler\Exception\FlattenException; use Twig\Environment; use Twig\Error\LoaderError; use Twig\Loader\ExistsLoaderInterface; @@ -24,50 +24,36 @@ * * @author Yonel Ceruto */ -class TwigHtmlErrorRenderer implements ErrorRendererInterface +class TwigErrorRenderer implements ErrorRendererInterface { private $twig; - private $htmlErrorRenderer; + private $fallbackErrorRenderer; private $debug; - public function __construct(Environment $twig, HtmlErrorRenderer $htmlErrorRenderer, bool $debug = false) + public function __construct(Environment $twig, HtmlErrorRenderer $fallbackErrorRenderer = null, bool $debug = false) { $this->twig = $twig; - $this->htmlErrorRenderer = $htmlErrorRenderer; + $this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); $this->debug = $debug; } /** * {@inheritdoc} */ - public static function getFormat(): string + public function render(\Throwable $exception): FlattenException { - return 'html'; - } - - /** - * {@inheritdoc} - */ - public function render(FlattenException $exception): string - { - $debug = $this->debug && ($exception->getHeaders()['X-Debug'] ?? true); - - if ($debug) { - return $this->htmlErrorRenderer->render($exception); - } - - $template = $this->findTemplate($exception->getStatusCode()); + $exception = $this->fallbackErrorRenderer->render($exception); - if (null === $template) { - return $this->htmlErrorRenderer->render($exception); + if ($this->debug || !$template = $this->findTemplate($exception->getStatusCode())) { + return $exception; } - return $this->twig->render($template, [ + return $exception->setAsString($this->twig->render($template, [ 'legacy' => false, // to be removed in 5.0 'exception' => $exception, 'status_code' => $exception->getStatusCode(), - 'status_text' => $exception->getTitle(), - ]); + 'status_text' => $exception->getStatusText(), + ])); } private function findTemplate(int $statusCode): ?string diff --git a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php index d1051596dd390..b5118b7f08c7b 100644 --- a/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php +++ b/src/Symfony/Bridge/Twig/Mime/NotificationEmail.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\Twig\Mime; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; +use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\Mime\Header\Headers; use Symfony\Component\Mime\Part\AbstractPart; use Twig\Extra\CssInliner\CssInlinerExtension; diff --git a/src/Symfony/Bundle/TwigBundle/Tests/ErrorRenderer/TwigHtmlErrorRendererTest.php b/src/Symfony/Bridge/Twig/Tests/ErrorRenderer/TwigErrorRendererTest.php similarity index 54% rename from src/Symfony/Bundle/TwigBundle/Tests/ErrorRenderer/TwigHtmlErrorRendererTest.php rename to src/Symfony/Bridge/Twig/Tests/ErrorRenderer/TwigErrorRendererTest.php index fa04d363caf21..9febc61e61887 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/ErrorRenderer/TwigHtmlErrorRendererTest.php +++ b/src/Symfony/Bridge/Twig/Tests/ErrorRenderer/TwigErrorRendererTest.php @@ -9,21 +9,21 @@ * file that was distributed with this source code. */ -namespace Symfony\Bundle\TwigBundle\Tests\ErrorRenderer; +namespace Symfony\Bridge\Twig\Tests\ErrorRenderer; use PHPUnit\Framework\TestCase; -use Symfony\Bundle\TwigBundle\ErrorRenderer\TwigHtmlErrorRenderer; -use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; +use Symfony\Bridge\Twig\ErrorRenderer\TwigErrorRenderer; +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Twig\Environment; use Twig\Loader\ArrayLoader; -class TwigHtmlErrorRendererTest extends TestCase +class TwigErrorRendererTest extends TestCase { public function testFallbackToNativeRendererIfDebugOn() { - $exception = FlattenException::createFromThrowable(new \Exception()); + $exception = new \Exception(); $twig = $this->createMock(Environment::class); $nativeRenderer = $this->createMock(HtmlErrorRenderer::class); @@ -33,12 +33,12 @@ public function testFallbackToNativeRendererIfDebugOn() ->with($exception) ; - (new TwigHtmlErrorRenderer($twig, $nativeRenderer, true))->render($exception); + (new TwigErrorRenderer($twig, $nativeRenderer, true))->render(new \Exception()); } public function testFallbackToNativeRendererIfCustomTemplateNotFound() { - $exception = FlattenException::createFromThrowable(new NotFoundHttpException()); + $exception = new NotFoundHttpException(); $twig = new Environment(new ArrayLoader([])); @@ -47,27 +47,19 @@ public function testFallbackToNativeRendererIfCustomTemplateNotFound() ->expects($this->once()) ->method('render') ->with($exception) + ->willReturn(FlattenException::createFromThrowable($exception)) ; - (new TwigHtmlErrorRenderer($twig, $nativeRenderer, false))->render($exception); + (new TwigErrorRenderer($twig, $nativeRenderer, false))->render($exception); } public function testRenderCustomErrorTemplate() { - $exception = FlattenException::createFromThrowable(new NotFoundHttpException()); - $twig = new Environment(new ArrayLoader([ '@Twig/Exception/error404.html.twig' => '

Page Not Found

', ])); + $exception = (new TwigErrorRenderer($twig))->render(new NotFoundHttpException()); - $nativeRenderer = $this->createMock(HtmlErrorRenderer::class); - $nativeRenderer - ->expects($this->never()) - ->method('render') - ; - - $content = (new TwigHtmlErrorRenderer($twig, $nativeRenderer, false))->render($exception); - - $this->assertSame('

Page Not Found

', $content); + $this->assertSame('

Page Not Found

', $exception->getAsString()); } } diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 563b82327fbb3..ef493d86cb053 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -24,10 +24,11 @@ "egulias/email-validator": "^2.1.10", "symfony/asset": "^3.4|^4.0|^5.0", "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/error-handler": "^4.4|^5.0", "symfony/finder": "^3.4|^4.0|^5.0", "symfony/form": "^4.4|^5.0", "symfony/http-foundation": "^4.3|^5.0", - "symfony/http-kernel": "^3.4|^4.0", + "symfony/http-kernel": "^4.4", "symfony/mime": "^4.3|^5.0", "symfony/polyfill-intl-icu": "~1.0", "symfony/routing": "^3.4|^4.0|^5.0", diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php index 7ee9e12c0afc5..e21550115b295 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -32,7 +32,6 @@ class UnusedTagsPass implements CompilerPassInterface 'controller.service_arguments', 'config_cache.resource_checker', 'data_collector', - 'error_renderer.renderer', 'form.type', 'form.type_extension', 'form.type_guesser', diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 173165b03eb4a..ed85fa2ea7586 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -33,7 +33,6 @@ use Symfony\Component\DependencyInjection\Compiler\RegisterReverseContainerPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\ErrorHandler\ErrorHandler; -use Symfony\Component\ErrorRenderer\DependencyInjection\ErrorRendererPass; use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; use Symfony\Component\Form\DependencyInjection\FormPass; use Symfony\Component\HttpClient\DependencyInjection\HttpClientPass; @@ -92,7 +91,6 @@ public function build(ContainerBuilder $container) KernelEvents::FINISH_REQUEST, ]; - $container->addCompilerPass(new ErrorRendererPass()); $container->addCompilerPass(new LoggerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); $container->addCompilerPass(new RegisterControllerArgumentLocatorsPass()); $container->addCompilerPass(new RemoveEmptyControllerArgumentLocatorsPass(), PassConfig::TYPE_BEFORE_REMOVING); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml index 73b9eff6fe426..7276892940acb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml @@ -194,12 +194,6 @@ - - - - - - diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/error_renderer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/error_renderer.xml index 4e3fc596f943c..af80a51d3f67b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/error_renderer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/error_renderer.xml @@ -5,12 +5,7 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd"> - - - - - - + %kernel.debug% %kernel.charset% @@ -19,21 +14,19 @@ - - - %kernel.debug% + + + + + + + + + - - - - %kernel.debug% - %kernel.charset% - - - - - %kernel.debug% - + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml index 23da8b07bcb04..4698c505930a3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml @@ -12,6 +12,8 @@ + + @@ -59,6 +61,12 @@ + + %kernel.debug% + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index e3faf21102453..46b1cd80e13ba 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -21,7 +21,6 @@ "symfony/cache": "^4.4|^5.0", "symfony/config": "^4.3.4|^5.0", "symfony/dependency-injection": "^4.4|^5.0", - "symfony/error-renderer": "^4.4|^5.0", "symfony/http-foundation": "^4.4|^5.0", "symfony/http-kernel": "^4.4", "symfony/polyfill-mbstring": "~1.0", @@ -50,7 +49,7 @@ "symfony/process": "^3.4|^4.0|^5.0", "symfony/security-csrf": "^3.4|^4.0|^5.0", "symfony/security-http": "^3.4|^4.0|^5.0", - "symfony/serializer": "^4.3|^5.0", + "symfony/serializer": "^4.4|^5.0", "symfony/stopwatch": "^3.4|^4.0|^5.0", "symfony/translation": "^4.4|^5.0", "symfony/templating": "^3.4|^4.0|^5.0", diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginTest.php index 4fbdb27c65989..a69f5e591d1fa 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginTest.php @@ -70,6 +70,6 @@ public function testDefaultJsonLoginBadRequest() $this->assertSame(400, $response->getStatusCode()); $this->assertSame('application/json', $response->headers->get('Content-Type')); - $this->assertSame(['title' => 'Bad Request', 'status' => 400, 'detail' => 'Whoops, looks like something went wrong.'], json_decode($response->getContent(), true)); + $this->assertSame(['type' => 'https://tools.ietf.org/html/rfc2616#section-10', 'title' => 'An error occurred', 'status' => 400, 'detail' => 'Bad Request'], json_decode($response->getContent(), true)); } } diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml index d6ed10e896ff9..3522f27f13898 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml @@ -1,6 +1,9 @@ imports: - { resource: ./../config/framework.yml } +framework: + serializer: ~ + security: encoders: Symfony\Component\Security\Core\User\User: plaintext diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 7f3a55477b095..c9202bad57b70 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -35,6 +35,7 @@ "symfony/form": "^3.4|^4.0|^5.0", "symfony/framework-bundle": "^4.4|^5.0", "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/serializer": "^4.4|^5.0", "symfony/translation": "^3.4|^4.0|^5.0", "symfony/twig-bundle": "^4.4|^5.0", "symfony/twig-bridge": "^3.4|^4.0|^5.0", diff --git a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md index 99b14db8d52c5..780c46466dd36 100644 --- a/src/Symfony/Bundle/TwigBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/TwigBundle/CHANGELOG.md @@ -6,8 +6,7 @@ CHANGELOG * marked the `TemplateIterator` as `internal` * added HTML comment to beginning and end of `exception_full.html.twig` - * added a new `TwigHtmlErrorRenderer` for `html` format, integrated with the `ErrorRenderer` component - * deprecated `ExceptionController` and `PreviewErrorController` controllers, use `ErrorController` from the `HttpKernel` component instead + * deprecated `ExceptionController` and `PreviewErrorController` controllers, use `ErrorController` from the `HttpKernel` component instead * deprecated all built-in error templates in favor of the new error renderer mechanism * deprecated `twig.exception_controller` configuration option, set it to "null" and use `framework.error_controller` configuration instead diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml index fa886981e9000..425acdadef5a0 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml @@ -162,10 +162,9 @@ - - + - + %kernel.debug% diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php index 01abbd166715d..1a991ba694904 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/TwigExtensionTest.php @@ -23,6 +23,7 @@ use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; class TwigExtensionTest extends TestCase { @@ -302,6 +303,7 @@ public function testRuntimeLoader() $container->register('templating.locator', 'FooClass'); $container->register('templating.name_parser', 'FooClass'); $container->register('foo', '%foo%')->addTag('twig.runtime'); + $container->register('error_renderer.html', HtmlErrorRenderer::class); $container->addCompilerPass(new RuntimeLoaderPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->getCompilerPassConfig()->setRemovingPasses([]); $container->getCompilerPassConfig()->setAfterRemovingPasses([]); diff --git a/src/Symfony/Bundle/TwigBundle/Tests/Functional/EmptyAppTest.php b/src/Symfony/Bundle/TwigBundle/Tests/Functional/EmptyAppTest.php index 55851f1d6992e..a7f3bc27dadec 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/Functional/EmptyAppTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/Functional/EmptyAppTest.php @@ -15,7 +15,7 @@ use Symfony\Bundle\TwigBundle\TwigBundle; use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\ErrorRenderer\ErrorRenderer; +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpKernel\Kernel; @@ -65,7 +65,8 @@ public function registerContainerConfiguration(LoaderInterface $loader) 'strict_variables' => false, 'exception_controller' => null, ]); - $container->register('error_renderer', ErrorRenderer::class); + $container->register('error_renderer.html', HtmlErrorRenderer::class); + $container->setAlias('error_renderer', 'error_renderer.html'); $container->setParameter('debug.file_link_format', null); }); } diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index c10735b578eb5..d9bf24fa488f8 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -17,7 +17,6 @@ ], "require": { "php": "^7.1.3", - "symfony/error-renderer": "^4.4|^5.0", "symfony/twig-bridge": "^4.4|^5.0", "symfony/http-foundation": "^4.3|^5.0", "symfony/http-kernel": "^4.4", diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php index 168f2ca2ceeb0..095d65486aae7 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionController.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\WebProfilerBundle\Controller; -use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -42,11 +42,7 @@ public function __construct(Profiler $profiler = null, Environment $twig, bool $ $this->profiler = $profiler; $this->twig = $twig; $this->debug = $debug; - $this->errorRenderer = $errorRenderer; - - if (null === $errorRenderer) { - $this->errorRenderer = new HtmlErrorRenderer($debug, $this->twig->getCharset(), $fileLinkFormat); - } + $this->errorRenderer = $errorRenderer ?? new HtmlErrorRenderer($debug, $this->twig->getCharset(), $fileLinkFormat); } /** diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionPanelController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionPanelController.php index cff1f39400c16..4941208c88bc2 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionPanelController.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ExceptionPanelController.php @@ -11,7 +11,7 @@ namespace Symfony\Bundle\WebProfilerBundle\Controller; -use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Profiler\Profiler; @@ -25,12 +25,12 @@ */ class ExceptionPanelController { - private $htmlErrorRenderer; + private $errorRenderer; private $profiler; - public function __construct(HtmlErrorRenderer $htmlErrorRenderer, ?Profiler $profiler) + public function __construct(HtmlErrorRenderer $errorRenderer, Profiler $profiler = null) { - $this->htmlErrorRenderer = $htmlErrorRenderer; + $this->errorRenderer = $errorRenderer; $this->profiler = $profiler; } @@ -48,7 +48,7 @@ public function body(string $token): Response ->getException() ; - return new Response($this->htmlErrorRenderer->getBody($exception), 200, ['Content-Type' => 'text/html']); + return new Response($this->errorRenderer->getBody($exception), 200, ['Content-Type' => 'text/html']); } /** @@ -56,6 +56,6 @@ public function body(string $token): Response */ public function stylesheet(): Response { - return new Response($this->htmlErrorRenderer->getStylesheet(), 200, ['Content-Type' => 'text/css']); + return new Response($this->errorRenderer->getStylesheet(), 200, ['Content-Type' => 'text/css']); } } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml index 0d68bf00b04c6..89ecca5baf8e0 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.xml @@ -27,12 +27,12 @@ %kernel.debug% - + The "%service_id%" service is deprecated since Symfony 4.4, use the "web_profiler.controller.exception_panel" service instead. - + diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php index 9934077cf1aac..b94ef8045b684 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/DependencyInjection/WebProfilerExtensionTest.php @@ -17,7 +17,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; use Symfony\Component\EventDispatcher\EventDispatcher; @@ -54,7 +54,7 @@ protected function setUp(): void $this->kernel = $this->getMockBuilder('Symfony\\Component\\HttpKernel\\KernelInterface')->getMock(); $this->container = new ContainerBuilder(); - $this->container->register('error_renderer.renderer.html', HtmlErrorRenderer::class)->setPublic(true); + $this->container->register('error_handler.error_renderer.html', HtmlErrorRenderer::class)->setPublic(true); $this->container->register('event_dispatcher', EventDispatcher::class)->setPublic(true); $this->container->register('router', $this->getMockClass('Symfony\\Component\\Routing\\RouterInterface'))->setPublic(true); $this->container->register('twig', 'Twig\Environment')->setPublic(true); diff --git a/src/Symfony/Bundle/WebProfilerBundle/composer.json b/src/Symfony/Bundle/WebProfilerBundle/composer.json index bb131bea9f33c..e3f7bc010706d 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/composer.json +++ b/src/Symfony/Bundle/WebProfilerBundle/composer.json @@ -18,7 +18,6 @@ "require": { "php": "^7.1.3", "symfony/config": "^4.2|^5.0", - "symfony/error-renderer": "^4.4|^5.0", "symfony/http-kernel": "^4.4", "symfony/routing": "^3.4|^4.0|^5.0", "symfony/twig-bundle": "^4.2|^5.0", diff --git a/src/Symfony/Component/Debug/CHANGELOG.md b/src/Symfony/Component/Debug/CHANGELOG.md index b2e17f38211a8..989c1a72da7af 100644 --- a/src/Symfony/Component/Debug/CHANGELOG.md +++ b/src/Symfony/Component/Debug/CHANGELOG.md @@ -4,7 +4,7 @@ CHANGELOG 4.4.0 ----- - * deprecated `FlattenException`, use the `FlattenException` of the `ErrorRenderer` component + * deprecated `FlattenException`, use the `FlattenException` of the `ErrorHandler` component * deprecated the whole component in favor of the `ErrorHandler` component 4.3.0 diff --git a/src/Symfony/Component/Debug/Exception/FlattenException.php b/src/Symfony/Component/Debug/Exception/FlattenException.php index 4e213b1bd92ae..f55f71b0c9d25 100644 --- a/src/Symfony/Component/Debug/Exception/FlattenException.php +++ b/src/Symfony/Component/Debug/Exception/FlattenException.php @@ -21,7 +21,7 @@ * * @author Fabien Potencier * - * @deprecated since Symfony 4.4, use Symfony\Component\ErrorRenderer\Exception\FlattenException instead. + * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Exception\FlattenException instead. */ class FlattenException { diff --git a/src/Symfony/Component/ErrorHandler/BufferingLogger.php b/src/Symfony/Component/ErrorHandler/BufferingLogger.php index 49c838f272cd0..16e433dedf898 100644 --- a/src/Symfony/Component/ErrorHandler/BufferingLogger.php +++ b/src/Symfony/Component/ErrorHandler/BufferingLogger.php @@ -34,4 +34,25 @@ public function cleanLogs(): array return $logs; } + + public function __destruct() + { + foreach ($this->logs as [$level, $message, $context]) { + if (false !== strpos($message, '{')) { + foreach ($context as $key => $val) { + if (null === $val || is_scalar($val) || (\is_object($val) && \is_callable([$val, '__toString']))) { + $message = str_replace("{{$key}}", $val, $message); + } elseif ($val instanceof \DateTimeInterface) { + $message = str_replace("{{$key}}", $val->format(\DateTime::RFC3339), $message); + } elseif (\is_object($val)) { + $message = str_replace("{{$key}}", '[object '.\get_class($val).']', $message); + } else { + $message = str_replace("{{$key}}", '['.\gettype($val).']', $message); + } + } + } + + error_log(sprintf('%s [%s] %s', date(\DateTime::RFC3339), $level, $message)); + } + } } diff --git a/src/Symfony/Component/ErrorHandler/Debug.php b/src/Symfony/Component/ErrorHandler/Debug.php index 6f75dbf60e38a..f95334e01ab58 100644 --- a/src/Symfony/Component/ErrorHandler/Debug.php +++ b/src/Symfony/Component/ErrorHandler/Debug.php @@ -18,39 +18,19 @@ */ class Debug { - private static $enabled = false; - - /** - * Enables the debug tools. - * - * This method registers an error handler and an exception handler. - */ - public static function enable(int $errorReportingLevel = E_ALL, bool $displayErrors = true): void + public static function enable(): ErrorHandler { - if (static::$enabled) { - return; - } - - static::$enabled = true; - - if (null !== $errorReportingLevel) { - error_reporting($errorReportingLevel); - } else { - error_reporting(E_ALL); - } + error_reporting(-1); if (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { ini_set('display_errors', 0); - } elseif ($displayErrors && (!filter_var(ini_get('log_errors'), FILTER_VALIDATE_BOOLEAN) || ini_get('error_log'))) { + } elseif (!filter_var(ini_get('log_errors'), FILTER_VALIDATE_BOOLEAN) || ini_get('error_log')) { // CLI - display errors only if they're not already logged to STDERR ini_set('display_errors', 1); } - if ($displayErrors) { - ErrorHandler::register(new ErrorHandler(new BufferingLogger())); - } else { - ErrorHandler::register()->throwAt(0, true); - } DebugClassLoader::enable(); + + return ErrorHandler::register(new ErrorHandler(new BufferingLogger())); } } diff --git a/src/Symfony/Component/ErrorHandler/ErrorHandler.php b/src/Symfony/Component/ErrorHandler/ErrorHandler.php index ab6df70bd8da9..6df9cdec22964 100644 --- a/src/Symfony/Component/ErrorHandler/ErrorHandler.php +++ b/src/Symfony/Component/ErrorHandler/ErrorHandler.php @@ -19,9 +19,9 @@ use Symfony\Component\ErrorHandler\ErrorEnhancer\ErrorEnhancerInterface; use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedFunctionErrorEnhancer; use Symfony\Component\ErrorHandler\ErrorEnhancer\UndefinedMethodErrorEnhancer; +use Symfony\Component\ErrorHandler\ErrorRenderer\CliErrorRenderer; +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; -use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; /** * A generic ErrorHandler for the PHP engine. @@ -45,10 +45,8 @@ * * @author Nicolas Grekas * @author Grégoire Pineau - * - * @final since Symfony 4.3 */ -class ErrorHandler +final class ErrorHandler { private $levels = [ E_DEPRECATED => 'Deprecated', @@ -145,10 +143,8 @@ public static function register(self $handler = null, bool $replace = true): sel $handler->setExceptionHandler($p); $prev[0]->setExceptionHandler($p); } - } elseif (null === $prev && !\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { - $handler->setExceptionHandler([$handler, 'sendPhpResponse']); } else { - $handler->setExceptionHandler($prev); + $handler->setExceptionHandler($prev ?? [$handler, 'renderException']); } $handler->throwAt(E_ALL & $handler->thrownErrors, true); @@ -280,7 +276,7 @@ public function setLoggers(array $loggers): array /** * Sets a user exception handler. * - * @param callable|null $handler A handler that must support \Throwable instances that will be called on Exception + * @param callable(\Throwable $e)|null $handler * * @return callable|null The previous exception handler */ @@ -583,11 +579,12 @@ public function handleException(\Throwable $exception) } $exceptionHandler = $this->exceptionHandler; - if ((!\is_array($exceptionHandler) || !$exceptionHandler[0] instanceof self || 'sendPhpResponse' !== $exceptionHandler[1]) && !\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) { - $this->exceptionHandler = [$this, 'sendPhpResponse']; - } else { + $this->exceptionHandler = [$this, 'renderException']; + + if (null === $exceptionHandler || $exceptionHandler === $this->exceptionHandler) { $this->exceptionHandler = null; } + try { if (null !== $exceptionHandler) { return $exceptionHandler($exception); @@ -600,7 +597,14 @@ public function handleException(\Throwable $exception) throw $exception; // Give back $exception to the native handler } - $this->handleException($handlerException); + $loggedErrors = $this->loggedErrors; + $this->loggedErrors = $exception === $handlerException ? 0 : $this->loggedErrors; + + try { + $this->handleException($handlerException); + } finally { + $this->loggedErrors = $loggedErrors; + } } /** @@ -684,36 +688,26 @@ public static function handleFatalError(array $error = null): void } /** - * Sends the error associated with the given Exception as a plain PHP response. + * Renders the given exception. * - * As this method is mainly called during Kernel boot, where nothing is yet - * available, the Response content is always HTML. + * As this method is mainly called during boot where nothing is yet available, + * the output is always either HTML or CLI depending where PHP runs. */ - private function sendPhpResponse(\Throwable $exception) + private function renderException(\Throwable $exception): void { - $charset = ini_get('default_charset') ?: 'UTF-8'; - $statusCode = 500; - $headers = []; - - if (class_exists(HtmlErrorRenderer::class)) { - $exception = FlattenException::createFromThrowable($exception); - $statusCode = $exception->getStatusCode(); - $headers = $exception->getHeaders(); - $response = (new HtmlErrorRenderer(0 !== $this->scopedErrors))->render($exception); - } else { - $message = htmlspecialchars($exception->getMessage(), ENT_COMPAT | ENT_SUBSTITUTE, $charset); - $response = sprintf('%s', $charset, $message); - } + $renderer = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliErrorRenderer() : new HtmlErrorRenderer(0 !== $this->scopedErrors); + + $exception = $renderer->render($exception); if (!headers_sent()) { - header(sprintf('HTTP/1.0 %s', $statusCode)); - foreach ($headers as $name => $value) { + http_response_code($exception->getStatusCode()); + + foreach ($exception->getHeaders() as $name => $value) { header($name.': '.$value, false); } - header('Content-Type: text/html; charset='.$charset); } - echo $response; + echo $exception->getAsString(); } /** diff --git a/src/Symfony/Component/ErrorHandler/ErrorRenderer/CliErrorRenderer.php b/src/Symfony/Component/ErrorHandler/ErrorRenderer/CliErrorRenderer.php new file mode 100644 index 0000000000000..aa132d1cfaccb --- /dev/null +++ b/src/Symfony/Component/ErrorHandler/ErrorRenderer/CliErrorRenderer.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorRenderer; + +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * @author Nicolas Grekas + */ +class CliErrorRenderer implements ErrorRendererInterface +{ + /** + * {@inheritdoc} + */ + public function render(\Throwable $exception): FlattenException + { + $cloner = new VarCloner(); + $dumper = new class() extends CliDumper { + protected function supportsColors(): bool + { + $outputStream = $this->outputStream; + $this->outputStream = fopen('php://stdout', 'w'); + + try { + return parent::supportsColors(); + } finally { + $this->outputStream = $outputStream; + } + } + }; + + return FlattenException::createFromThrowable($exception) + ->setAsString($dumper->dump($cloner->cloneVar($exception), true)); + } +} diff --git a/src/Symfony/Component/ErrorHandler/ErrorRenderer/ErrorRendererInterface.php b/src/Symfony/Component/ErrorHandler/ErrorRenderer/ErrorRendererInterface.php new file mode 100644 index 0000000000000..aba196603fdc9 --- /dev/null +++ b/src/Symfony/Component/ErrorHandler/ErrorRenderer/ErrorRendererInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorRenderer; + +use Symfony\Component\ErrorHandler\Exception\FlattenException; + +/** + * Formats an exception to be used as response content. + * + * @author Yonel Ceruto + */ +interface ErrorRendererInterface +{ + /** + * Renders a Throwable as a FlattenException. + */ + public function render(\Throwable $exception): FlattenException; +} diff --git a/src/Symfony/Component/ErrorRenderer/ErrorRenderer/HtmlErrorRenderer.php b/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php similarity index 96% rename from src/Symfony/Component/ErrorRenderer/ErrorRenderer/HtmlErrorRenderer.php rename to src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php index a9d0af516da13..68a9d6bb14dc6 100644 --- a/src/Symfony/Component/ErrorRenderer/ErrorRenderer/HtmlErrorRenderer.php +++ b/src/Symfony/Component/ErrorHandler/ErrorRenderer/HtmlErrorRenderer.php @@ -9,10 +9,10 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\ErrorRenderer\ErrorRenderer; +namespace Symfony\Component\ErrorHandler\ErrorRenderer; use Psr\Log\LoggerInterface; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; +use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; @@ -52,23 +52,17 @@ public function __construct(bool $debug = false, string $charset = null, $fileLi /** * {@inheritdoc} */ - public static function getFormat(): string + public function render(\Throwable $exception): FlattenException { - return 'html'; - } - - /** - * {@inheritdoc} - */ - public function render(FlattenException $exception): string - { - return $this->renderException($exception); + $exception = FlattenException::createFromThrowable($exception, null, [ + 'Content-Type' => 'text/html; charset='.$this->charset, + ]); + + return $exception->setAsString($this->renderException($exception)); } /** * Gets the HTML content associated with the given exception. - * - * @internal */ public function getBody(FlattenException $exception): string { @@ -77,8 +71,6 @@ public function getBody(FlattenException $exception): string /** * Gets the stylesheet associated with the given exception. - * - * @internal */ public function getStylesheet(): string { @@ -91,11 +83,10 @@ public function getStylesheet(): string private function renderException(FlattenException $exception, string $debugTemplate = 'views/exception_full.html.php'): string { - $debug = $this->debug && ($exception->getHeaders()['X-Debug'] ?? true); - $statusText = $this->escape($exception->getTitle()); + $statusText = $this->escape($exception->getStatusText()); $statusCode = $this->escape($exception->getStatusCode()); - if (!$debug) { + if (!$this->debug) { return $this->include('views/error.html.php', [ 'statusText' => $statusText, 'statusCode' => $statusCode, @@ -111,7 +102,7 @@ private function renderException(FlattenException $exception, string $debugTempl 'statusText' => $statusText, 'statusCode' => $statusCode, 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null, - 'currentContent' => $request ? $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level')) : null, + 'currentContent' => $request ? $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level', -1)) : '', ]); } @@ -321,7 +312,7 @@ private function include(string $name, array $context = []): string { extract($context, EXTR_SKIP); ob_start(); - include __DIR__.'/../Resources/'.$name; + include __DIR__ . '/../Resources/' .$name; return trim(ob_get_clean()); } diff --git a/src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php b/src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php new file mode 100644 index 0000000000000..c055bc3b65db3 --- /dev/null +++ b/src/Symfony/Component/ErrorHandler/ErrorRenderer/SerializerErrorRenderer.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\ErrorRenderer; + +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Serializer\Exception\NotEncodableValueException; +use Symfony\Component\Serializer\SerializerInterface; + +/** + * Formats an exception using Serializer for rendering. + * + * @author Nicolas Grekas + */ +class SerializerErrorRenderer implements ErrorRendererInterface +{ + private $serializer; + private $format; + private $fallbackErrorRenderer; + + /** + * @param string|callable(FlattenException) $format The format as a string or a callable that should return it + */ + public function __construct(SerializerInterface $serializer, $format, ErrorRendererInterface $fallbackErrorRenderer = null) + { + if (!\is_string($format) && !\is_callable($format)) { + throw new \TypeError(sprintf('Argument 2 passed to %s() must be a string or a callable, %s given.', __METHOD__, \is_object($format) ? \get_class($format) : \gettype($format))); + } + + $this->serializer = $serializer; + $this->format = $format; + $this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); + } + + /** + * {@inheritdoc} + */ + public function render(\Throwable $exception): FlattenException + { + $flattenException = FlattenException::createFromThrowable($exception); + + try { + $format = \is_string($this->format) ? $this->format : ($this->format)($flattenException); + + return $flattenException->setAsString($this->serializer->serialize($flattenException, $format, ['exception' => $exception])); + } catch (NotEncodableValueException $e) { + return $this->fallbackErrorRenderer->render($exception); + } + } + + public static function getPreferredFormat(RequestStack $requestStack): \Closure + { + return static function () use ($requestStack) { + if (!$request = $requestStack->getCurrentRequest()) { + throw new NotEncodableValueException(); + } + + return $request->getPreferredFormat(); + }; + } +} diff --git a/src/Symfony/Component/ErrorRenderer/Exception/FlattenException.php b/src/Symfony/Component/ErrorHandler/Exception/FlattenException.php similarity index 93% rename from src/Symfony/Component/ErrorRenderer/Exception/FlattenException.php rename to src/Symfony/Component/ErrorHandler/Exception/FlattenException.php index dd86f5b74bb07..61af8a7e1d8a7 100644 --- a/src/Symfony/Component/ErrorRenderer/Exception/FlattenException.php +++ b/src/Symfony/Component/ErrorHandler/Exception/FlattenException.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\ErrorRenderer\Exception; +namespace Symfony\Component\ErrorHandler\Exception; use Symfony\Component\Debug\Exception\FlattenException as LegacyFlattenException; use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface; @@ -25,7 +25,6 @@ */ class FlattenException extends LegacyFlattenException { - private $title; private $message; private $code; private $previous; @@ -33,9 +32,11 @@ class FlattenException extends LegacyFlattenException private $traceAsString; private $class; private $statusCode; + private $statusText; private $headers; private $file; private $line; + private $asString; public static function create(\Exception $exception, $statusCode = null, array $headers = []): self { @@ -60,12 +61,12 @@ public static function createFromThrowable(\Throwable $exception, int $statusCod } if (class_exists(Response::class) && isset(Response::$statusTexts[$statusCode])) { - $title = Response::$statusTexts[$statusCode]; + $statusText = Response::$statusTexts[$statusCode]; } else { - $title = 'Whoops, looks like something went wrong.'; + $statusText = 'Whoops, looks like something went wrong.'; } - $e->setTitle($title); + $e->setStatusText($statusText); $e->setStatusCode($statusCode); $e->setHeaders($headers); $e->setTraceFromThrowable($exception); @@ -171,14 +172,14 @@ public function setLine($line): self return $this; } - public function getTitle() + public function getStatusText() { - return $this->title; + return $this->statusText; } - public function setTitle(string $title): self + public function setStatusText(string $statusText): self { - $this->title = $title; + $this->statusText = $statusText; return $this; } @@ -355,8 +356,19 @@ public function getTraceAsString() return $this->traceAsString; } + public function setAsString(?string $asString) + { + $this->asString = $asString; + + return $this; + } + public function getAsString() { + if (null !== $this->asString) { + return $this->asString; + } + $message = ''; $next = false; diff --git a/src/Symfony/Component/ErrorRenderer/Resources/assets/css/error.css b/src/Symfony/Component/ErrorHandler/Resources/assets/css/error.css similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/assets/css/error.css rename to src/Symfony/Component/ErrorHandler/Resources/assets/css/error.css diff --git a/src/Symfony/Component/ErrorRenderer/Resources/assets/css/exception.css b/src/Symfony/Component/ErrorHandler/Resources/assets/css/exception.css similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/assets/css/exception.css rename to src/Symfony/Component/ErrorHandler/Resources/assets/css/exception.css diff --git a/src/Symfony/Component/ErrorRenderer/Resources/assets/css/exception_full.css b/src/Symfony/Component/ErrorHandler/Resources/assets/css/exception_full.css similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/assets/css/exception_full.css rename to src/Symfony/Component/ErrorHandler/Resources/assets/css/exception_full.css diff --git a/src/Symfony/Component/ErrorRenderer/Resources/assets/images/chevron-right.svg b/src/Symfony/Component/ErrorHandler/Resources/assets/images/chevron-right.svg similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/assets/images/chevron-right.svg rename to src/Symfony/Component/ErrorHandler/Resources/assets/images/chevron-right.svg diff --git a/src/Symfony/Component/ErrorRenderer/Resources/assets/images/favicon.png.base64 b/src/Symfony/Component/ErrorHandler/Resources/assets/images/favicon.png.base64 similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/assets/images/favicon.png.base64 rename to src/Symfony/Component/ErrorHandler/Resources/assets/images/favicon.png.base64 diff --git a/src/Symfony/Component/ErrorRenderer/Resources/assets/images/icon-book.svg b/src/Symfony/Component/ErrorHandler/Resources/assets/images/icon-book.svg similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/assets/images/icon-book.svg rename to src/Symfony/Component/ErrorHandler/Resources/assets/images/icon-book.svg diff --git a/src/Symfony/Component/ErrorRenderer/Resources/assets/images/icon-minus-square-o.svg b/src/Symfony/Component/ErrorHandler/Resources/assets/images/icon-minus-square-o.svg similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/assets/images/icon-minus-square-o.svg rename to src/Symfony/Component/ErrorHandler/Resources/assets/images/icon-minus-square-o.svg diff --git a/src/Symfony/Component/ErrorRenderer/Resources/assets/images/icon-minus-square.svg b/src/Symfony/Component/ErrorHandler/Resources/assets/images/icon-minus-square.svg similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/assets/images/icon-minus-square.svg rename to src/Symfony/Component/ErrorHandler/Resources/assets/images/icon-minus-square.svg diff --git a/src/Symfony/Component/ErrorRenderer/Resources/assets/images/icon-plus-square-o.svg b/src/Symfony/Component/ErrorHandler/Resources/assets/images/icon-plus-square-o.svg similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/assets/images/icon-plus-square-o.svg rename to src/Symfony/Component/ErrorHandler/Resources/assets/images/icon-plus-square-o.svg diff --git a/src/Symfony/Component/ErrorRenderer/Resources/assets/images/icon-plus-square.svg b/src/Symfony/Component/ErrorHandler/Resources/assets/images/icon-plus-square.svg similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/assets/images/icon-plus-square.svg rename to src/Symfony/Component/ErrorHandler/Resources/assets/images/icon-plus-square.svg diff --git a/src/Symfony/Component/ErrorRenderer/Resources/assets/images/icon-support.svg b/src/Symfony/Component/ErrorHandler/Resources/assets/images/icon-support.svg similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/assets/images/icon-support.svg rename to src/Symfony/Component/ErrorHandler/Resources/assets/images/icon-support.svg diff --git a/src/Symfony/Component/ErrorRenderer/Resources/assets/images/symfony-ghost.svg.php b/src/Symfony/Component/ErrorHandler/Resources/assets/images/symfony-ghost.svg.php similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/assets/images/symfony-ghost.svg.php rename to src/Symfony/Component/ErrorHandler/Resources/assets/images/symfony-ghost.svg.php diff --git a/src/Symfony/Component/ErrorRenderer/Resources/assets/images/symfony-logo.svg b/src/Symfony/Component/ErrorHandler/Resources/assets/images/symfony-logo.svg similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/assets/images/symfony-logo.svg rename to src/Symfony/Component/ErrorHandler/Resources/assets/images/symfony-logo.svg diff --git a/src/Symfony/Component/ErrorRenderer/Resources/assets/js/exception.js b/src/Symfony/Component/ErrorHandler/Resources/assets/js/exception.js similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/assets/js/exception.js rename to src/Symfony/Component/ErrorHandler/Resources/assets/js/exception.js diff --git a/src/Symfony/Component/ErrorHandler/Resources/stubs/Debug.php b/src/Symfony/Component/ErrorHandler/Resources/stubs/Debug.php deleted file mode 100644 index 2a3f8bed79328..0000000000000 --- a/src/Symfony/Component/ErrorHandler/Resources/stubs/Debug.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Debug; - -if (!class_exists(Debug::class, false)) { - class_alias(\Symfony\Component\ErrorHandler\Debug::class, Debug::class); -} - -if (false) { - /** - * @deprecated since Symfony 4.4, use Symfony\Component\ErrorHandler\Debug instead. - */ - class Debug extends \Symfony\Component\ErrorHandler\Debug - { - } -} diff --git a/src/Symfony/Component/ErrorRenderer/Resources/views/error.html.php b/src/Symfony/Component/ErrorHandler/Resources/views/error.html.php similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/views/error.html.php rename to src/Symfony/Component/ErrorHandler/Resources/views/error.html.php diff --git a/src/Symfony/Component/ErrorRenderer/Resources/views/exception.html.php b/src/Symfony/Component/ErrorHandler/Resources/views/exception.html.php similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/views/exception.html.php rename to src/Symfony/Component/ErrorHandler/Resources/views/exception.html.php diff --git a/src/Symfony/Component/ErrorHandler/Resources/views/exception_full.html.php b/src/Symfony/Component/ErrorHandler/Resources/views/exception_full.html.php new file mode 100644 index 0000000000000..4d46d59de5ff0 --- /dev/null +++ b/src/Symfony/Component/ErrorHandler/Resources/views/exception_full.html.php @@ -0,0 +1,43 @@ + + + + + + + + <?= $_message; ?> + + + + + + +
+ +
+ + + include('views/exception.html.php', $context); ?> + + + + + diff --git a/src/Symfony/Component/ErrorRenderer/Resources/views/logs.html.php b/src/Symfony/Component/ErrorHandler/Resources/views/logs.html.php similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/views/logs.html.php rename to src/Symfony/Component/ErrorHandler/Resources/views/logs.html.php diff --git a/src/Symfony/Component/ErrorRenderer/Resources/views/trace.html.php b/src/Symfony/Component/ErrorHandler/Resources/views/trace.html.php similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/views/trace.html.php rename to src/Symfony/Component/ErrorHandler/Resources/views/trace.html.php diff --git a/src/Symfony/Component/ErrorRenderer/Resources/views/traces.html.php b/src/Symfony/Component/ErrorHandler/Resources/views/traces.html.php similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/views/traces.html.php rename to src/Symfony/Component/ErrorHandler/Resources/views/traces.html.php diff --git a/src/Symfony/Component/ErrorRenderer/Resources/views/traces_text.html.php b/src/Symfony/Component/ErrorHandler/Resources/views/traces_text.html.php similarity index 100% rename from src/Symfony/Component/ErrorRenderer/Resources/views/traces_text.html.php rename to src/Symfony/Component/ErrorHandler/Resources/views/traces_text.html.php diff --git a/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php index 1c170732e8983..907294cd770b8 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/ErrorHandlerTest.php @@ -50,23 +50,13 @@ public function testRegister() $h = set_error_handler('var_dump'); restore_error_handler(); $this->assertSame([$newHandler, 'handleError'], $h); - } catch (\Exception $e) { + } finally { + restore_error_handler(); + restore_exception_handler(); } - + } finally { restore_error_handler(); restore_exception_handler(); - - if (isset($e)) { - throw $e; - } - } catch (\Exception $e) { - } - - restore_error_handler(); - restore_exception_handler(); - - if (isset($e)) { - throw $e; } } @@ -86,11 +76,9 @@ public function testErrorGetLast() 'line' => __LINE__ - 5, ]; $this->assertSame($expected, error_get_last()); - } catch (\Exception $e) { + } finally { restore_error_handler(); restore_exception_handler(); - - throw $e; } } @@ -323,14 +311,9 @@ public function testHandleError() unset($undefVar); $line = __LINE__ + 1; @$undefVar++; - - restore_error_handler(); - restore_exception_handler(); - } catch (\Exception $e) { + } finally { restore_error_handler(); restore_exception_handler(); - - throw $e; } } @@ -406,6 +389,7 @@ public function testHandleException(string $expectedMessage, \Throwable $excepti ; $handler->setDefaultLogger($logger, E_ERROR); + $handler->setExceptionHandler(null); try { $handler->handleException($exception); @@ -530,16 +514,12 @@ public function testHandleFatalError() ; $handler->setDefaultLogger($logger, E_PARSE); + $handler->setExceptionHandler(null); $handler->handleFatalError($error); - - restore_error_handler(); - restore_exception_handler(); - } catch (\Exception $e) { + } finally { restore_error_handler(); restore_exception_handler(); - - throw $e; } } @@ -563,6 +543,7 @@ public function testCustomExceptionHandler() $this->expectException('Exception'); $handler = new ErrorHandler(); $handler->setExceptionHandler(function ($e) use ($handler) { + $handler->setExceptionHandler(null); $handler->handleException($e); }); @@ -572,7 +553,7 @@ public function testCustomExceptionHandler() public function testSendPhpResponse() { $handler = new ErrorHandler(); - $handler->setExceptionHandler([$handler, 'sendPhpResponse']); + $handler->setExceptionHandler([$handler, 'renderException']); ob_start(); $handler->handleException(new \RuntimeException('Class Foo not found')); diff --git a/src/Symfony/Component/ErrorRenderer/Tests/ErrorRenderer/HtmlErrorRendererTest.php b/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/HtmlErrorRendererTest.php similarity index 51% rename from src/Symfony/Component/ErrorRenderer/Tests/ErrorRenderer/HtmlErrorRendererTest.php rename to src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/HtmlErrorRendererTest.php index 4868d215c47ce..b140ca6e52f74 100644 --- a/src/Symfony/Component/ErrorRenderer/Tests/ErrorRenderer/HtmlErrorRendererTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/HtmlErrorRendererTest.php @@ -9,21 +9,20 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\ErrorRenderer\Tests\ErrorRenderer; +namespace Symfony\Component\ErrorHandler\Tests\ErrorRenderer; use PHPUnit\Framework\TestCase; -use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface; -use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; +use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\ErrorHandler\Exception\FlattenException; class HtmlErrorRendererTest extends TestCase { /** * @dataProvider getRenderData */ - public function testRender(FlattenException $exception, ErrorRendererInterface $errorRenderer, string $expected) + public function testRender(\Throwable $exception, HtmlErrorRenderer $errorRenderer, string $expected) { - $this->assertStringMatchesFormat($expected, $errorRenderer->render($exception)); + $this->assertStringMatchesFormat($expected, $errorRenderer->render($exception)->getAsString()); } public function getRenderData(): iterable @@ -45,25 +44,13 @@ public function getRenderData(): iterable HTML; yield '->render() returns the HTML content WITH stack traces in debug mode' => [ - FlattenException::createFromThrowable(new \RuntimeException('Foo')), + new \RuntimeException('Foo'), new HtmlErrorRenderer(true), $expectedDebug, ]; yield '->render() returns the HTML content WITHOUT stack traces in non-debug mode' => [ - FlattenException::createFromThrowable(new \RuntimeException('Foo')), - new HtmlErrorRenderer(false), - $expectedNonDebug, - ]; - - yield '->render() returns the HTML content WITHOUT stack traces in debug mode FORCING non-debug via X-Debug header' => [ - FlattenException::createFromThrowable(new \RuntimeException('Foo'), null, ['X-Debug' => false]), - new HtmlErrorRenderer(true), - $expectedNonDebug, - ]; - - yield '->render() returns the HTML content WITHOUT stack traces in non-debug mode EVEN FORCING debug via X-Debug header' => [ - FlattenException::createFromThrowable(new \RuntimeException('Foo'), null, ['X-Debug' => true]), + new \RuntimeException('Foo'), new HtmlErrorRenderer(false), $expectedNonDebug, ]; diff --git a/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php b/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php new file mode 100644 index 0000000000000..a1698e0a88cd9 --- /dev/null +++ b/src/Symfony/Component/ErrorHandler/Tests/ErrorRenderer/SerializerErrorRendererTest.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\ErrorHandler\Tests\ErrorRenderer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ErrorHandler\ErrorRenderer\SerializerErrorRenderer; +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Normalizer\ProblemNormalizer; +use Symfony\Component\Serializer\Serializer; + +class SerializerErrorRendererTest extends TestCase +{ + public function testDefaultContent() + { + $errorRenderer = new SerializerErrorRenderer(new Serializer(), 'html'); + + self::assertStringContainsString('

The server returned a "500 Internal Server Error".

', $errorRenderer->render(new \RuntimeException())->getAsString()); + } + + public function testSerializerContent() + { + $exception = new \RuntimeException('Foo'); + $errorRenderer = new SerializerErrorRenderer( + new Serializer([new ProblemNormalizer()], [new JsonEncoder()]), + function () { return 'json'; } + ); + + $this->assertSame('{"type":"https:\/\/tools.ietf.org\/html\/rfc2616#section-10","title":"An error occurred","status":500,"detail":"Internal Server Error"}', $errorRenderer->render($exception)->getAsString()); + } +} diff --git a/src/Symfony/Component/ErrorRenderer/Tests/Exception/FlattenExceptionTest.php b/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php similarity index 99% rename from src/Symfony/Component/ErrorRenderer/Tests/Exception/FlattenExceptionTest.php rename to src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php index dad78d152540d..3413bd484e04c 100644 --- a/src/Symfony/Component/ErrorRenderer/Tests/Exception/FlattenExceptionTest.php +++ b/src/Symfony/Component/ErrorHandler/Tests/Exception/FlattenExceptionTest.php @@ -9,10 +9,10 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\ErrorRenderer\Tests\Exception; +namespace Symfony\Component\ErrorHandler\Tests\Exception; use PHPUnit\Framework\TestCase; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; +use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; diff --git a/src/Symfony/Component/ErrorHandler/Tests/phpt/exception_rethrown.phpt b/src/Symfony/Component/ErrorHandler/Tests/phpt/exception_rethrown.phpt index 82a9006d840f9..8e6c5222d03f6 100644 --- a/src/Symfony/Component/ErrorHandler/Tests/phpt/exception_rethrown.phpt +++ b/src/Symfony/Component/ErrorHandler/Tests/phpt/exception_rethrown.phpt @@ -16,20 +16,23 @@ if (true) { { public function log($level, $message, array $context = []) { - echo $message, "\n"; + echo 'LOG: ', $message, "\n"; } } } -set_exception_handler(function ($e) { echo 123; throw $e; }); +$_SERVER['NO_COLOR'] = '1'; +set_exception_handler(function ($e) { echo "EHLO\n"; throw $e; }); ErrorHandler::register()->setDefaultLogger(new TestLogger()); -ini_set('display_errors', 1); throw new \Exception('foo'); ?> --EXPECTF-- -Uncaught Exception: foo -123 -Fatal error: Uncaught %s:25 -Stack trace: -%a +LOG: Uncaught Exception: foo +EHLO +Exception {%S + #message: "foo" + #code: 0 + #file: "%s" + #line: 25 +} diff --git a/src/Symfony/Component/ErrorHandler/composer.json b/src/Symfony/Component/ErrorHandler/composer.json index dd5e0d0679111..cfe822f4e8b39 100644 --- a/src/Symfony/Component/ErrorHandler/composer.json +++ b/src/Symfony/Component/ErrorHandler/composer.json @@ -17,20 +17,16 @@ ], "require": { "php": "^7.1.3", - "psr/log": "~1.0" + "psr/log": "~1.0", + "symfony/debug": "^4.4", + "symfony/var-dumper": "^4.4|^5.0" }, "require-dev": { - "symfony/http-kernel": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/error-renderer": "For better error rendering" - }, - "conflict": { - "symfony/http-kernel": "<3.4" + "symfony/http-kernel": "^4.4|^5.0", + "symfony/serializer": "^4.4|^5.0" }, "autoload": { "psr-4": { "Symfony\\Component\\ErrorHandler\\": "" }, - "classmap": [ "Resources/stubs/Debug.php" ], "exclude-from-classmap": [ "/Tests/" ] diff --git a/src/Symfony/Component/ErrorRenderer/.gitattributes b/src/Symfony/Component/ErrorRenderer/.gitattributes deleted file mode 100644 index ebb9287043dc4..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -/Tests export-ignore -/phpunit.xml.dist export-ignore -/.gitignore export-ignore diff --git a/src/Symfony/Component/ErrorRenderer/.gitignore b/src/Symfony/Component/ErrorRenderer/.gitignore deleted file mode 100644 index c49a5d8df5c65..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/src/Symfony/Component/ErrorRenderer/CHANGELOG.md b/src/Symfony/Component/ErrorRenderer/CHANGELOG.md deleted file mode 100644 index 094072510d707..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/CHANGELOG.md +++ /dev/null @@ -1,7 +0,0 @@ -CHANGELOG -========= - -4.4.0 ------ - - * added the component diff --git a/src/Symfony/Component/ErrorRenderer/Command/DebugCommand.php b/src/Symfony/Component/ErrorRenderer/Command/DebugCommand.php deleted file mode 100644 index 58cb57b68cbf5..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/Command/DebugCommand.php +++ /dev/null @@ -1,125 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorRenderer\Command; - -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; -use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; - -/** - * A console command for retrieving information about error renderers. - * - * @author Yonel Ceruto - * - * @internal - */ -class DebugCommand extends Command -{ - protected static $defaultName = 'debug:error-renderer'; - - private $renderers; - private $fileLinkFormatter; - - /** - * @param ErrorRendererInterface[] $renderers - */ - public function __construct(array $renderers, FileLinkFormatter $fileLinkFormatter = null) - { - $this->renderers = $renderers; - $this->fileLinkFormatter = $fileLinkFormatter; - - parent::__construct(); - } - - /** - * {@inheritdoc} - */ - protected function configure(): void - { - $this - ->addArgument('format', InputArgument::OPTIONAL, sprintf('Outputs a sample in a specific format (one of %s)', implode(', ', array_keys($this->renderers)))) - ->setDescription('Displays all available error renderers and their formats.') - ->setHelp(<<<'EOF' -The %command.name% command displays all available error renderers and -their formats: - - php %command.full_name% - -Or output a sample in a specific format: - - php %command.full_name% json - -EOF - ) - ; - } - - /** - * {@inheritdoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - $io = new SymfonyStyle($input, $output); - $renderers = $this->renderers; - - if ($format = $input->getArgument('format')) { - if (!isset($renderers[$format])) { - throw new InvalidArgumentException(sprintf('No error renderer found for format "%s". Known format are %s.', $format, implode(', ', array_keys($this->renderers)))); - } - - $exception = FlattenException::createFromThrowable(new \Exception('This is a sample exception.'), 500, ['X-Debug' => false]); - $io->writeln($renderers[$format]->render($exception)); - } else { - $tableRows = []; - foreach ($renderers as $format => $renderer) { - $tableRows[] = [sprintf('%s', $format), $this->formatClassLink(\get_class($renderer))]; - } - - $io->title('Error Renderers'); - $io->text('The following error renderers are available:'); - $io->newLine(); - $io->table(['Format', 'Class'], $tableRows); - } - - return 0; - } - - private function formatClassLink(string $class): string - { - if ('' === $fileLink = $this->getFileLink($class)) { - return $class; - } - - return sprintf('%s', $fileLink, $class); - } - - private function getFileLink(string $class): string - { - if (null === $this->fileLinkFormatter) { - return ''; - } - - try { - $r = new \ReflectionClass($class); - } catch (\ReflectionException $e) { - return ''; - } - - return $this->fileLinkFormatter->format($r->getFileName(), $r->getStartLine()); - } -} diff --git a/src/Symfony/Component/ErrorRenderer/DependencyInjection/ErrorRendererPass.php b/src/Symfony/Component/ErrorRenderer/DependencyInjection/ErrorRendererPass.php deleted file mode 100644 index 1e297b7de13b3..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/DependencyInjection/ErrorRendererPass.php +++ /dev/null @@ -1,71 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorRenderer\DependencyInjection; - -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; -use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface; - -/** - * @author Yonel Ceruto - */ -class ErrorRendererPass implements CompilerPassInterface -{ - private $rendererService; - private $rendererTag; - private $debugCommandService; - - public function __construct(string $rendererService = 'error_renderer', string $rendererTag = 'error_renderer.renderer', string $debugCommandService = 'console.command.error_renderer_debug') - { - $this->rendererService = $rendererService; - $this->rendererTag = $rendererTag; - $this->debugCommandService = $debugCommandService; - } - - /** - * {@inheritdoc} - */ - public function process(ContainerBuilder $container) - { - if (!$container->hasDefinition($this->rendererService)) { - return; - } - - $renderers = []; - foreach ($container->findTaggedServiceIds($this->rendererTag, true) as $serviceId => $tags) { - /** @var ErrorRendererInterface $class */ - $class = $container->getDefinition($serviceId)->getClass(); - - foreach ($tags as $tag) { - $format = $tag['format'] ?? $class::getFormat(); - $priority = $tag['priority'] ?? 0; - if (!isset($renderers[$priority][$format])) { - $renderers[$priority][$format] = new Reference($serviceId); - } - } - } - - if ($renderers) { - ksort($renderers); - $renderers = array_merge(...$renderers); - } - - $definition = $container->getDefinition($this->rendererService); - $definition->replaceArgument(0, ServiceLocatorTagPass::register($container, $renderers)); - - if ($container->hasDefinition($this->debugCommandService)) { - $container->getDefinition($this->debugCommandService)->replaceArgument(0, $renderers); - } - } -} diff --git a/src/Symfony/Component/ErrorRenderer/DependencyInjection/LazyLoadingErrorRenderer.php b/src/Symfony/Component/ErrorRenderer/DependencyInjection/LazyLoadingErrorRenderer.php deleted file mode 100644 index 82936a29af099..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/DependencyInjection/LazyLoadingErrorRenderer.php +++ /dev/null @@ -1,44 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorRenderer\DependencyInjection; - -use Psr\Container\ContainerInterface; -use Symfony\Component\ErrorRenderer\ErrorRenderer; - -/** - * Lazily loads error renderers from the dependency injection container. - * - * @author Yonel Ceruto - */ -class LazyLoadingErrorRenderer extends ErrorRenderer -{ - private $container; - private $initialized = []; - - public function __construct(ContainerInterface $container) - { - $this->container = $container; - } - - /** - * {@inheritdoc} - */ - public function render($exception, string $format = 'html'): string - { - if (!isset($this->initialized[$format]) && $this->container->has($format)) { - $this->addRenderer($this->container->get($format), $format); - $this->initialized[$format] = true; - } - - return parent::render($exception, $format); - } -} diff --git a/src/Symfony/Component/ErrorRenderer/ErrorRenderer.php b/src/Symfony/Component/ErrorRenderer/ErrorRenderer.php deleted file mode 100644 index da984bdd18895..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/ErrorRenderer.php +++ /dev/null @@ -1,77 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorRenderer; - -use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface; -use Symfony\Component\ErrorRenderer\Exception\ErrorRendererNotFoundException; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; - -/** - * Formats an exception to be used as response content. - * - * It delegates to implementations of ErrorRendererInterface depending on the format. - * - * @see ErrorRendererInterface - * - * @author Yonel Ceruto - */ -class ErrorRenderer -{ - private $renderers = []; - - /** - * @param ErrorRendererInterface[] $renderers - */ - public function __construct(iterable $renderers) - { - foreach ($renderers as $renderer) { - $this->addRenderer($renderer); - } - } - - /** - * Registers an error renderer that is format specific. - * - * By passing an explicit format you can register a renderer for a different format than what - * ErrorRendererInterface::getFormat() would return in order to register the same renderer for - * several format aliases. - */ - public function addRenderer(ErrorRendererInterface $renderer, string $format = null): self - { - $this->renderers[$format ?? $renderer::getFormat()] = $renderer; - - return $this; - } - - /** - * Renders an Exception and returns the Response content. - * - * @param \Throwable|FlattenException $exception A \Throwable or FlattenException instance - * @param string $format The request format (html, json, xml, etc.) - * - * @return string The Response content as a string - * - * @throws ErrorRendererNotFoundException if no renderer is found - */ - public function render($exception, string $format = 'html'): string - { - if (!isset($this->renderers[$format])) { - throw new ErrorRendererNotFoundException(sprintf('No error renderer found for format "%s".', $format)); - } - - if ($exception instanceof \Throwable) { - $exception = FlattenException::createFromThrowable($exception); - } - - return $this->renderers[$format]->render($exception); - } -} diff --git a/src/Symfony/Component/ErrorRenderer/ErrorRenderer/ErrorRendererInterface.php b/src/Symfony/Component/ErrorRenderer/ErrorRenderer/ErrorRendererInterface.php deleted file mode 100644 index 5c0d58060202d..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/ErrorRenderer/ErrorRendererInterface.php +++ /dev/null @@ -1,32 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorRenderer\ErrorRenderer; - -use Symfony\Component\ErrorRenderer\Exception\FlattenException; - -/** - * Interface for classes that can render errors in a specific format. - * - * @author Yonel Ceruto - */ -interface ErrorRendererInterface -{ - /** - * Gets the format this renderer can return errors as. - */ - public static function getFormat(): string; - - /** - * Returns the response content of the rendered exception. - */ - public function render(FlattenException $exception): string; -} diff --git a/src/Symfony/Component/ErrorRenderer/ErrorRenderer/JsonErrorRenderer.php b/src/Symfony/Component/ErrorRenderer/ErrorRenderer/JsonErrorRenderer.php deleted file mode 100644 index d708fb0f15c85..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/ErrorRenderer/JsonErrorRenderer.php +++ /dev/null @@ -1,60 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorRenderer\ErrorRenderer; - -use Symfony\Component\ErrorRenderer\Exception\FlattenException; - -/** - * @author Yonel Ceruto - */ -class JsonErrorRenderer implements ErrorRendererInterface -{ - private $debug; - - public function __construct(bool $debug = false) - { - $this->debug = $debug; - } - - /** - * {@inheritdoc} - */ - public static function getFormat(): string - { - return 'json'; - } - - /** - * {@inheritdoc} - */ - public function render(FlattenException $exception): string - { - $debug = $this->debug && ($exception->getHeaders()['X-Debug'] ?? true); - - if ($debug) { - $message = $exception->getMessage(); - } else { - $message = 404 === $exception->getStatusCode() ? 'Sorry, the page you are looking for could not be found.' : 'Whoops, looks like something went wrong.'; - } - - $content = [ - 'title' => $exception->getTitle(), - 'status' => $exception->getStatusCode(), - 'detail' => $message, - ]; - if ($debug) { - $content['exceptions'] = $exception->toArray(); - } - - return (string) json_encode($content, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS | JSON_PRESERVE_ZERO_FRACTION); - } -} diff --git a/src/Symfony/Component/ErrorRenderer/ErrorRenderer/TxtErrorRenderer.php b/src/Symfony/Component/ErrorRenderer/ErrorRenderer/TxtErrorRenderer.php deleted file mode 100644 index 2bafb2cfb4d8b..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/ErrorRenderer/TxtErrorRenderer.php +++ /dev/null @@ -1,104 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorRenderer\ErrorRenderer; - -use Symfony\Component\ErrorRenderer\Exception\FlattenException; - -/** - * @author Yonel Ceruto - */ -class TxtErrorRenderer implements ErrorRendererInterface -{ - private $debug; - - public function __construct(bool $debug = false) - { - $this->debug = $debug; - } - - /** - * {@inheritdoc} - */ - public static function getFormat(): string - { - return 'txt'; - } - - /** - * {@inheritdoc} - */ - public function render(FlattenException $exception): string - { - $debug = $this->debug && ($exception->getHeaders()['X-Debug'] ?? true); - - if ($debug) { - $message = $exception->getMessage(); - } else { - $message = 404 === $exception->getStatusCode() ? 'Sorry, the page you are looking for could not be found.' : 'Whoops, looks like something went wrong.'; - } - - $content = sprintf("[title] %s\n", $exception->getTitle()); - $content .= sprintf("[status] %s\n", $exception->getStatusCode()); - $content .= sprintf("[detail] %s\n", $message); - - if ($debug) { - foreach ($exception->toArray() as $i => $e) { - $content .= sprintf("[%d] %s: %s\n", $i + 1, $e['class'], $e['message']); - foreach ($e['trace'] as $trace) { - if ($trace['function']) { - $content .= sprintf('at %s%s%s(%s) ', $trace['class'], $trace['type'], $trace['function'], $this->formatArgs($trace['args'])); - } - if (isset($trace['file'], $trace['line'])) { - $content .= $this->formatPath($trace['file'], $trace['line']); - } - $content .= "\n"; - } - } - } - - return $content; - } - - private function formatPath(string $path, int $line): string - { - $file = preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path; - - return sprintf('in %s %s', $path, 0 < $line ? ' line '.$line : ''); - } - - /** - * Formats an array as a string. - */ - private function formatArgs(array $args): string - { - $result = []; - foreach ($args as $key => $item) { - if ('object' === $item[0]) { - $formattedValue = sprintf('object(%s)', $item[1]); - } elseif ('array' === $item[0]) { - $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); - } elseif ('null' === $item[0]) { - $formattedValue = 'null'; - } elseif ('boolean' === $item[0]) { - $formattedValue = strtolower(var_export($item[1], true)); - } elseif ('resource' === $item[0]) { - $formattedValue = 'resource'; - } else { - $formattedValue = str_replace("\n", '', var_export($item[1], true)); - } - - $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue); - } - - return implode(', ', $result); - } -} diff --git a/src/Symfony/Component/ErrorRenderer/ErrorRenderer/XmlErrorRenderer.php b/src/Symfony/Component/ErrorRenderer/ErrorRenderer/XmlErrorRenderer.php deleted file mode 100644 index 290e0a63ffef1..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/ErrorRenderer/XmlErrorRenderer.php +++ /dev/null @@ -1,125 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorRenderer\ErrorRenderer; - -use Symfony\Component\ErrorRenderer\Exception\FlattenException; - -/** - * @author Yonel Ceruto - */ -class XmlErrorRenderer implements ErrorRendererInterface -{ - private $debug; - private $charset; - - public function __construct(bool $debug = false, string $charset = null) - { - $this->debug = $debug; - $this->charset = $charset ?: (ini_get('default_charset') ?: 'UTF-8'); - } - - /** - * {@inheritdoc} - */ - public static function getFormat(): string - { - return 'xml'; - } - - /** - * {@inheritdoc} - */ - public function render(FlattenException $exception): string - { - $debug = $this->debug && ($exception->getHeaders()['X-Debug'] ?? true); - $title = $this->escapeXml($exception->getTitle()); - if ($debug) { - $message = $this->escapeXml($exception->getMessage()); - } else { - $message = 404 === $exception->getStatusCode() ? 'Sorry, the page you are looking for could not be found.' : 'Whoops, looks like something went wrong.'; - } - $statusCode = $this->escapeXml($exception->getStatusCode()); - $charset = $this->escapeXml($this->charset); - - $exceptions = ''; - if ($debug) { - $exceptions .= ''; - foreach ($exception->toArray() as $e) { - $exceptions .= sprintf('', $e['class'], $this->escapeXml($e['message'])); - foreach ($e['trace'] as $trace) { - $exceptions .= ''; - if ($trace['function']) { - $exceptions .= sprintf('at %s%s%s(%s) ', $trace['class'], $trace['type'], $trace['function'], $this->formatArgs($trace['args'])); - } - if (isset($trace['file'], $trace['line'])) { - $exceptions .= $this->formatPath($trace['file'], $trace['line']); - } - $exceptions .= ''; - } - $exceptions .= ''; - } - $exceptions .= ''; - } - - return << - - {$title} - {$statusCode} - {$message} - {$exceptions} - -EOF; - } - - /** - * XML-encodes a string. - */ - private function escapeXml(string $str): string - { - return htmlspecialchars($str, ENT_COMPAT | ENT_SUBSTITUTE, $this->charset); - } - - private function formatPath(string $path, int $line): string - { - $file = $this->escapeXml(preg_match('#[^/\\\\]*+$#', $path, $file) ? $file[0] : $path); - - return sprintf('in %s %s', $this->escapeXml($path), 0 < $line ? ' line '.$line : ''); - } - - /** - * Formats an array as a string. - */ - private function formatArgs(array $args): string - { - $result = []; - foreach ($args as $key => $item) { - if ('object' === $item[0]) { - $formattedValue = sprintf('object(%s)', $item[1]); - } elseif ('array' === $item[0]) { - $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); - } elseif ('null' === $item[0]) { - $formattedValue = 'null'; - } elseif ('boolean' === $item[0]) { - $formattedValue = strtolower(var_export($item[1], true)); - } elseif ('resource' === $item[0]) { - $formattedValue = 'resource'; - } else { - $formattedValue = str_replace("\n", '', $this->escapeXml(var_export($item[1], true))); - } - - $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escapeXml($key), $formattedValue); - } - - return implode(', ', $result); - } -} diff --git a/src/Symfony/Component/ErrorRenderer/Exception/ErrorRendererNotFoundException.php b/src/Symfony/Component/ErrorRenderer/Exception/ErrorRendererNotFoundException.php deleted file mode 100644 index 4020ced161fc1..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/Exception/ErrorRendererNotFoundException.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorRenderer\Exception; - -class ErrorRendererNotFoundException extends \RuntimeException -{ -} diff --git a/src/Symfony/Component/ErrorRenderer/LICENSE b/src/Symfony/Component/ErrorRenderer/LICENSE deleted file mode 100644 index 1a1869751d250..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2019 Fabien Potencier - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/src/Symfony/Component/ErrorRenderer/README.md b/src/Symfony/Component/ErrorRenderer/README.md deleted file mode 100644 index 272c2924d89d3..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/README.md +++ /dev/null @@ -1,12 +0,0 @@ -ErrorRenderer Component -====================== - -The ErrorRenderer component provides tools to display errors and exceptions. - -Resources ---------- - - * [Contributing](https://symfony.com/doc/current/contributing/index.html) - * [Report issues](https://github.com/symfony/symfony/issues) and - [send Pull Requests](https://github.com/symfony/symfony/pulls) - in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/ErrorRenderer/Resources/views/exception_full.html.php b/src/Symfony/Component/ErrorRenderer/Resources/views/exception_full.html.php deleted file mode 100644 index 7ce8de0666df1..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/Resources/views/exception_full.html.php +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - <?= $_message; ?> - - - - - -
- -
- - include('views/exception.html.php', $context); ?> - - - - - diff --git a/src/Symfony/Component/ErrorRenderer/Tests/Command/DebugCommandTest.php b/src/Symfony/Component/ErrorRenderer/Tests/Command/DebugCommandTest.php deleted file mode 100644 index c5a9768c8bb5a..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/Tests/Command/DebugCommandTest.php +++ /dev/null @@ -1,88 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorRenderer\Tests\Command; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\Console\Application; -use Symfony\Component\Console\Tester\CommandTester; -use Symfony\Component\ErrorRenderer\Command\DebugCommand; -use Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer; -use Symfony\Component\ErrorRenderer\ErrorRenderer\TxtErrorRenderer; -use Symfony\Component\ErrorRenderer\ErrorRenderer\XmlErrorRenderer; - -class DebugCommandTest extends TestCase -{ - public function testAvailableRenderers() - { - $tester = $this->createCommandTester(); - $ret = $tester->execute([], ['decorated' => false]); - - $this->assertEquals(0, $ret, 'Returns 0 in case of success'); - $this->assertSame(<<getDisplay(true)); - } - - public function testFormatArgument() - { - $tester = $this->createCommandTester(); - $ret = $tester->execute(['format' => 'json'], ['decorated' => false]); - - $this->assertEquals(0, $ret, 'Returns 0 in case of success'); - $this->assertSame(<<getDisplay(true)); - } - - private function createCommandTester() - { - $command = new DebugCommand([ - 'json' => new JsonErrorRenderer(false), - 'xml' => new XmlErrorRenderer(false), - 'txt' => new TxtErrorRenderer(false), - ]); - - $application = new Application(); - $application->add($command); - - return new CommandTester($application->find('debug:error-renderer')); - } - - public function testInvalidFormat() - { - $this->expectException('Symfony\Component\Console\Exception\InvalidArgumentException'); - $this->expectExceptionMessage('No error renderer found for format "foo". Known format are json, xml, txt.'); - $tester = $this->createCommandTester(); - $tester->execute(['format' => 'foo'], ['decorated' => false]); - } -} diff --git a/src/Symfony/Component/ErrorRenderer/Tests/DependencyInjection/ErrorRendererPassTest.php b/src/Symfony/Component/ErrorRenderer/Tests/DependencyInjection/ErrorRendererPassTest.php deleted file mode 100644 index e69fd860b85d8..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/Tests/DependencyInjection/ErrorRendererPassTest.php +++ /dev/null @@ -1,67 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorRenderer\Tests\DependencyInjection; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\ServiceLocator; -use Symfony\Component\ErrorRenderer\DependencyInjection\ErrorRendererPass; -use Symfony\Component\ErrorRenderer\DependencyInjection\LazyLoadingErrorRenderer; -use Symfony\Component\ErrorRenderer\ErrorRenderer\HtmlErrorRenderer; -use Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer; - -class ErrorRendererPassTest extends TestCase -{ - public function testProcess() - { - $container = new ContainerBuilder(); - $container->setParameter('kernel.debug', true); - $definition = $container->register('error_renderer', LazyLoadingErrorRenderer::class) - ->addArgument([]) - ; - $container->register('error_renderer.renderer.html', HtmlErrorRenderer::class) - ->addTag('error_renderer.renderer') - ; - $container->register('error_renderer.renderer.json', JsonErrorRenderer::class) - ->addTag('error_renderer.renderer') - ; - - (new ErrorRendererPass())->process($container); - - $serviceLocatorDefinition = $container->getDefinition((string) $definition->getArgument(0)); - $this->assertSame(ServiceLocator::class, $serviceLocatorDefinition->getClass()); - - $expected = [ - 'html' => new ServiceClosureArgument(new Reference('error_renderer.renderer.html')), - 'json' => new ServiceClosureArgument(new Reference('error_renderer.renderer.json')), - ]; - $this->assertEquals($expected, $serviceLocatorDefinition->getArgument(0)); - } - - public function testServicesAreOrderedAccordingToPriority() - { - $container = new ContainerBuilder(); - $definition = $container->register('error_renderer')->setArguments([null]); - $container->register('r2')->addTag('error_renderer.renderer', ['format' => 'json', 'priority' => 100]); - $container->register('r1')->addTag('error_renderer.renderer', ['format' => 'json', 'priority' => 200]); - $container->register('r3')->addTag('error_renderer.renderer', ['format' => 'json']); - (new ErrorRendererPass())->process($container); - - $expected = [ - 'json' => new ServiceClosureArgument(new Reference('r1')), - ]; - $serviceLocatorDefinition = $container->getDefinition((string) $definition->getArgument(0)); - $this->assertEquals($expected, $serviceLocatorDefinition->getArgument(0)); - } -} diff --git a/src/Symfony/Component/ErrorRenderer/Tests/DependencyInjection/LazyLoadingErrorRendererTest.php b/src/Symfony/Component/ErrorRenderer/Tests/DependencyInjection/LazyLoadingErrorRendererTest.php deleted file mode 100644 index 5811a0c026e0e..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/Tests/DependencyInjection/LazyLoadingErrorRendererTest.php +++ /dev/null @@ -1,66 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorRenderer\Tests\DependencyInjection; - -use PHPUnit\Framework\TestCase; -use Psr\Container\ContainerInterface; -use Symfony\Component\ErrorRenderer\DependencyInjection\LazyLoadingErrorRenderer; -use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; - -class LazyLoadingErrorRendererTest extends TestCase -{ - public function testInvalidErrorRenderer() - { - $this->expectException('Symfony\Component\ErrorRenderer\Exception\ErrorRendererNotFoundException'); - $this->expectExceptionMessage('No error renderer found for format "foo".'); - $container = $this->getMockBuilder(ContainerInterface::class)->getMock(); - $container->expects($this->once())->method('has')->with('foo')->willReturn(false); - - $exception = FlattenException::createFromThrowable(new \Exception('Foo')); - (new LazyLoadingErrorRenderer($container))->render($exception, 'foo'); - } - - public function testCustomErrorRenderer() - { - $container = $this->getMockBuilder(ContainerInterface::class)->getMock(); - $container - ->expects($this->once()) - ->method('has') - ->with('foo') - ->willReturn(true) - ; - $container - ->expects($this->once()) - ->method('get') - ->willReturn(new FooErrorRenderer()) - ; - - $errorRenderer = new LazyLoadingErrorRenderer($container); - - $exception = FlattenException::createFromThrowable(new \RuntimeException('Foo')); - $this->assertSame('Foo', $errorRenderer->render($exception, 'foo')); - } -} - -class FooErrorRenderer implements ErrorRendererInterface -{ - public static function getFormat(): string - { - return 'foo'; - } - - public function render(FlattenException $exception): string - { - return $exception->getMessage(); - } -} diff --git a/src/Symfony/Component/ErrorRenderer/Tests/ErrorRenderer/JsonErrorRendererTest.php b/src/Symfony/Component/ErrorRenderer/Tests/ErrorRenderer/JsonErrorRendererTest.php deleted file mode 100644 index 55e6d5cdf227d..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/Tests/ErrorRenderer/JsonErrorRendererTest.php +++ /dev/null @@ -1,76 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorRenderer\Tests\ErrorRenderer; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface; -use Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; - -class JsonErrorRendererTest extends TestCase -{ - /** - * @dataProvider getRenderData - */ - public function testRender(FlattenException $exception, ErrorRendererInterface $errorRenderer, string $expected) - { - $this->assertStringMatchesFormat($expected, $errorRenderer->render($exception)); - } - - public function getRenderData(): iterable - { - $expectedDebug = <<render() returns the JSON content WITH stack traces in debug mode' => [ - FlattenException::createFromThrowable(new \RuntimeException('Foo')), - new JsonErrorRenderer(true), - $expectedDebug, - ]; - - yield '->render() returns the JSON content WITHOUT stack traces in non-debug mode' => [ - FlattenException::createFromThrowable(new \RuntimeException('Foo')), - new JsonErrorRenderer(false), - $expectedNonDebug, - ]; - - yield '->render() returns the JSON content WITHOUT stack traces in debug mode FORCING non-debug via X-Debug header' => [ - FlattenException::createFromThrowable(new \RuntimeException('Foo'), null, ['X-Debug' => false]), - new JsonErrorRenderer(true), - $expectedNonDebug, - ]; - - yield '->render() returns the JSON content WITHOUT stack traces in non-debug mode EVEN FORCING debug via X-Debug header' => [ - FlattenException::createFromThrowable(new \RuntimeException('Foo'), null, ['X-Debug' => true]), - new JsonErrorRenderer(false), - $expectedNonDebug, - ]; - } -} diff --git a/src/Symfony/Component/ErrorRenderer/Tests/ErrorRenderer/TxtErrorRendererTest.php b/src/Symfony/Component/ErrorRenderer/Tests/ErrorRenderer/TxtErrorRendererTest.php deleted file mode 100644 index b6c1570df88d7..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/Tests/ErrorRenderer/TxtErrorRendererTest.php +++ /dev/null @@ -1,69 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorRenderer\Tests\ErrorRenderer; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface; -use Symfony\Component\ErrorRenderer\ErrorRenderer\TxtErrorRenderer; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; - -class TxtErrorRendererTest extends TestCase -{ - /** - * @dataProvider getRenderData - */ - public function testRender(FlattenException $exception, ErrorRendererInterface $errorRenderer, string $expected) - { - $this->assertStringMatchesFormat($expected, $errorRenderer->render($exception)); - } - - public function getRenderData(): iterable - { - $expectedDebug = <<render() returns the TXT content WITH stack traces in debug mode' => [ - FlattenException::createFromThrowable(new \RuntimeException('Foo')), - new TxtErrorRenderer(true), - $expectedDebug, - ]; - - yield '->render() returns the TXT content WITHOUT stack traces in non-debug mode' => [ - FlattenException::createFromThrowable(new \RuntimeException('Foo')), - new TxtErrorRenderer(false), - $expectedNonDebug, - ]; - - yield '->render() returns the TXT content WITHOUT stack traces in debug mode FORCING non-debug via X-Debug header' => [ - FlattenException::createFromThrowable(new \RuntimeException('Foo'), null, ['X-Debug' => false]), - new TxtErrorRenderer(true), - $expectedNonDebug, - ]; - - yield '->render() returns the TXT content WITHOUT stack traces in non-debug mode EVEN FORCING debug via X-Debug header' => [ - FlattenException::createFromThrowable(new \RuntimeException('Foo'), null, ['X-Debug' => true]), - new TxtErrorRenderer(false), - $expectedNonDebug, - ]; - } -} diff --git a/src/Symfony/Component/ErrorRenderer/Tests/ErrorRenderer/XmlErrorRendererTest.php b/src/Symfony/Component/ErrorRenderer/Tests/ErrorRenderer/XmlErrorRendererTest.php deleted file mode 100644 index 3a756720ecc46..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/Tests/ErrorRenderer/XmlErrorRendererTest.php +++ /dev/null @@ -1,75 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorRenderer\Tests\ErrorRenderer; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface; -use Symfony\Component\ErrorRenderer\ErrorRenderer\XmlErrorRenderer; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; - -class XmlErrorRendererTest extends TestCase -{ - /** - * @dataProvider getRenderData - */ - public function testRender(FlattenException $exception, ErrorRendererInterface $errorRenderer, string $expected) - { - $this->assertStringMatchesFormat($expected, $errorRenderer->render($exception)); - } - - public function getRenderData(): iterable - { - $expectedDebug = << - - Internal Server Error - 500 - Foo - %A - -XML; - - $expectedNonDebug = << - - Internal Server Error - 500 - Whoops, looks like something went wrong. - - -XML; - - yield '->render() returns the XML content WITH stack traces in debug mode' => [ - FlattenException::createFromThrowable(new \RuntimeException('Foo')), - new XmlErrorRenderer(true), - $expectedDebug, - ]; - - yield '->render() returns the XML content WITHOUT stack traces in non-debug mode' => [ - FlattenException::createFromThrowable(new \RuntimeException('Foo')), - new XmlErrorRenderer(false), - $expectedNonDebug, - ]; - - yield '->render() returns the XML content WITHOUT stack traces in debug mode FORCING non-debug via X-Debug header' => [ - FlattenException::createFromThrowable(new \RuntimeException('Foo'), null, ['X-Debug' => false]), - new XmlErrorRenderer(true), - $expectedNonDebug, - ]; - - yield '->render() returns the XML content WITHOUT stack traces in non-debug mode EVEN FORCING debug via X-Debug header' => [ - FlattenException::createFromThrowable(new \RuntimeException('Foo'), null, ['X-Debug' => true]), - new XmlErrorRenderer(false), - $expectedNonDebug, - ]; - } -} diff --git a/src/Symfony/Component/ErrorRenderer/Tests/ErrorRendererTest.php b/src/Symfony/Component/ErrorRenderer/Tests/ErrorRendererTest.php deleted file mode 100644 index 11ef6a7d7eb4f..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/Tests/ErrorRendererTest.php +++ /dev/null @@ -1,56 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ErrorRenderer\Tests; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\ErrorRenderer\ErrorRenderer; -use Symfony\Component\ErrorRenderer\ErrorRenderer\ErrorRendererInterface; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; - -class ErrorRendererTest extends TestCase -{ - public function testErrorRendererNotFound() - { - $this->expectException('Symfony\Component\ErrorRenderer\Exception\ErrorRendererNotFoundException'); - $this->expectExceptionMessage('No error renderer found for format "foo".'); - $exception = FlattenException::createFromThrowable(new \Exception('foo')); - (new ErrorRenderer([]))->render($exception, 'foo'); - } - - public function testInvalidErrorRenderer() - { - $this->expectException('TypeError'); - new ErrorRenderer([new \stdClass()]); - } - - public function testCustomErrorRenderer() - { - $renderers = [new FooErrorRenderer()]; - $errorRenderer = new ErrorRenderer($renderers); - - $exception = FlattenException::createFromThrowable(new \RuntimeException('Foo')); - $this->assertSame('Foo', $errorRenderer->render($exception, 'foo')); - } -} - -class FooErrorRenderer implements ErrorRendererInterface -{ - public static function getFormat(): string - { - return 'foo'; - } - - public function render(FlattenException $exception): string - { - return $exception->getMessage(); - } -} diff --git a/src/Symfony/Component/ErrorRenderer/composer.json b/src/Symfony/Component/ErrorRenderer/composer.json deleted file mode 100644 index 90351239fc689..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/composer.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "symfony/error-renderer", - "type": "library", - "description": "Symfony ErrorRenderer Component", - "keywords": [], - "homepage": "https://symfony.com", - "license": "MIT", - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Yonel Ceruto", - "email": "yonelceruto@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "require": { - "php": "^7.1.3", - "psr/log": "~1.0", - "symfony/debug": "^4.4" - }, - "require-dev": { - "symfony/console": "^4.4", - "symfony/dependency-injection": "^4.4", - "symfony/http-kernel": "^4.4" - }, - "conflict": { - "symfony/http-kernel": "<4.4" - }, - "autoload": { - "psr-4": { "Symfony\\Component\\ErrorRenderer\\": "" }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "minimum-stability": "dev", - "extra": { - "branch-alias": { - "dev-master": "4.4-dev" - } - } -} diff --git a/src/Symfony/Component/ErrorRenderer/phpunit.xml.dist b/src/Symfony/Component/ErrorRenderer/phpunit.xml.dist deleted file mode 100644 index c4c29459f3e74..0000000000000 --- a/src/Symfony/Component/ErrorRenderer/phpunit.xml.dist +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - ./Tests/ - - - - - - ./ - - ./Tests - ./vendor - - - - diff --git a/src/Symfony/Component/HttpKernel/Controller/ErrorController.php b/src/Symfony/Component/HttpKernel/Controller/ErrorController.php index a86fa5c5cf391..b6c440103ffd3 100644 --- a/src/Symfony/Component/HttpKernel/Controller/ErrorController.php +++ b/src/Symfony/Component/HttpKernel/Controller/ErrorController.php @@ -11,11 +11,10 @@ namespace Symfony\Component\HttpKernel\Controller; -use Symfony\Component\ErrorRenderer\ErrorRenderer; -use Symfony\Component\ErrorRenderer\Exception\ErrorRendererNotFoundException; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; +use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\HttpKernelInterface; /** @@ -30,26 +29,22 @@ class ErrorController private $controller; private $errorRenderer; - public function __construct(HttpKernelInterface $kernel, $controller, ErrorRenderer $errorRenderer) + public function __construct(HttpKernelInterface $kernel, $controller, ErrorRendererInterface $errorRenderer) { $this->kernel = $kernel; $this->controller = $controller; $this->errorRenderer = $errorRenderer; } - public function __invoke(Request $request, FlattenException $exception): Response + public function __invoke(\Throwable $exception): Response { - try { - return new Response($this->errorRenderer->render($exception, $request->getPreferredFormat()), $exception->getStatusCode(), $exception->getHeaders()); - } catch (ErrorRendererNotFoundException $_) { - return new Response($this->errorRenderer->render($exception), $exception->getStatusCode(), $exception->getHeaders()); - } + $exception = $this->errorRenderer->render($exception); + + return new Response($exception->getAsString(), $exception->getStatusCode(), $exception->getHeaders()); } public function preview(Request $request, int $code): Response { - $exception = FlattenException::createFromThrowable(new \Exception('This is a sample exception.'), $code, ['X-Debug' => false]); - /* * This Request mimics the parameters set by * \Symfony\Component\HttpKernel\EventListener\ErrorListener::duplicateRequest, with @@ -57,7 +52,7 @@ public function preview(Request $request, int $code): Response */ $subRequest = $request->duplicate(null, null, [ '_controller' => $this->controller, - 'exception' => $exception, + 'exception' => new HttpException($code, 'This is a sample exception.'), 'logger' => null, 'showException' => false, ]); diff --git a/src/Symfony/Component/HttpKernel/DataCollector/ExceptionDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/ExceptionDataCollector.php index 08e79c53e12ac..0a00a2e40e680 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/ExceptionDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/ExceptionDataCollector.php @@ -11,7 +11,7 @@ namespace Symfony\Component\HttpKernel\DataCollector; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; +use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; diff --git a/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php b/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php index 9a16cde741432..472c3a2383f07 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php @@ -12,10 +12,11 @@ namespace Symfony\Component\HttpKernel\EventListener; use Psr\Log\LoggerInterface; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; +use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Symfony\Component\HttpKernel\HttpKernelInterface; @@ -87,9 +88,28 @@ public function onKernelException(ExceptionEvent $event) } } + public function onControllerArguments(ControllerArgumentsEvent $event) + { + $e = $event->getRequest()->attributes->get('exception'); + + if (!$e instanceof \Throwable || false === $k = array_search($e, $event->getArguments(), true)) { + return; + } + + $r = new \ReflectionFunction(\Closure::fromCallable($event->getController())); + $r = $r->getParameters()[$k] ?? null; + + if ($r && $r->hasType() && FlattenException::class === $r->getType()->getName()) { + $arguments = $event->getArguments(); + $arguments[$k] = FlattenException::createFromThrowable($e); + $event->setArguments($arguments); + } + } + public static function getSubscribedEvents() { return [ + KernelEvents::CONTROLLER_ARGUMENTS => 'onControllerArguments', KernelEvents::EXCEPTION => [ ['logKernelException', 0], ['onKernelException', -128], @@ -123,7 +143,7 @@ protected function duplicateRequest(\Exception $exception, Request $request) { $attributes = [ '_controller' => $this->controller, - 'exception' => FlattenException::createFromThrowable($exception), + 'exception' => $exception, 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null, ]; $request = $request->duplicate(null, null, $attributes); diff --git a/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php b/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php index dc2fd818ce93a..aa4349428ad12 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php @@ -12,7 +12,7 @@ namespace Symfony\Component\HttpKernel\EventListener; use Psr\Log\LoggerInterface; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; +use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ErrorControllerTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ErrorControllerTest.php index 4a64af3ab1e33..a857615f1c3d3 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Controller/ErrorControllerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ErrorControllerTest.php @@ -12,10 +12,8 @@ namespace Symfony\Component\HttpKernel\Tests\Controller; use PHPUnit\Framework\TestCase; -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\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; +use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Controller\ErrorController; @@ -28,12 +26,12 @@ class ErrorControllerTest extends TestCase /** * @dataProvider getInvokeControllerDataProvider */ - public function testInvokeController(Request $request, FlattenException $exception, int $statusCode, string $content) + public function testInvokeController(Request $request, \Exception $exception, int $statusCode, string $content) { $kernel = $this->getMockBuilder(HttpKernelInterface::class)->getMock(); - $errorRenderer = new ErrorRenderer([new HtmlErrorRenderer(), new JsonErrorRenderer()]); + $errorRenderer = new HtmlErrorRenderer(); $controller = new ErrorController($kernel, null, $errorRenderer); - $response = $controller($request, $exception); + $response = $controller($exception); $this->assertSame($statusCode, $response->getStatusCode()); self::assertStringContainsString($content, strtr($response->getContent(), ["\n" => '', ' ' => ''])); @@ -43,50 +41,23 @@ public function getInvokeControllerDataProvider() { yield 'default status code and HTML format' => [ new Request(), - FlattenException::createFromThrowable(new \Exception()), + new \Exception(), 500, 'The server returned a "500 Internal Server Error".', ]; yield 'custom status code' => [ new Request(), - FlattenException::createFromThrowable(new NotFoundHttpException('Page not found.')), + new NotFoundHttpException('Page not found.'), 404, 'The server returned a "404 Not Found".', ]; - $request = new Request(); - $request->attributes->set('_format', 'json'); - yield 'custom format via _format attribute' => [ - $request, - FlattenException::createFromThrowable(new \Exception('foo')), - 500, - '{"title": "Internal Server Error","status": 500,"detail": "Whoops, looks like something went wrong."}', - ]; - - $request = new Request(); - $request->headers->set('Accept', 'application/json'); - yield 'custom format via Accept header' => [ - $request, - FlattenException::createFromThrowable(new HttpException(405, 'Invalid request.')), - 405, - '{"title": "Method Not Allowed","status": 405,"detail": "Whoops, looks like something went wrong."}', - ]; - - $request = new Request(); - $request->headers->set('Content-Type', 'application/json'); - yield 'custom format via Content-Type header' => [ - $request, - FlattenException::createFromThrowable(new HttpException(405, 'Invalid request.')), - 405, - '{"title": "Method Not Allowed","status": 405,"detail": "Whoops, looks like something went wrong."}', - ]; - $request = new Request(); $request->attributes->set('_format', 'unknown'); yield 'default HTML format for unknown formats' => [ $request, - FlattenException::createFromThrowable(new HttpException(405, 'Invalid request.')), + new HttpException(405, 'Invalid request.'), 405, 'The server returned a "405 Method Not Allowed".', ]; @@ -106,7 +77,7 @@ public function testPreviewController() $exception = $request->attributes->get('exception'); $this->assertSame($_controller, $request->attributes->get('_controller')); - $this->assertInstanceOf(FlattenException::class, $exception); + $this->assertInstanceOf(\Throwable::class, $exception); $this->assertSame($code, $exception->getStatusCode()); $this->assertFalse($request->attributes->get('showException')); @@ -116,7 +87,7 @@ public function testPreviewController() ) ->willReturn($response = new Response()); - $controller = new ErrorController($kernel, $_controller, new ErrorRenderer([])); + $controller = new ErrorController($kernel, $_controller, new HtmlErrorRenderer()); $this->assertSame($response, $controller->preview(new Request(), $code)); } diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/ExceptionDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/ExceptionDataCollectorTest.php index 8b1d1317d85b2..d1fc578b131f6 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/ExceptionDataCollectorTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/ExceptionDataCollectorTest.php @@ -12,7 +12,7 @@ namespace Symfony\Component\HttpKernel\Tests\DataCollector; use PHPUnit\Framework\TestCase; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; +use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\ExceptionDataCollector; diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php index 3707f3c4c920e..ae7149199f963 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php @@ -12,9 +12,13 @@ namespace Symfony\Component\HttpKernel\Tests\EventListener; use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent; use Symfony\Component\HttpKernel\Event\ExceptionEvent; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\EventListener\ErrorListener; @@ -153,6 +157,30 @@ public function testCSPHeaderIsRemoved() $this->assertFalse($response->headers->has('content-security-policy'), 'CSP header has been removed'); $this->assertFalse($dispatcher->hasListeners(KernelEvents::RESPONSE), 'CSP removal listener has been removed'); } + + public function testOnControllerArguments() + { + $controller = function (FlattenException $exception) { + return new Response('OK: '.$exception->getMessage()); + }; + + $listener = new ErrorListener($controller, $this->createMock(LoggerInterface::class), true); + + $kernel = $this->createMock(HttpKernelInterface::class); + $kernel->method('handle')->willReturnCallback(function (Request $request) use ($listener, $controller, $kernel) { + $this->assertSame($controller, $request->attributes->get('_controller')); + $arguments = (new ArgumentResolver())->getArguments($request, $controller); + $event = new ControllerArgumentsEvent($kernel, $controller, $arguments, $request, HttpKernelInterface::SUB_REQUEST); + $listener->onControllerArguments($event); + + return $controller(...$event->getArguments()); + }); + + $event = new ExceptionEvent($kernel, Request::create('/'), HttpKernelInterface::MASTER_REQUEST, new \Exception('foo')); + $listener->onKernelException($event); + + $this->assertSame('OK: foo', $event->getResponse()->getContent()); + } } class TestLogger extends Logger implements DebugLoggerInterface diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index 51f0d919a7ec0..ba6ab33de8e5b 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -18,7 +18,6 @@ "require": { "php": "^7.1.3", "symfony/error-handler": "^4.4|^5.0", - "symfony/error-renderer": "^4.4|^5.0", "symfony/event-dispatcher": "^4.4", "symfony/http-foundation": "^4.4|^5.0", "symfony/polyfill-ctype": "^1.8", @@ -40,7 +39,6 @@ "symfony/templating": "^3.4|^4.0|^5.0", "symfony/translation": "^4.2|^5.0", "symfony/translation-contracts": "^1.1|^2", - "symfony/var-dumper": "^4.1.1|^5.0", "psr/cache": "~1.0", "twig/twig": "^1.34|^2.4|^3.0" }, @@ -53,15 +51,13 @@ "symfony/console": ">=5", "symfony/dependency-injection": "<4.3", "symfony/translation": "<4.2", - "symfony/var-dumper": "<4.1.1", "twig/twig": "<1.34|<2.4,>=2" }, "suggest": { "symfony/browser-kit": "", "symfony/config": "", "symfony/console": "", - "symfony/dependency-injection": "", - "symfony/var-dumper": "" + "symfony/dependency-injection": "" }, "autoload": { "psr-4": { "Symfony\\Component\\HttpKernel\\": "" }, diff --git a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageToFailureTransportListener.php b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageToFailureTransportListener.php index 0aa8fd5ea74b6..8c84cf7992786 100644 --- a/src/Symfony/Component/Messenger/EventListener/SendFailedMessageToFailureTransportListener.php +++ b/src/Symfony/Component/Messenger/EventListener/SendFailedMessageToFailureTransportListener.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Messenger\EventListener; use Psr\Log\LoggerInterface; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; +use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent; use Symfony\Component\Messenger\Exception\HandlerFailedException; diff --git a/src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php b/src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php index 49bbf2f778571..60c3898b08606 100644 --- a/src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php +++ b/src/Symfony/Component/Messenger/Stamp/RedeliveryStamp.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Messenger\Stamp; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; +use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\Messenger\Envelope; /** diff --git a/src/Symfony/Component/Messenger/Tests/Stamp/RedeliveryStampTest.php b/src/Symfony/Component/Messenger/Tests/Stamp/RedeliveryStampTest.php index c55a670bf36fb..7fcabfc2d66f6 100644 --- a/src/Symfony/Component/Messenger/Tests/Stamp/RedeliveryStampTest.php +++ b/src/Symfony/Component/Messenger/Tests/Stamp/RedeliveryStampTest.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Messenger\Tests\Stamp; use PHPUnit\Framework\TestCase; -use Symfony\Component\ErrorRenderer\Exception\FlattenException; +use Symfony\Component\ErrorHandler\Exception\FlattenException; use Symfony\Component\Messenger\Stamp\RedeliveryStamp; class RedeliveryStampTest extends TestCase diff --git a/src/Symfony/Component/Messenger/composer.json b/src/Symfony/Component/Messenger/composer.json index 48f85f2b63d1a..62b5240b827ab 100644 --- a/src/Symfony/Component/Messenger/composer.json +++ b/src/Symfony/Component/Messenger/composer.json @@ -25,7 +25,6 @@ "doctrine/persistence": "~1.0", "symfony/console": "^3.4|^4.0|^5.0", "symfony/dependency-injection": "^3.4.19|^4.1.8|^5.0", - "symfony/error-renderer": "^4.4|^5.0", "symfony/event-dispatcher": "^4.3|^5.0", "symfony/http-kernel": "^4.4", "symfony/process": "^3.4|^4.0|^5.0", diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 0958f5054062a..7f213186e3c7f 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * deprecated the `XmlEncoder::TYPE_CASE_ATTRIBUTES` constant, use `XmlEncoder::TYPE_CAST_ATTRIBUTES` instead * added option to output a UTF-8 BOM in CSV encoder via `CsvEncoder::OUTPUT_UTF8_BOM_KEY` context option + * added `ProblemNormalizer` to normalize errors according to the API Problem spec (RFC 7807) 4.3.0 ----- diff --git a/src/Symfony/Component/Serializer/Normalizer/ProblemNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/ProblemNormalizer.php new file mode 100644 index 0000000000000..0569c2923bcda --- /dev/null +++ b/src/Symfony/Component/Serializer/Normalizer/ProblemNormalizer.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Normalizer; + +use Symfony\Component\ErrorHandler\Exception\FlattenException; + +/** + * Normalizes errors according to the API Problem spec (RFC 7807). + * + * @see https://tools.ietf.org/html/rfc7807 + * + * @author Kévin Dunglas + * @author Yonel Ceruto + */ +class ProblemNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface +{ + private $debug; + private $defaultContext = [ + 'type' => 'https://tools.ietf.org/html/rfc2616#section-10', + 'title' => 'An error occurred', + ]; + + public function __construct(bool $debug = false, array $defaultContext = []) + { + $this->debug = $debug; + $this->defaultContext = $defaultContext + $this->defaultContext; + } + + /** + * {@inheritdoc} + */ + public function normalize($exception, $format = null, array $context = []) + { + $context += $this->defaultContext; + + $data = [ + 'type' => $context['type'], + 'title' => $context['title'], + 'status' => $context['status'] ?? $exception->getStatusCode(), + 'detail' => $this->debug ? $exception->getMessage() : $exception->getStatusText(), + ]; + if ($this->debug) { + $data['class'] = $exception->getClass(); + $data['trace'] = $exception->getTrace(); + } + + return $data; + } + + /** + * {@inheritdoc} + */ + public function supportsNormalization($data, $format = null): bool + { + return $data instanceof FlattenException; + } + + /** + * {@inheritdoc} + */ + public function hasCacheableSupportsMethod(): bool + { + return true; + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ProblemNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ProblemNormalizerTest.php new file mode 100644 index 0000000000000..4a754ac5b7d6c --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ProblemNormalizerTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ErrorHandler\Exception\FlattenException; +use Symfony\Component\Serializer\Normalizer\ProblemNormalizer; + +class ProblemNormalizerTest extends TestCase +{ + /** + * @var ProblemNormalizer + */ + private $normalizer; + + protected function setUp(): void + { + $this->normalizer = new ProblemNormalizer(false); + } + + public function testSupportNormalization() + { + $this->assertTrue($this->normalizer->supportsNormalization(FlattenException::createFromThrowable(new \Exception()))); + $this->assertFalse($this->normalizer->supportsNormalization(new \Exception())); + $this->assertFalse($this->normalizer->supportsNormalization(new \stdClass())); + } + + public function testNormalize() + { + $expected = [ + 'type' => 'https://tools.ietf.org/html/rfc2616#section-10', + 'title' => 'An error occurred', + 'status' => 500, + 'detail' => 'Internal Server Error', + ]; + + $this->assertSame($expected, $this->normalizer->normalize(FlattenException::createFromThrowable(new \RuntimeException('Error')))); + } +} diff --git a/src/Symfony/Component/Serializer/composer.json b/src/Symfony/Component/Serializer/composer.json index 45c185a364e7e..cf48812e1ebf0 100644 --- a/src/Symfony/Component/Serializer/composer.json +++ b/src/Symfony/Component/Serializer/composer.json @@ -20,17 +20,18 @@ "symfony/polyfill-ctype": "~1.8" }, "require-dev": { - "symfony/yaml": "^3.4|^4.0|^5.0", + "doctrine/annotations": "~1.0", + "doctrine/cache": "~1.0", + "phpdocumentor/reflection-docblock": "^3.2|^4.0", + "symfony/cache": "^3.4|^4.0|^5.0", "symfony/config": "^3.4|^4.0|^5.0", - "symfony/property-access": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/error-handler": "^4.4|^5.0", "symfony/http-foundation": "^3.4|^4.0|^5.0", - "symfony/cache": "^3.4|^4.0|^5.0", + "symfony/property-access": "^3.4|^4.0|^5.0", "symfony/property-info": "^3.4.13|~4.0|^5.0", "symfony/validator": "^3.4|^4.0|^5.0", - "doctrine/annotations": "~1.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "doctrine/cache": "~1.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0" + "symfony/yaml": "^3.4|^4.0|^5.0" }, "conflict": { "phpdocumentor/type-resolver": "<0.2.1",