Skip to content

Commit dafcc21

Browse files
committed
Added native ErrorRenderer as fallback system and deprecating templates
1 parent 22c73a9 commit dafcc21

19 files changed

+116
-35
lines changed

src/Symfony/Bundle/SecurityBundle/Tests/Functional/JsonLoginTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,6 @@ public function testDefaultJsonLoginBadRequest()
7070

7171
$this->assertSame(400, $response->getStatusCode());
7272
$this->assertSame('application/json', $response->headers->get('Content-Type'));
73-
$this->assertArraySubset(['error' => ['code' => 400, 'message' => 'Bad Request']], json_decode($response->getContent(), true));
73+
$this->assertArraySubset(['title' => 'Bad Request', 'status' => 400, 'detail' => 'Invalid JSON.'], json_decode($response->getContent(), true));
7474
}
7575
}

src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/bundles.php

-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,5 @@
1212
return [
1313
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
1414
new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
15-
new Symfony\Bundle\TwigBundle\TwigBundle(),
1615
new Symfony\Bundle\SecurityBundle\Tests\Functional\Bundle\JsonLoginBundle\JsonLoginBundle(),
1716
];

src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/config.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
imports:
2-
- { resource: ./../config/default.yml }
2+
- { resource: ./../config/framework.yml }
33

44
security:
55
encoders:

src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/JsonLogin/custom_handlers.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
imports:
2-
- { resource: ./../config/default.yml }
2+
- { resource: ./../config/framework.yml }
33

44
security:
55
encoders:

src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php

+67-28
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,15 @@
1111

1212
namespace Symfony\Bundle\TwigBundle\Controller;
1313

14+
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRenderer;
15+
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
16+
use Symfony\Component\ErrorHandler\ErrorRenderer\JsonErrorRenderer;
17+
use Symfony\Component\ErrorHandler\ErrorRenderer\TxtErrorRenderer;
18+
use Symfony\Component\ErrorHandler\ErrorRenderer\XmlErrorRenderer;
19+
use Symfony\Component\ErrorHandler\Exception\ErrorRendererNotFoundException;
1420
use Symfony\Component\ErrorHandler\Exception\FlattenException;
1521
use Symfony\Component\HttpFoundation\Request;
1622
use Symfony\Component\HttpFoundation\Response;
17-
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
1823
use Twig\Environment;
1924
use Twig\Error\LoaderError;
2025
use Twig\Loader\ExistsLoaderInterface;
@@ -30,15 +35,22 @@ class ExceptionController
3035
{
3136
protected $twig;
3237
protected $debug;
38+
private $errorRenderer;
3339

3440
/**
3541
* @param Environment $twig
3642
* @param bool $debug Show error (false) or exception (true) pages by default
3743
*/
38-
public function __construct(Environment $twig, bool $debug)
44+
public function __construct(Environment $twig, bool $debug, ErrorRenderer $errorRenderer = null)
3945
{
4046
$this->twig = $twig;
4147
$this->debug = $debug;
48+
$this->errorRenderer = $errorRenderer ?? new ErrorRenderer([
49+
new HtmlErrorRenderer($debug),
50+
new JsonErrorRenderer($debug),
51+
new XmlErrorRenderer($debug),
52+
new TxtErrorRenderer($debug),
53+
]);
4254
}
4355

4456
/**
@@ -49,26 +61,12 @@ public function __construct(Environment $twig, bool $debug)
4961
* be used.
5062
*
5163
* @return Response
52-
*
53-
* @throws \InvalidArgumentException When the exception template does not exist
5464
*/
55-
public function showAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null)
65+
public function showAction(Request $request, FlattenException $exception)
5666
{
57-
$currentContent = $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level', -1));
58-
$showException = $request->attributes->get('showException', $this->debug); // As opposed to an additional parameter, this maintains BC
59-
60-
$code = $exception->getStatusCode();
61-
62-
return new Response($this->twig->render(
63-
(string) $this->findTemplate($request, $request->getRequestFormat(), $code, $showException),
64-
[
65-
'status_code' => $code,
66-
'status_text' => isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : '',
67-
'exception' => $exception,
68-
'logger' => $logger,
69-
'currentContent' => $currentContent,
70-
]
71-
), 200, ['Content-Type' => $request->getMimeType($request->getRequestFormat()) ?: 'text/html']);
67+
return new Response($this->render($exception, $request), 200, [
68+
'Content-Type' => $request->getMimeType($request->getRequestFormat())
69+
]);
7270
}
7371

7472
/**
@@ -94,32 +92,45 @@ protected function getAndCleanOutputBuffering($startObLevel)
9492
* @param bool $showException
9593
*
9694
* @return string
95+
*
96+
* @deprecated since Symfony 4.4
9797
*/
9898
protected function findTemplate(Request $request, $format, $code, $showException)
9999
{
100-
$name = $showException ? 'exception' : 'error';
101-
if ($showException && 'html' == $format) {
102-
$name = 'exception_full';
100+
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.4, use "findTwigTemplate()" method instead.', __METHOD__), E_USER_DEPRECATED);
101+
102+
if ($template = $this->findTwigTemplate($code, $format, $showException)) {
103+
return $template;
103104
}
104105

106+
// default to a generic HTML exception
107+
$request->setRequestFormat('html');
108+
109+
return sprintf('@Twig/Exception/%s.html.twig', $showException ? 'exception_full' : 'error');
110+
}
111+
112+
protected function findTwigTemplate(int $statusCode, string $format, bool $showException): ?string
113+
{
105114
// For error pages, try to find a template for the specific HTTP status code and format
106115
if (!$showException) {
107-
$template = sprintf('@Twig/Exception/%s%s.%s.twig', $name, $code, $format);
116+
$template = sprintf('@Twig/Exception/error%s.%s.twig', $statusCode, $format);
108117
if ($this->templateExists($template)) {
109118
return $template;
110119
}
111120
}
112121

122+
$name = 'error';
123+
if ($showException) {
124+
$name = 'html' === $format ? 'exception_full' : 'exception';
125+
}
126+
113127
// try to find a template for the given format
114128
$template = sprintf('@Twig/Exception/%s.%s.twig', $name, $format);
115129
if ($this->templateExists($template)) {
116130
return $template;
117131
}
118132

119-
// default to a generic HTML exception
120-
$request->setRequestFormat('html');
121-
122-
return sprintf('@Twig/Exception/%s.html.twig', $showException ? 'exception_full' : $name);
133+
return null;
123134
}
124135

125136
// to be removed when the minimum required version of Twig is >= 3.0
@@ -141,4 +152,32 @@ protected function templateExists($template)
141152

142153
return false;
143154
}
155+
156+
private function render(FlattenException $exception, Request $request): string
157+
{
158+
$statusCode = $exception->getStatusCode();
159+
$logger = $request->attributes->get('logger');
160+
$showException = $request->attributes->get('showException', $this->debug);
161+
$currentContent = $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level', -1));
162+
163+
$template = $this->findTwigTemplate($statusCode, $request->getRequestFormat(), $showException);
164+
165+
if (null === $template) {
166+
try {
167+
// fallback to the native renderer system
168+
return $this->errorRenderer->render($exception, $request->getRequestFormat());
169+
} catch (ErrorRendererNotFoundException $e) {
170+
$request->setRequestFormat('html');
171+
$template = sprintf('@Twig/Exception/%s.html.twig', $showException ? 'exception_full' : 'error');
172+
}
173+
}
174+
175+
return $this->twig->render($template, [
176+
'status_code' => $statusCode,
177+
'status_text' => Response::$statusTexts[$statusCode] ?? '',
178+
'exception' => $exception,
179+
'logger' => $logger,
180+
'currentContent' => $currentContent,
181+
]);
182+
}
144183
}

src/Symfony/Bundle/TwigBundle/Controller/PreviewErrorController.php

-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ public function previewErrorPageAction(Request $request, $code)
4747
'_controller' => $this->controller,
4848
'exception' => $exception,
4949
'logger' => null,
50-
'format' => $request->getRequestFormat(),
5150
'showException' => false,
5251
]);
5352

src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@
140140
<service id="twig.controller.exception" class="Symfony\Bundle\TwigBundle\Controller\ExceptionController" public="true">
141141
<argument type="service" id="twig" />
142142
<argument>%kernel.debug%</argument>
143+
<argument type="service" id="error_handler.error_renderer" on-invalid="null" />
143144
</service>
144145

145146
<service id="twig.controller.preview_error" class="Symfony\Bundle\TwigBundle\Controller\PreviewErrorController" public="true">
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
{% deprecated 'The template "' ~ _self ~'" is deprecated since Symfony 4.4, will be removed in 5.0.' %}
12
{{ include('@Twig/Exception/error.xml.twig') }}

src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.js.twig

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{% deprecated 'The template "' ~ _self ~'" is deprecated since Symfony 4.4, will be removed in 5.0.' %}
12
/*
23
{{ status_code }} {{ status_text }}
34

Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
{% deprecated 'The template "' ~ _self ~'" is deprecated since Symfony 4.4, will be removed in 5.0.' %}
12
{{ { 'error': { 'code': status_code, 'message': status_text } }|json_encode|raw }}
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
{% deprecated 'The template "' ~ _self ~'" is deprecated since Symfony 4.4, will be removed in 5.0.' %}
12
{{ include('@Twig/Exception/error.xml.twig') }}

src/Symfony/Bundle/TwigBundle/Resources/views/Exception/error.txt.twig

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{% deprecated 'The template "' ~ _self ~'" is deprecated since Symfony 4.4, will be removed in 5.0.' %}
12
Oops! An Error Occurred
23
=======================
34

Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{% deprecated 'The template "' ~ _self ~'" is deprecated since Symfony 4.4, will be removed in 5.0.' %}
12
<?xml version="1.0" encoding="{{ _charset }}" ?>
23

34
<error code="{{ status_code }}" message="{{ status_text }}" />
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{% deprecated 'The template "' ~ _self ~'" is deprecated since Symfony 4.4, will be removed in 5.0.' %}
12
/*
23
{{ include('@Twig/Exception/exception.txt.twig', { exception: exception }) }}
34
*/
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
{% deprecated 'The template "' ~ _self ~'" is deprecated since Symfony 4.4, will be removed in 5.0.' %}
12
{{ { 'error': { 'code': status_code, 'message': status_text, 'exception': exception.toarray } }|json_encode|raw }}
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
{% deprecated 'The template "' ~ _self ~'" is deprecated since Symfony 4.4, will be removed in 5.0.' %}
12
{{ include('@Twig/Exception/exception.xml.twig', { exception: exception }) }}

src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.txt.twig

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{% deprecated 'The template "' ~ _self ~'" is deprecated since Symfony 4.4, will be removed in 5.0.' %}
12
[exception] {{ status_code ~ ' | ' ~ status_text ~ ' | ' ~ exception.class }}
23
[message] {{ exception.message }}
34
{% for i, e in exception.toarray %}

src/Symfony/Bundle/TwigBundle/Resources/views/Exception/exception.xml.twig

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{% deprecated 'The template "' ~ _self ~'" is deprecated since Symfony 4.4, will be removed in 5.0.' %}
12
<?xml version="1.0" encoding="{{ _charset }}" ?>
23

34
<error code="{{ status_code }}" message="{{ status_text }}">

src/Symfony/Bundle/TwigBundle/Tests/Controller/ExceptionControllerTest.php

+34-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
use Symfony\Bundle\TwigBundle\Controller\ExceptionController;
1515
use Symfony\Bundle\TwigBundle\Tests\TestCase;
16+
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRenderer;
17+
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface;
1618
use Symfony\Component\ErrorHandler\Exception\FlattenException;
1719
use Symfony\Component\HttpFoundation\Request;
1820
use Twig\Environment;
@@ -35,11 +37,27 @@ public function testShowActionCanBeForcedToShowErrorPage()
3537
$this->assertEquals('<html>not found</html>', $response->getContent());
3638
}
3739

40+
public function testFallbackToErrorRendererIfNoTemplateForRequestedFormat()
41+
{
42+
$twig = $this->createTwigEnv(['@Twig/Exception/error.html.twig' => '<html></html>']);
43+
44+
$request = $this->createRequest('foo');
45+
$exception = FlattenException::create(new \Exception('bar'));
46+
$errorRenderer = new ErrorRenderer([
47+
new FooErrorRenderer(),
48+
]);
49+
$controller = new ExceptionController($twig, false, $errorRenderer);
50+
$response = $controller->showAction($request, $exception);
51+
52+
$this->assertSame('foo', $request->getRequestFormat());
53+
$this->assertSame('bar', $response->getContent());
54+
}
55+
3856
public function testFallbackToHtmlIfNoTemplateForRequestedFormat()
3957
{
4058
$twig = $this->createTwigEnv(['@Twig/Exception/error.html.twig' => '<html></html>']);
4159

42-
$request = $this->createRequest('txt');
60+
$request = $this->createRequest('foo');
4361
$exception = FlattenException::create(new \Exception());
4462
$controller = new ExceptionController($twig, false);
4563

@@ -52,7 +70,7 @@ public function testFallbackToHtmlWithFullExceptionIfNoTemplateForRequestedForma
5270
{
5371
$twig = $this->createTwigEnv(['@Twig/Exception/exception_full.html.twig' => '<html></html>']);
5472

55-
$request = $this->createRequest('txt');
73+
$request = $this->createRequest('foo');
5674
$request->attributes->set('showException', true);
5775
$exception = FlattenException::create(new \Exception());
5876
$controller = new ExceptionController($twig, false);
@@ -90,3 +108,17 @@ private function createTwigEnv(array $templates)
90108
return new Environment(new ArrayLoader($templates));
91109
}
92110
}
111+
112+
113+
class FooErrorRenderer implements ErrorRendererInterface
114+
{
115+
public static function getFormat(): string
116+
{
117+
return 'foo';
118+
}
119+
120+
public function render(FlattenException $exception): string
121+
{
122+
return $exception->getMessage();
123+
}
124+
}

0 commit comments

Comments
 (0)