Skip to content

Commit ebc9be1

Browse files
committed
feature #40575 [FrameworkBundle][HttpKernel][TwigBridge] Add an helper to generate fragments URL (dunglas)
This PR was squashed before being merged into the 5.3-dev branch. Discussion ---------- [FrameworkBundle][HttpKernel][TwigBridge] Add an helper to generate fragments URL | Q | A | ------------- | --- | Branch? | 5.x | Bug fix? | no | New feature? | yes <!-- please update src/**/CHANGELOG.md files --> | Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files --> | Tickets | Fix n/a | License | MIT | Doc PR | todo This PR adds a new helper to generate raw fragment URL. Fragments will be useful to generate lazy frames with Symfony UX Turbo (symfony/ux#64). This will also be convenient when using hinclude, ESI etc in case you want full control over the generated HTML. This is also more in sync with the new best practices we apply in the form component (generate the HTML by yourself instead of using Twig helpers hiding the HTML elements). Example: ```html <turbo-frame id="set_aside_tray" src="{{ fragment_uri(controller('Symfony\Bundle\FrameworkBundle\Controller', {template: "foo.html.twig"})) }}"> <img src="/icons/spinner.gif"> </turbo-frame> ``` Commits ------- 5d29d76612 [FrameworkBundle][HttpKernel][TwigBridge] Add an helper to generate fragments URL
2 parents 9470371 + 0425a7e commit ebc9be1

6 files changed

+135
-53
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ CHANGELOG
1212
* Deprecate `HttpKernelInterface::MASTER_REQUEST` and add `HttpKernelInterface::MAIN_REQUEST` as replacement
1313
* Deprecate `KernelEvent::isMasterRequest()` and add `isMainRequest()` as replacement
1414
* Add `#[AsController]` attribute for declaring standalone controllers on PHP 8
15+
* Add `FragmentUriGeneratorInterface` and `FragmentUriGenerator` to generate the URI of a fragment
1516

1617
5.2.0
1718
-----

Fragment/AbstractSurrogateFragmentRenderer.php

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,7 @@ public function render($uri, Request $request, array $options = [])
8383

8484
private function generateSignedFragmentUri(ControllerReference $uri, Request $request): string
8585
{
86-
if (null === $this->signer) {
87-
throw new \LogicException('You must use a URI when using the ESI rendering strategy or set a URL signer.');
88-
}
89-
90-
// we need to sign the absolute URI, but want to return the path only.
91-
$fragmentUri = $this->signer->sign($this->generateFragmentUri($uri, $request, true));
92-
93-
return substr($fragmentUri, \strlen($request->getSchemeAndHttpHost()));
86+
return (new FragmentUriGenerator($this->fragmentPath, $this->signer))->generate($uri, $request);
9487
}
9588

9689
private function containsNonScalars(array $values): bool

Fragment/FragmentUriGenerator.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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\Component\HttpKernel\Fragment;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpFoundation\RequestStack;
16+
use Symfony\Component\HttpKernel\Controller\ControllerReference;
17+
use Symfony\Component\HttpKernel\UriSigner;
18+
19+
/**
20+
* Generates a fragment URI.
21+
*
22+
* @author Kévin Dunglas <[email protected]>
23+
* @author Fabien Potencier <[email protected]>
24+
*/
25+
final class FragmentUriGenerator implements FragmentUriGeneratorInterface
26+
{
27+
private $fragmentPath;
28+
private $signer;
29+
private $requestStack;
30+
31+
public function __construct(string $fragmentPath, UriSigner $signer = null, RequestStack $requestStack = null)
32+
{
33+
$this->fragmentPath = $fragmentPath;
34+
$this->signer = $signer;
35+
$this->requestStack = $requestStack;
36+
}
37+
38+
/**
39+
* {@inheritDoc}
40+
*/
41+
public function generate(ControllerReference $controller, Request $request = null, bool $absolute = false, bool $strict = true, bool $sign = true): string
42+
{
43+
if (null === $request && (null === $this->requestStack || null === $request = $this->requestStack->getCurrentRequest())) {
44+
throw new \LogicException('Generating a fragment URL can only be done when handling a Request.');
45+
}
46+
47+
if ($sign && null === $this->signer) {
48+
throw new \LogicException('You must use a URI when using the ESI rendering strategy or set a URL signer.');
49+
}
50+
51+
if ($strict) {
52+
$this->checkNonScalar($controller->attributes);
53+
}
54+
55+
// We need to forward the current _format and _locale values as we don't have
56+
// a proper routing pattern to do the job for us.
57+
// This makes things inconsistent if you switch from rendering a controller
58+
// to rendering a route if the route pattern does not contain the special
59+
// _format and _locale placeholders.
60+
if (!isset($controller->attributes['_format'])) {
61+
$controller->attributes['_format'] = $request->getRequestFormat();
62+
}
63+
if (!isset($controller->attributes['_locale'])) {
64+
$controller->attributes['_locale'] = $request->getLocale();
65+
}
66+
67+
$controller->attributes['_controller'] = $controller->controller;
68+
$controller->query['_path'] = http_build_query($controller->attributes, '', '&');
69+
$path = $this->fragmentPath.'?'.http_build_query($controller->query, '', '&');
70+
71+
// we need to sign the absolute URI, but want to return the path only.
72+
$fragmentUri = $sign || $absolute ? $request->getUriForPath($path) : $request->getBaseUrl().$path;
73+
74+
if (!$sign) {
75+
return $fragmentUri;
76+
}
77+
78+
$fragmentUri = $this->signer->sign($fragmentUri);
79+
80+
return $absolute ? $fragmentUri : substr($fragmentUri, \strlen($request->getSchemeAndHttpHost()));
81+
}
82+
83+
private function checkNonScalar(array $values): void
84+
{
85+
foreach ($values as $key => $value) {
86+
if (\is_array($value)) {
87+
$this->checkNonScalar($value);
88+
} elseif (!is_scalar($value) && null !== $value) {
89+
throw new \LogicException(sprintf('Controller attributes cannot contain non-scalar/non-null values (value for key "%s" is not a scalar or null).', $key));
90+
}
91+
}
92+
}
93+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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\Component\HttpKernel\Fragment;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpKernel\Controller\ControllerReference;
16+
17+
/**
18+
* Interface implemented by rendering strategies able to generate an URL for a fragment.
19+
*
20+
* @author Kévin Dunglas <[email protected]>
21+
*/
22+
interface FragmentUriGeneratorInterface
23+
{
24+
/**
25+
* Generates a fragment URI for a given controller.
26+
*
27+
* @param bool $absolute Whether to generate an absolute URL or not
28+
* @param bool $strict Whether to allow non-scalar attributes or not
29+
* @param bool $sign Whether to sign the URL or not
30+
*
31+
* @return string A fragment URI
32+
*/
33+
public function generate(ControllerReference $controller, Request $request = null, bool $absolute = false, bool $strict = true, bool $sign = true): string;
34+
}

Fragment/HIncludeFragmentRenderer.php

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,7 @@ public function hasTemplating()
6262
public function render($uri, Request $request, array $options = [])
6363
{
6464
if ($uri instanceof ControllerReference) {
65-
if (null === $this->signer) {
66-
throw new \LogicException('You must use a proper URI when using the Hinclude rendering strategy or set a URL signer.');
67-
}
68-
69-
// we need to sign the absolute URI, but want to return the path only.
70-
$uri = substr($this->signer->sign($this->generateFragmentUri($uri, $request, true)), \strlen($request->getSchemeAndHttpHost()));
65+
$uri = (new FragmentUriGenerator($this->fragmentPath, $this->signer))->generate($uri, $request);
7166
}
7267

7368
// We need to replace ampersands in the URI with the encoded form in order to return valid html/xml content.

Fragment/RoutableFragmentRenderer.php

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
*/
2323
abstract class RoutableFragmentRenderer implements FragmentRendererInterface
2424
{
25-
private $fragmentPath = '/_fragment';
25+
/**
26+
* @internal
27+
*/
28+
protected $fragmentPath = '/_fragment';
2629

2730
/**
2831
* Sets the fragment path that triggers the fragment listener.
@@ -44,43 +47,6 @@ public function setFragmentPath(string $path)
4447
*/
4548
protected function generateFragmentUri(ControllerReference $reference, Request $request, bool $absolute = false, bool $strict = true)
4649
{
47-
if ($strict) {
48-
$this->checkNonScalar($reference->attributes);
49-
}
50-
51-
// We need to forward the current _format and _locale values as we don't have
52-
// a proper routing pattern to do the job for us.
53-
// This makes things inconsistent if you switch from rendering a controller
54-
// to rendering a route if the route pattern does not contain the special
55-
// _format and _locale placeholders.
56-
if (!isset($reference->attributes['_format'])) {
57-
$reference->attributes['_format'] = $request->getRequestFormat();
58-
}
59-
if (!isset($reference->attributes['_locale'])) {
60-
$reference->attributes['_locale'] = $request->getLocale();
61-
}
62-
63-
$reference->attributes['_controller'] = $reference->controller;
64-
65-
$reference->query['_path'] = http_build_query($reference->attributes, '', '&');
66-
67-
$path = $this->fragmentPath.'?'.http_build_query($reference->query, '', '&');
68-
69-
if ($absolute) {
70-
return $request->getUriForPath($path);
71-
}
72-
73-
return $request->getBaseUrl().$path;
74-
}
75-
76-
private function checkNonScalar(array $values)
77-
{
78-
foreach ($values as $key => $value) {
79-
if (\is_array($value)) {
80-
$this->checkNonScalar($value);
81-
} elseif (!is_scalar($value) && null !== $value) {
82-
throw new \LogicException(sprintf('Controller attributes cannot contain non-scalar/non-null values (value for key "%s" is not a scalar or null).', $key));
83-
}
84-
}
50+
return (new FragmentUriGenerator($this->fragmentPath))->generate($reference, $request, $absolute, $strict, false);
8551
}
8652
}

0 commit comments

Comments
 (0)