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", pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy