Skip to content

Commit a3c0ef1

Browse files
committed
feature #2022 Inject Twig by default instead of EngineInterface for Symfony >= 4.3 (sarcher)
This PR was squashed before being merged into the 2.6-dev branch (closes #2022). Discussion ---------- Inject Twig by default instead of EngineInterface for Symfony >= 4.3 This addresses #2020 by: * Changing the default `services.templating` value from `templating` to `twig` if Symfony is >= 4.3 (it can still be overridden in `symfony/templating` is present * Removing the type hint from `TwigExceptionController` and `ViewHandler` to allow either `Twig\Environment` or `EngineInterface` to be passed * Borrowing the bridge code from TwigBundle to handle the new Twig template lookup via try/catch Seems to work in 4.2 and 4.3, I tried it in both; we'll see what the CI says. Not sure this is the best approach but I needed something working and so far so good. Please let me know if this needs to look different. I considered just having two separate classes for with/without the templating component, but this is far less code to maintain. Commits ------- e6f8eb5 Inject Twig by default instead of EngineInterface for Symfony >= 4.3
2 parents 1646413 + e6f8eb5 commit a3c0ef1

File tree

4 files changed

+64
-6
lines changed

4 files changed

+64
-6
lines changed

Controller/TemplatingExceptionController.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\HttpFoundation\Request;
1717
use Symfony\Component\Templating\EngineInterface;
1818
use Symfony\Component\Templating\TemplateReferenceInterface;
19+
use Twig\Environment;
1920

2021
abstract class TemplatingExceptionController extends ExceptionController
2122
{
@@ -25,8 +26,18 @@ public function __construct(
2526
ViewHandlerInterface $viewHandler,
2627
ExceptionValueMap $exceptionCodes,
2728
$showException,
28-
EngineInterface $templating
29+
$templating
2930
) {
31+
if (!$templating instanceof EngineInterface && !$templating instanceof Environment) {
32+
throw new \TypeError(sprintf(
33+
'The fourth argument of %s must be an instance of %s or %s, but %s was given.',
34+
__METHOD__,
35+
EngineInterface::class,
36+
Environment::class,
37+
is_object($templating) ? get_class($templating) : gettype($templating)
38+
));
39+
}
40+
3041
parent::__construct($viewHandler, $exceptionCodes, $showException);
3142

3243
$this->templating = $templating;

Controller/TwigExceptionController.php

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
namespace FOS\RestBundle\Controller;
1313

1414
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\Templating\EngineInterface;
16+
use Twig\Environment;
17+
use Twig\Error\LoaderError;
18+
use Twig\Loader\ExistsLoaderInterface;
1519

1620
/**
1721
* Custom ExceptionController that uses the view layer and supports HTTP response status code mapping.
@@ -48,14 +52,20 @@ protected function findTemplate(Request $request, $statusCode, $showException)
4852
// For error pages, try to find a template for the specific HTTP status code and format
4953
if (!$showException) {
5054
$template = sprintf('@Twig/Exception/%s%s.%s.twig', $name, $statusCode, $format);
51-
if ($this->templating->exists($template)) {
55+
if (
56+
($this->templating instanceof EngineInterface && $this->templating->exists($template)) ||
57+
($this->templating instanceof Environment && $this->templateExists($template))
58+
) {
5259
return $template;
5360
}
5461
}
5562

5663
// try to find a template for the given format
5764
$template = sprintf('@Twig/Exception/%s.%s.twig', $name, $format);
58-
if ($this->templating->exists($template)) {
65+
if (
66+
($this->templating instanceof EngineInterface && $this->templating->exists($template)) ||
67+
($this->templating instanceof Environment && $this->templateExists($template))
68+
) {
5969
return $template;
6070
}
6171

@@ -64,4 +74,27 @@ protected function findTemplate(Request $request, $statusCode, $showException)
6474

6575
return sprintf('@Twig/Exception/%s.html.twig', $showException ? 'exception_full' : $name);
6676
}
77+
78+
/**
79+
* See if a template exists using the modern Twig mechanism.
80+
*
81+
* This code is based on TwigBundle and should be removed when the minimum required
82+
* version of Twig is >= 3.0. See src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php
83+
*/
84+
private function templateExists(string $template): bool
85+
{
86+
$loader = $this->templating->getLoader();
87+
if ($loader instanceof ExistsLoaderInterface || method_exists($loader, 'exists')) {
88+
return $loader->exists($template);
89+
}
90+
91+
try {
92+
$loader->getSourceContext($template)->getCode();
93+
94+
return true;
95+
} catch (LoaderError $e) {
96+
}
97+
98+
return false;
99+
}
67100
}

DependencyInjection/Compiler/TwigExceptionPass.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function process(ContainerBuilder $container)
2828
if ($container->hasDefinition('fos_rest.exception_listener') &&
2929
null === $container->getDefinition('fos_rest.exception_listener')->getArgument(0)
3030
) {
31-
if (isset($container->getParameter('kernel.bundles')['TwigBundle']) && $container->has('templating.engine.twig')) {
31+
if (isset($container->getParameter('kernel.bundles')['TwigBundle']) && ($container->has('templating.engine.twig') || $container->has('twig'))) {
3232
// only use this when TwigBundle is enabled and the deprecated SF templating integration is used
3333
$controller = Kernel::VERSION_ID >= 40100 ? 'fos_rest.exception.twig_controller::showAction' : 'fos_rest.exception.twig_controller:showAction';
3434
} else {
@@ -39,7 +39,11 @@ public function process(ContainerBuilder $container)
3939
}
4040

4141
if (!$container->has('templating.engine.twig')) {
42-
$container->removeDefinition('fos_rest.exception.twig_controller');
42+
if ($container->has('twig') && $container->has('fos_rest.exception.twig_controller')) {
43+
$container->findDefinition('fos_rest.exception.twig_controller')->replaceArgument(3, $container->findDefinition('twig'));
44+
} else {
45+
$container->removeDefinition('fos_rest.exception.twig_controller');
46+
}
4347
}
4448
}
4549
}

View/ViewHandler.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
2323
use Symfony\Component\Templating\EngineInterface;
2424
use Symfony\Component\Templating\TemplateReferenceInterface;
25+
use Twig\Environment;
2526

2627
/**
2728
* View may be used in controllers to build up a response in a format agnostic way
@@ -122,7 +123,7 @@ class ViewHandler implements ConfigurableViewHandlerInterface
122123
public function __construct(
123124
UrlGeneratorInterface $urlGenerator,
124125
Serializer $serializer,
125-
EngineInterface $templating = null,
126+
$templating,
126127
RequestStack $requestStack,
127128
array $formats = null,
128129
$failedValidationCode = Response::HTTP_BAD_REQUEST,
@@ -132,6 +133,15 @@ public function __construct(
132133
$defaultEngine = 'twig',
133134
array $options = []
134135
) {
136+
if (null !== $templating && !$templating instanceof EngineInterface && !$templating instanceof Environment) {
137+
throw new \TypeError(sprintf(
138+
'If provided, the templating engine must be an instance of %s or %s, but %s was given.',
139+
EngineInterface::class,
140+
Environment::class,
141+
get_class($templating)
142+
));
143+
}
144+
135145
$this->urlGenerator = $urlGenerator;
136146
$this->serializer = $serializer;
137147
$this->templating = $templating;

0 commit comments

Comments
 (0)