Skip to content

Commit fb525dd

Browse files
ycerutonicolas-grekas
authored andcommitted
[ErrorHandler] help finish the PR
1 parent 3a9a10e commit fb525dd

File tree

4 files changed

+165
-1
lines changed

4 files changed

+165
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
4.4.0
55
-----
66

7+
* added a new `TwigErrorRenderer` for `html` format, integrated with the `ErrorHandler` component
78
* marked all classes extending twig as `@final`
89
* deprecated to pass `$rootDir` and `$fileLinkFormatter` as 5th and 6th argument respectively to the
910
`DebugCommand::__construct()` method, swap the variables position.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Twig\ErrorRenderer;
13+
14+
use Symfony\Component\ErrorHandler\ErrorRenderer\ErrorRendererInterface;
15+
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
16+
use Symfony\Component\ErrorHandler\Exception\FlattenException;
17+
use Twig\Environment;
18+
use Twig\Error\LoaderError;
19+
use Twig\Loader\ExistsLoaderInterface;
20+
21+
/**
22+
* Provides the ability to render custom Twig-based HTML error pages
23+
* in non-debug mode, otherwise falls back to HtmlErrorRenderer.
24+
*
25+
* @author Yonel Ceruto <[email protected]>
26+
*/
27+
class TwigErrorRenderer implements ErrorRendererInterface
28+
{
29+
private $twig;
30+
private $fallbackErrorRenderer;
31+
private $debug;
32+
33+
public function __construct(Environment $twig, HtmlErrorRenderer $fallbackErrorRenderer = null, bool $debug = false)
34+
{
35+
$this->twig = $twig;
36+
$this->fallbackErrorRenderer = $fallbackErrorRenderer ?? new HtmlErrorRenderer();
37+
$this->debug = $debug;
38+
}
39+
40+
/**
41+
* {@inheritdoc}
42+
*/
43+
public function render(\Throwable $exception): FlattenException
44+
{
45+
$exception = $this->fallbackErrorRenderer->render($exception);
46+
47+
if ($this->debug || !$template = $this->findTemplate($exception->getStatusCode())) {
48+
return $exception;
49+
}
50+
51+
return $exception->setAsString($this->twig->render($template, [
52+
'legacy' => false, // to be removed in 5.0
53+
'exception' => $exception,
54+
'status_code' => $exception->getStatusCode(),
55+
'status_text' => $exception->getStatusText(),
56+
]));
57+
}
58+
59+
private function findTemplate(int $statusCode): ?string
60+
{
61+
$template = sprintf('@Twig/Exception/error%s.html.twig', $statusCode);
62+
if ($this->templateExists($template)) {
63+
return $template;
64+
}
65+
66+
$template = '@Twig/Exception/error.html.twig';
67+
if ($this->templateExists($template)) {
68+
return $template;
69+
}
70+
71+
return null;
72+
}
73+
74+
/**
75+
* To be removed in 5.0.
76+
*
77+
* Use instead:
78+
*
79+
* $this->twig->getLoader()->exists($template)
80+
*/
81+
private function templateExists(string $template): bool
82+
{
83+
$loader = $this->twig->getLoader();
84+
if ($loader instanceof ExistsLoaderInterface || method_exists($loader, 'exists')) {
85+
return $loader->exists($template);
86+
}
87+
88+
try {
89+
$loader->getSourceContext($template);
90+
91+
return true;
92+
} catch (LoaderError $e) {
93+
}
94+
95+
return false;
96+
}
97+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Twig\Tests\ErrorRenderer;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Bridge\Twig\ErrorRenderer\TwigErrorRenderer;
16+
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
17+
use Symfony\Component\ErrorHandler\Exception\FlattenException;
18+
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
19+
use Twig\Environment;
20+
use Twig\Loader\ArrayLoader;
21+
22+
class TwigErrorRendererTest extends TestCase
23+
{
24+
public function testFallbackToNativeRendererIfDebugOn()
25+
{
26+
$exception = new \Exception();
27+
28+
$twig = $this->createMock(Environment::class);
29+
$nativeRenderer = $this->createMock(HtmlErrorRenderer::class);
30+
$nativeRenderer
31+
->expects($this->once())
32+
->method('render')
33+
->with($exception)
34+
;
35+
36+
(new TwigErrorRenderer($twig, $nativeRenderer, true))->render(new \Exception());
37+
}
38+
39+
public function testFallbackToNativeRendererIfCustomTemplateNotFound()
40+
{
41+
$exception = new NotFoundHttpException();
42+
43+
$twig = new Environment(new ArrayLoader([]));
44+
45+
$nativeRenderer = $this->createMock(HtmlErrorRenderer::class);
46+
$nativeRenderer
47+
->expects($this->once())
48+
->method('render')
49+
->with($exception)
50+
->willReturn(FlattenException::createFromThrowable($exception))
51+
;
52+
53+
(new TwigErrorRenderer($twig, $nativeRenderer, false))->render($exception);
54+
}
55+
56+
public function testRenderCustomErrorTemplate()
57+
{
58+
$twig = new Environment(new ArrayLoader([
59+
'@Twig/Exception/error404.html.twig' => '<h1>Page Not Found</h1>',
60+
]));
61+
$exception = (new TwigErrorRenderer($twig))->render(new NotFoundHttpException());
62+
63+
$this->assertSame('<h1>Page Not Found</h1>', $exception->getAsString());
64+
}
65+
}

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@
2424
"egulias/email-validator": "^2.1.10",
2525
"symfony/asset": "^3.4|^4.0|^5.0",
2626
"symfony/dependency-injection": "^3.4|^4.0|^5.0",
27+
"symfony/error-handler": "^4.4|^5.0",
2728
"symfony/finder": "^3.4|^4.0|^5.0",
2829
"symfony/form": "^4.4|^5.0",
2930
"symfony/http-foundation": "^4.3|^5.0",
30-
"symfony/http-kernel": "^3.4|^4.0",
31+
"symfony/http-kernel": "^4.4",
3132
"symfony/mime": "^4.3|^5.0",
3233
"symfony/polyfill-intl-icu": "~1.0",
3334
"symfony/routing": "^3.4|^4.0|^5.0",

0 commit comments

Comments
 (0)