-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[TwigBridge] Improve error rendering when running tests #58456
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning Content-type: text/html
when using the CliErrorRenderer is wrong as it does not return HTML (and could lead to XSS if some of the content looks like HTML tags). The CliErrorRenderer should be updated to add a header setting the content type to text/plain
in the FLattenException it returns (which won't change anything when using it for actual CLI usage, but will behave properly when using the CliErrorRenderer to return an HTTP response)
I made the changes requested in the review. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related to #58070, so it's a 👍 for me, definitely!
Edit: approved once tests are green of course 😄
52d8157
to
1ce9351
Compare
bool|callable $debug = false, | ||
) { | ||
$this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer(); | ||
$this->fallbackErrorRenderer = \in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) ? new CliErrorRenderer() : ($fallbackErrorRenderer ?? new HtmlErrorRenderer()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is breaking DI: if one dared to pass a $fallbackErrorRenderer, we have to honor it.
In order to pass a CliErrorRenderer, we should use DI instead.
If this targets tests, then we might pass a CliErrorRenderer service when framework.test
is enabled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we change that line by:
$this->fallbackErrorRenderer = $fallbackErrorRenderer ?? (\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) ? new CliErrorRenderer() : new HtmlErrorRenderer());
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My suggestion in #58456 (review) was about changing the default fallback renderer, which is indeed what your new suggestion does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would this achieve what you want?
we do define a $fallbackErrorRenderer so I guess the new code would be dead code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. The feature no longer works after changing that line.
What about improving the HtmlErrorRendered instead so that it dumps a text version after the HTML one in an HTML comment? (we'd just need to ensure the dump cannot break the HTML comment to make this XSS-safe) |
@nicolas-grekas that would be like iterating over the idea implemented in #31514. But, this proposal tries to fix this for real and never output HTML content in the console. I pushed a commit a043bf7 trying to do some of the things that you said, but honestly I don't know what I'm doing 😐 |
Nicolas gave me more clues about how to solve this so I submitted a new commit with a different approach. Thanks. |
src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ErrorRendererPass.php
Show resolved
Hide resolved
src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ErrorRendererPass.php
Show resolved
Hide resolved
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | ||
use Symfony\Component\Workflow\Workflow; | ||
use Symfony\Component\Yaml\Yaml; | ||
use function Symfony\Component\DependencyInjection\Loader\Configurator\inline_service; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is unused (like many other use statements)
// in the 'test' environment, use the CLI error renderer as the default one | ||
if ($container->hasDefinition('test.client')) { | ||
$container->getDefinition('twig.error_renderer.html') | ||
->setArgument(1, new Definition(CliErrorRenderer::class)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wouldn't this break if the error-handler component is not installed (which is not a dependency of the bundle right now) ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and maybe we need a conflict with older versions of the component to ensure we don't inject a version that does not properly report a plaintext content type (and so is subject to XSS)
// in the 'test' environment, use the CLI error renderer as the default one | ||
if ($container->hasDefinition('test.client')) { | ||
$container->getDefinition('twig.error_renderer.html') | ||
->setArgument(1, new Definition(CliErrorRenderer::class)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the decision of which fallback error renderer is the best does not depend on the environment but on the SAPI value, as you did at the beginning. What about creating a service factory that injects the proper error renderer depending on the SAPI value?
By the way, the issue described (PR description) also applies when the twig-bundle is not installed, so updating the |
Solving this in a bundle looks appropriate to me. DI is the proper way to improve the DX here. Let's keep things simple. |
My 5 cents on achieving this by using a factory to determine which renderer is most appropriate, depending on the SAPI value: Patch (Click to open)
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/error_renderer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/error_renderer.php
index 67f28ce44d..a40fb2aa97 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/error_renderer.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/error_renderer.php
@@ -11,7 +11,10 @@
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
+use Symfony\Component\ErrorHandler\ErrorRenderer\CliErrorRenderer;
+use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
+use Symfony\Component\ErrorHandler\ErrorRenderer\SapiErrorRendererFactory;
return static function (ContainerConfigurator $container) {
$container->services()
@@ -32,7 +35,18 @@ return static function (ContainerConfigurator $container) {
service('logger')->nullOnInvalid(),
])
+ ->set('error_handler.error_renderer.cli', CliErrorRenderer::class)
+
+ ->set('error_handler.error_renderer.default', ErrorRendererInterface::class)
+ ->factory([SapiErrorRendererFactory::class, 'create'])
+ ->args([
+ service('error_renderer.cli'),
+ service('error_renderer.html'),
+ ])
+
->alias('error_renderer.html', 'error_handler.error_renderer.html')
- ->alias('error_renderer', 'error_renderer.html')
+ ->alias('error_renderer.cli', 'error_handler.error_renderer.cli')
+ ->alias('error_renderer.default', 'error_handler.error_renderer.default')
+ ->alias('error_renderer', 'error_renderer.default')
;
};
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php
index d689d42995..eadc71db13 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php
@@ -213,7 +213,7 @@ return static function (ContainerConfigurator $container) {
inline_service()
->factory([SerializerErrorRenderer::class, 'getPreferredFormat'])
->args([service('request_stack')]),
- service('error_renderer.html'),
+ service('error_renderer.default'),
inline_service()
->factory([HtmlErrorRenderer::class, 'isDebug'])
->args([service('request_stack'), param('kernel.debug')]),
diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json
index af83a9a13f..fe49a27735 100644
--- a/src/Symfony/Bundle/FrameworkBundle/composer.json
+++ b/src/Symfony/Bundle/FrameworkBundle/composer.json
@@ -23,7 +23,7 @@
"symfony/config": "^6.4|^7.0",
"symfony/dependency-injection": "^7.2",
"symfony/deprecation-contracts": "^2.5|^3",
- "symfony/error-handler": "^6.4|^7.0",
+ "symfony/error-handler": "^7.2",
"symfony/event-dispatcher": "^6.4|^7.0",
"symfony/http-foundation": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0",
diff --git a/src/Symfony/Component/ErrorHandler/ErrorRenderer/SapiErrorRendererFactory.php b/src/Symfony/Component/ErrorHandler/ErrorRenderer/SapiErrorRendererFactory.php
new file mode 100644
index 0000000000..89b6e5b75c
--- /dev/null
+++ b/src/Symfony/Component/ErrorHandler/ErrorRenderer/SapiErrorRendererFactory.php
@@ -0,0 +1,20 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\ErrorHandler\ErrorRenderer;
+
+final class SapiErrorRendererFactory
+{
+ public static function create(ErrorRendererInterface $cliErrorRenderer, ErrorRendererInterface $htmlErrorRenderer): ErrorRendererInterface
+ {
+ return \in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) ? $cliErrorRenderer : $htmlErrorRenderer;
+ }
+} DI graph after the patch: In fact, no change is required in |
Let's close this in favor of #60033, which tries to achieve the same goals but with a different strategy. Thanks Yonel! |
Exception handling in Symfony is fantastic. E.g. I add a
$foo = 0 / 0;
statement in the Symfony Demo app and I see this nice and useful exception page in debug mode:The problem is that if I run tests, that exception will output the same content in the terminal. Thousands and thousands of lines of HTML + CSS + JS 😐
This is easily one of the worst and most frustrating parts of Symfony. In #31514 we tried to improve this by adding an HTML comment at the beginning and the end of the long HTML exception page to quickly figure out the exception in the console.
This PR proposes to always use the
CliRenderer
when the exception is going to be rendered in a console/terminal context.For example, the same exception as before now would look like this 🙌