From 5d29d766122f307b3ac38b68b163e555c9807271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 24 Mar 2021 19:24:50 +0100 Subject: [PATCH] [FrameworkBundle][HttpKernel][TwigBridge] Add an helper to generate fragments URL --- src/Symfony/Bridge/Twig/CHANGELOG.md | 1 + .../Twig/Extension/HttpKernelExtension.php | 1 + .../Twig/Extension/HttpKernelRuntime.php | 14 ++- .../Extension/HttpKernelExtensionTest.php | 34 +++++++ .../Bundle/FrameworkBundle/CHANGELOG.md | 1 + .../Resources/config/fragment_renderer.php | 6 ++ .../FrameworkExtensionTest.php | 3 + .../Controller/FragmentController.php | 7 ++ .../TestBundle/Resources/config/routing.yml | 4 + .../Tests/Functional/FragmentTest.php | 8 ++ .../TwigBundle/Resources/config/twig.php | 2 +- src/Symfony/Component/HttpKernel/CHANGELOG.md | 1 + .../AbstractSurrogateFragmentRenderer.php | 9 +- .../Fragment/FragmentUriGenerator.php | 93 +++++++++++++++++++ .../FragmentUriGeneratorInterface.php | 34 +++++++ .../Fragment/HIncludeFragmentRenderer.php | 7 +- .../Fragment/RoutableFragmentRenderer.php | 44 +-------- 17 files changed, 214 insertions(+), 55 deletions(-) create mode 100644 src/Symfony/Component/HttpKernel/Fragment/FragmentUriGenerator.php create mode 100644 src/Symfony/Component/HttpKernel/Fragment/FragmentUriGeneratorInterface.php diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index 90bc89861711f..200ee29a03ea8 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * Add a new `markAsPublic` method on `NotificationEmail` to change the `importance` context option to null after creation +* Add a new `fragment_uri()` helper to generate the URI of a fragment 5.3.0 ----- diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php index f4b3e4b66953c..1af9ddb23cf51 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelExtension.php @@ -30,6 +30,7 @@ public function getFunctions(): array return [ new TwigFunction('render', [HttpKernelRuntime::class, 'renderFragment'], ['is_safe' => ['html']]), new TwigFunction('render_*', [HttpKernelRuntime::class, 'renderFragmentStrategy'], ['is_safe' => ['html']]), + new TwigFunction('fragment_uri', [HttpKernelRuntime::class, 'generateFragmentUri']), new TwigFunction('controller', static::class.'::controller'), ]; } diff --git a/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php b/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php index ebc9e4469b558..ab83054a9ff0f 100644 --- a/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php +++ b/src/Symfony/Bridge/Twig/Extension/HttpKernelRuntime.php @@ -13,6 +13,7 @@ use Symfony\Component\HttpKernel\Controller\ControllerReference; use Symfony\Component\HttpKernel\Fragment\FragmentHandler; +use Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface; /** * Provides integration with the HttpKernel component. @@ -22,10 +23,12 @@ final class HttpKernelRuntime { private $handler; + private $fragmentUriGenerator; - public function __construct(FragmentHandler $handler) + public function __construct(FragmentHandler $handler, FragmentUriGeneratorInterface $fragmentUriGenerator = null) { $this->handler = $handler; + $this->fragmentUriGenerator = $fragmentUriGenerator; } /** @@ -54,4 +57,13 @@ public function renderFragmentStrategy(string $strategy, $uri, array $options = { return $this->handler->render($uri, $strategy, $options); } + + public function generateFragmentUri(ControllerReference $controller, bool $absolute = false, bool $strict = true, bool $sign = true): string + { + if (null === $this->fragmentUriGenerator) { + throw new \LogicException(sprintf('An instance of "%s" must be provided to use "%s()".', FragmentUriGeneratorInterface::class, __METHOD__)); + } + + return $this->fragmentUriGenerator->generate($controller, null, $absolute, $strict, $sign); + } } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php index 5fa1ef3bad62c..a3294db0d2ae6 100644 --- a/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Extension/HttpKernelExtensionTest.php @@ -14,11 +14,14 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\Twig\Extension\HttpKernelExtension; use Symfony\Bridge\Twig\Extension\HttpKernelRuntime; +use Symfony\Bundle\FrameworkBundle\Controller\TemplateController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Fragment\FragmentHandler; use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; +use Symfony\Component\HttpKernel\Fragment\FragmentUriGenerator; +use Symfony\Component\HttpKernel\UriSigner; use Twig\Environment; use Twig\Loader\ArrayLoader; use Twig\RuntimeLoader\RuntimeLoaderInterface; @@ -53,6 +56,37 @@ public function testUnknownFragmentRenderer() $renderer->render('/foo'); } + public function testGenerateFragmentUri() + { + if (!class_exists(FragmentUriGenerator::class)) { + $this->markTestSkipped('HttpKernel 5.3+ is required'); + } + + $requestStack = new RequestStack(); + $requestStack->push(Request::create('/')); + + $fragmentHandler = new FragmentHandler($requestStack); + $fragmentUriGenerator = new FragmentUriGenerator('/_fragment', new UriSigner('s3cr3t'), $requestStack); + + $kernelRuntime = new HttpKernelRuntime($fragmentHandler, $fragmentUriGenerator); + + $loader = new ArrayLoader([ + 'index' => sprintf(<< true, 'cache' => false]); + $twig->addExtension(new HttpKernelExtension()); + + $loader = $this->createMock(RuntimeLoaderInterface::class); + $loader->expects($this->any())->method('load')->willReturnMap([ + [HttpKernelRuntime::class, $kernelRuntime], + ]); + $twig->addRuntimeLoader($loader); + + $this->assertSame('/_fragment?_hash=PP8%2FeEbn1pr27I9wmag%2FM6jYGVwUZ0l2h0vhh2OJ6CI%3D&_path=template%3Dfoo.html.twig%26_format%3Dhtml%26_locale%3Den%26_controller%3DSymfonyBundleFrameworkBundleControllerTemplateController%253A%253AtemplateAction', $twig->render('index')); + } + protected function getFragmentHandler($return) { $strategy = $this->createMock(FragmentRendererInterface::class); diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 17f36c33c8615..c82bbe82e2411 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -18,6 +18,7 @@ CHANGELOG * Deprecate all other values than "none", "php_array" and "file" for `framework.annotation.cache` * Add `KernelTestCase::getContainer()` as the best way to get a container in tests * Rename the container parameter `profiler_listener.only_master_requests` to `profiler_listener.only_main_requests` + * Add service `fragment.uri_generator` to generate the URI of a fragment 5.2.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.php index 2d42a10026f05..80d7e2cb157b1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/fragment_renderer.php @@ -13,6 +13,8 @@ use Symfony\Component\HttpKernel\DependencyInjection\LazyLoadingFragmentHandler; use Symfony\Component\HttpKernel\Fragment\EsiFragmentRenderer; +use Symfony\Component\HttpKernel\Fragment\FragmentUriGenerator; +use Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface; use Symfony\Component\HttpKernel\Fragment\HIncludeFragmentRenderer; use Symfony\Component\HttpKernel\Fragment\InlineFragmentRenderer; use Symfony\Component\HttpKernel\Fragment\SsiFragmentRenderer; @@ -31,6 +33,10 @@ param('kernel.debug'), ]) + ->set('fragment.uri_generator', FragmentUriGenerator::class) + ->args([param('fragment.path'), service('uri_signer')]) + ->alias(FragmentUriGeneratorInterface::class, 'fragment.uri_generator') + ->set('fragment.renderer.inline', InlineFragmentRenderer::class) ->args([service('http_kernel'), service('event_dispatcher')]) ->call('setFragmentPath', [param('fragment.path')]) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 9d5d1aa9fb544..7eb28b9d123db 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -49,6 +49,7 @@ use Symfony\Component\HttpClient\ScopingHttpClient; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass; +use Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface; use Symfony\Component\Messenger\Transport\TransportFactory; use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\Security\Core\Security; @@ -181,6 +182,8 @@ public function testEsiDisabled() public function testFragmentsAndHinclude() { $container = $this->createContainerFromFile('fragments_and_hinclude'); + $this->assertTrue($container->has('fragment.uri_generator')); + $this->assertTrue($container->hasAlias(FragmentUriGeneratorInterface::class)); $this->assertTrue($container->hasParameter('fragment.renderer.hinclude.global_template')); $this->assertEquals('global_hinclude_template', $container->getParameter('fragment.renderer.hinclude.global_template')); } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/FragmentController.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/FragmentController.php index 8436a3b24809a..42044570201e9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/FragmentController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/FragmentController.php @@ -15,6 +15,8 @@ use Symfony\Component\DependencyInjection\ContainerAwareTrait; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\FragmentUriGeneratorInterface; use Twig\Environment; class FragmentController implements ContainerAwareInterface @@ -45,6 +47,11 @@ public function forwardLocaleAction(Request $request) { return new Response($request->getLocale()); } + + public function fragmentUriAction(Request $request, FragmentUriGeneratorInterface $fragmentUriGenerator) + { + return new Response($fragmentUriGenerator->generate(new ControllerReference(self::class.'::indexAction'), $request)); + } } class Bar diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Resources/config/routing.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Resources/config/routing.yml index 94eb4d7ac1293..d790c1a13125b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Resources/config/routing.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Resources/config/routing.yml @@ -57,6 +57,10 @@ fragment_inlined: path: /fragment_inlined defaults: { _controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\FragmentController::inlinedAction } +fragment_uri: + path: /fragment_uri + defaults: { _controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\FragmentController::fragmentUriAction } + array_controller: path: /array_controller defaults: { _controller: [ArrayController, someAction] } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/FragmentTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/FragmentTest.php index a4ac17238a4b8..b514ed3b8e042 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/FragmentTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/FragmentTest.php @@ -44,4 +44,12 @@ public function getConfigs() [true], ]; } + + public function testGenerateFragmentUri() + { + $client = self::createClient(['test_case' => 'Fragment', 'root_config' => 'config.yml', 'debug' => true]); + $client->request('GET', '/fragment_uri'); + + $this->assertSame('/_fragment?_hash=CCRGN2D%2FoAJbeGz%2F%2FdoH3bNSPwLCrmwC1zAYCGIKJ0E%3D&_path=_format%3Dhtml%26_locale%3Den%26_controller%3DSymfony%255CBundle%255CFrameworkBundle%255CTests%255CFunctional%255CBundle%255CTestBundle%255CController%255CFragmentController%253A%253AindexAction', $client->getResponse()->getContent()); + } } diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php index aa71e0de86b15..8423c67bf56e6 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.php @@ -123,7 +123,7 @@ ->set('twig.extension.httpkernel', HttpKernelExtension::class) ->set('twig.runtime.httpkernel', HttpKernelRuntime::class) - ->args([service('fragment.handler')]) + ->args([service('fragment.handler'), service('fragment.uri_generator')->ignoreOnInvalid()]) ->set('twig.extension.httpfoundation', HttpFoundationExtension::class) ->args([service('url_helper')]) diff --git a/src/Symfony/Component/HttpKernel/CHANGELOG.md b/src/Symfony/Component/HttpKernel/CHANGELOG.md index 6d310de13e81b..1a5f67ba1db83 100644 --- a/src/Symfony/Component/HttpKernel/CHANGELOG.md +++ b/src/Symfony/Component/HttpKernel/CHANGELOG.md @@ -12,6 +12,7 @@ CHANGELOG * Deprecate `HttpKernelInterface::MASTER_REQUEST` and add `HttpKernelInterface::MAIN_REQUEST` as replacement * Deprecate `KernelEvent::isMasterRequest()` and add `isMainRequest()` as replacement * Add `#[AsController]` attribute for declaring standalone controllers on PHP 8 + * Add `FragmentUriGeneratorInterface` and `FragmentUriGenerator` to generate the URI of a fragment 5.2.0 ----- diff --git a/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php index 06d8c380bdbec..d051ad8b5f95f 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php +++ b/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php @@ -83,14 +83,7 @@ public function render($uri, Request $request, array $options = []) private function generateSignedFragmentUri(ControllerReference $uri, Request $request): string { - if (null === $this->signer) { - throw new \LogicException('You must use a URI when using the ESI rendering strategy or set a URL signer.'); - } - - // we need to sign the absolute URI, but want to return the path only. - $fragmentUri = $this->signer->sign($this->generateFragmentUri($uri, $request, true)); - - return substr($fragmentUri, \strlen($request->getSchemeAndHttpHost())); + return (new FragmentUriGenerator($this->fragmentPath, $this->signer))->generate($uri, $request); } private function containsNonScalars(array $values): bool diff --git a/src/Symfony/Component/HttpKernel/Fragment/FragmentUriGenerator.php b/src/Symfony/Component/HttpKernel/Fragment/FragmentUriGenerator.php new file mode 100644 index 0000000000000..dabe3048ffd72 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Fragment/FragmentUriGenerator.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\UriSigner; + +/** + * Generates a fragment URI. + * + * @author Kévin Dunglas + * @author Fabien Potencier + */ +final class FragmentUriGenerator implements FragmentUriGeneratorInterface +{ + private $fragmentPath; + private $signer; + private $requestStack; + + public function __construct(string $fragmentPath, UriSigner $signer = null, RequestStack $requestStack = null) + { + $this->fragmentPath = $fragmentPath; + $this->signer = $signer; + $this->requestStack = $requestStack; + } + + /** + * {@inheritDoc} + */ + public function generate(ControllerReference $controller, Request $request = null, bool $absolute = false, bool $strict = true, bool $sign = true): string + { + if (null === $request && (null === $this->requestStack || null === $request = $this->requestStack->getCurrentRequest())) { + throw new \LogicException('Generating a fragment URL can only be done when handling a Request.'); + } + + if ($sign && null === $this->signer) { + throw new \LogicException('You must use a URI when using the ESI rendering strategy or set a URL signer.'); + } + + if ($strict) { + $this->checkNonScalar($controller->attributes); + } + + // We need to forward the current _format and _locale values as we don't have + // a proper routing pattern to do the job for us. + // This makes things inconsistent if you switch from rendering a controller + // to rendering a route if the route pattern does not contain the special + // _format and _locale placeholders. + if (!isset($controller->attributes['_format'])) { + $controller->attributes['_format'] = $request->getRequestFormat(); + } + if (!isset($controller->attributes['_locale'])) { + $controller->attributes['_locale'] = $request->getLocale(); + } + + $controller->attributes['_controller'] = $controller->controller; + $controller->query['_path'] = http_build_query($controller->attributes, '', '&'); + $path = $this->fragmentPath.'?'.http_build_query($controller->query, '', '&'); + + // we need to sign the absolute URI, but want to return the path only. + $fragmentUri = $sign || $absolute ? $request->getUriForPath($path) : $request->getBaseUrl().$path; + + if (!$sign) { + return $fragmentUri; + } + + $fragmentUri = $this->signer->sign($fragmentUri); + + return $absolute ? $fragmentUri : substr($fragmentUri, \strlen($request->getSchemeAndHttpHost())); + } + + private function checkNonScalar(array $values): void + { + foreach ($values as $key => $value) { + if (\is_array($value)) { + $this->checkNonScalar($value); + } elseif (!is_scalar($value) && null !== $value) { + throw new \LogicException(sprintf('Controller attributes cannot contain non-scalar/non-null values (value for key "%s" is not a scalar or null).', $key)); + } + } + } +} diff --git a/src/Symfony/Component/HttpKernel/Fragment/FragmentUriGeneratorInterface.php b/src/Symfony/Component/HttpKernel/Fragment/FragmentUriGeneratorInterface.php new file mode 100644 index 0000000000000..d1492fa64a50b --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Fragment/FragmentUriGeneratorInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +/** + * Interface implemented by rendering strategies able to generate an URL for a fragment. + * + * @author Kévin Dunglas + */ +interface FragmentUriGeneratorInterface +{ + /** + * Generates a fragment URI for a given controller. + * + * @param bool $absolute Whether to generate an absolute URL or not + * @param bool $strict Whether to allow non-scalar attributes or not + * @param bool $sign Whether to sign the URL or not + * + * @return string A fragment URI + */ + public function generate(ControllerReference $controller, Request $request = null, bool $absolute = false, bool $strict = true, bool $sign = true): string; +} diff --git a/src/Symfony/Component/HttpKernel/Fragment/HIncludeFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/HIncludeFragmentRenderer.php index a599a26e14d3f..36106670b8123 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/HIncludeFragmentRenderer.php +++ b/src/Symfony/Component/HttpKernel/Fragment/HIncludeFragmentRenderer.php @@ -62,12 +62,7 @@ public function hasTemplating() public function render($uri, Request $request, array $options = []) { if ($uri instanceof ControllerReference) { - if (null === $this->signer) { - throw new \LogicException('You must use a proper URI when using the Hinclude rendering strategy or set a URL signer.'); - } - - // we need to sign the absolute URI, but want to return the path only. - $uri = substr($this->signer->sign($this->generateFragmentUri($uri, $request, true)), \strlen($request->getSchemeAndHttpHost())); + $uri = (new FragmentUriGenerator($this->fragmentPath, $this->signer))->generate($uri, $request); } // We need to replace ampersands in the URI with the encoded form in order to return valid html/xml content. diff --git a/src/Symfony/Component/HttpKernel/Fragment/RoutableFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/RoutableFragmentRenderer.php index c9a175758d8dd..615b5fa504e10 100644 --- a/src/Symfony/Component/HttpKernel/Fragment/RoutableFragmentRenderer.php +++ b/src/Symfony/Component/HttpKernel/Fragment/RoutableFragmentRenderer.php @@ -22,7 +22,10 @@ */ abstract class RoutableFragmentRenderer implements FragmentRendererInterface { - private $fragmentPath = '/_fragment'; + /** + * @internal + */ + protected $fragmentPath = '/_fragment'; /** * Sets the fragment path that triggers the fragment listener. @@ -44,43 +47,6 @@ public function setFragmentPath(string $path) */ protected function generateFragmentUri(ControllerReference $reference, Request $request, bool $absolute = false, bool $strict = true) { - if ($strict) { - $this->checkNonScalar($reference->attributes); - } - - // We need to forward the current _format and _locale values as we don't have - // a proper routing pattern to do the job for us. - // This makes things inconsistent if you switch from rendering a controller - // to rendering a route if the route pattern does not contain the special - // _format and _locale placeholders. - if (!isset($reference->attributes['_format'])) { - $reference->attributes['_format'] = $request->getRequestFormat(); - } - if (!isset($reference->attributes['_locale'])) { - $reference->attributes['_locale'] = $request->getLocale(); - } - - $reference->attributes['_controller'] = $reference->controller; - - $reference->query['_path'] = http_build_query($reference->attributes, '', '&'); - - $path = $this->fragmentPath.'?'.http_build_query($reference->query, '', '&'); - - if ($absolute) { - return $request->getUriForPath($path); - } - - return $request->getBaseUrl().$path; - } - - private function checkNonScalar(array $values) - { - foreach ($values as $key => $value) { - if (\is_array($value)) { - $this->checkNonScalar($value); - } elseif (!is_scalar($value) && null !== $value) { - throw new \LogicException(sprintf('Controller attributes cannot contain non-scalar/non-null values (value for key "%s" is not a scalar or null).', $key)); - } - } + return (new FragmentUriGenerator($this->fragmentPath))->generate($reference, $request, $absolute, $strict, false); } }