Skip to content

Commit 93c0a12

Browse files
committed
UrlFactory
1 parent 96a2947 commit 93c0a12

File tree

3 files changed

+91
-46
lines changed

3 files changed

+91
-46
lines changed

src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php

+5-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
use Symfony\UX\LiveComponent\Util\LiveComponentStack;
5353
use Symfony\UX\LiveComponent\Util\LiveControllerAttributesCreator;
5454
use Symfony\UX\LiveComponent\Util\TwigAttributeHelperFactory;
55+
use Symfony\UX\LiveComponent\Util\UrlFactory;
5556
use Symfony\UX\LiveComponent\Util\UrlPropsExtractor;
5657
use Symfony\UX\TwigComponent\ComponentFactory;
5758
use Symfony\UX\TwigComponent\ComponentRenderer;
@@ -138,8 +139,8 @@ function (ChildDefinition $definition, AsLiveComponent $attribute) {
138139

139140
$container->register('ux.live_component.live_url_subscriber', LiveUrlSubscriber::class)
140141
->setArguments([
141-
new Reference('router'),
142142
new Reference('ux.live_component.metadata_factory'),
143+
new Reference('ux.live_component.url_factory'),
143144
])
144145
->addTag('kernel.event_subscriber')
145146
;
@@ -209,6 +210,9 @@ function (ChildDefinition $definition, AsLiveComponent $attribute) {
209210
$container->register('ux.live_component.attribute_helper_factory', TwigAttributeHelperFactory::class)
210211
->setArguments([new Reference('twig')]);
211212

213+
$container->register('ux.live_component.url_factory', UrlFactory::class)
214+
->setArguments([new Reference('router')]);
215+
212216
$container->register('ux.live_component.live_controller_attributes_creator', LiveControllerAttributesCreator::class)
213217
->setArguments([
214218
new Reference('ux.live_component.metadata_factory'),

src/LiveComponent/src/EventListener/LiveUrlSubscriber.php

+9-45
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,34 @@
1717
use Symfony\Component\HttpKernel\KernelEvents;
1818
use Symfony\Component\Routing\RouterInterface;
1919
use Symfony\UX\LiveComponent\Metadata\LiveComponentMetadataFactory;
20+
use Symfony\UX\LiveComponent\Util\UrlFactory;
2021

2122
class LiveUrlSubscriber implements EventSubscriberInterface
2223
{
2324
private const URL_HEADER = 'X-Live-Url';
2425

2526
public function __construct(
26-
private readonly RouterInterface $router,
27-
private readonly LiveComponentMetadataFactory $metadataFactory,
27+
private LiveComponentMetadataFactory $metadataFactory,
28+
private UrlFactory $urlFactory
2829
) {
2930
}
3031

3132
public function onKernelResponse(ResponseEvent $event): void
3233
{
33-
if (!$this->isLiveComponentRequest($request = $event->getRequest())) {
34+
$request = $event->getRequest();
35+
if (!$request->attributes->has('_live_component')) {
3436
return;
3537
}
3638
if (!$event->isMainRequest()) {
3739
return;
3840
}
3941

4042
if ($previousLocation = $request->headers->get(self::URL_HEADER)) {
41-
$newUrl = $this->computeNewUrl(
43+
$liveProps = $this->getLivePropsToMap($request);
44+
$newUrl = $this->urlFactory->createFromPreviousAndProps(
4245
$previousLocation,
43-
$this->getLivePropsToMap($request)
46+
$liveProps['path'],
47+
$liveProps['query']
4448
);
4549
if ($newUrl) {
4650
$event->getResponse()->headers->set(
@@ -82,44 +86,4 @@ private function getLivePropsToMap(Request $request): array
8286

8387
return $urlLiveProps;
8488
}
85-
86-
private function computeNewUrl(string $previousUrl, array $livePropsToMap): string
87-
{
88-
$parsed = parse_url($previousUrl);
89-
90-
$url = $parsed['path'] ?? '';
91-
if (isset($parsed['query'])) {
92-
$url .= '?'.$parsed['query'];
93-
}
94-
parse_str($parsed['query'] ?? '', $previousQueryParams);
95-
96-
$newUrl = $this->router->generate(
97-
$this->router->match($url)['_route'],
98-
array_merge($previousQueryParams, $livePropsToMap['path'])
99-
);
100-
parse_str(parse_url($newUrl)['query'] ?? '', $queryParams);
101-
$queryString = http_build_query(array_merge($queryParams, $livePropsToMap['query']));
102-
103-
return preg_replace('/[?#].*/', '', $newUrl).
104-
('' !== $queryString ? '?' : '').
105-
$queryString;
106-
}
107-
108-
/**
109-
* copied from LiveComponentSubscriber.
110-
*/
111-
private function isLiveComponentRequest(Request $request): bool
112-
{
113-
if (!$request->attributes->has('_live_component')) {
114-
return false;
115-
}
116-
117-
// if ($this->testMode) {
118-
// return true;
119-
// }
120-
121-
// Except when testing, require the correct content-type in the Accept header.
122-
// This also acts as a CSRF protection since this can only be set in accordance with same-origin/CORS policies.
123-
return \in_array('application/vnd.live-component+html', $request->getAcceptableContentTypes(), true);
124-
}
12589
}
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symfony\UX\LiveComponent\Util;
6+
7+
use Symfony\Component\Routing\RouterInterface;
8+
9+
/**
10+
* @internal
11+
*/
12+
class UrlFactory
13+
{
14+
public function __construct(
15+
private RouterInterface $router,
16+
) {
17+
}
18+
19+
public function createFromPreviousAndProps(
20+
string $previousUrl,
21+
array $pathMappedProps,
22+
array $queryMappedProps
23+
): string {
24+
$parsed = parse_url($previousUrl);
25+
26+
//Make sure to handle only path and query
27+
$previousUrl = $parsed['path'] ?? '';
28+
if (isset($parsed['query'])) {
29+
$previousUrl .= '?'.$parsed['query'];
30+
}
31+
32+
$newUrl = $this->createPath($previousUrl, $pathMappedProps);
33+
34+
return $this->replaceQueryString(
35+
$newUrl,
36+
array_merge(
37+
$this->getPreviousQueryParameters($parsed['query'] ?? ''),
38+
$this->getRemnantProps($newUrl),
39+
$queryMappedProps,
40+
)
41+
);
42+
}
43+
44+
private function createPath(string $previousUrl, array $props): string
45+
{
46+
return $this->router->generate(
47+
$this->router->match($previousUrl)['_route'],
48+
$props
49+
);
50+
}
51+
52+
53+
private function replaceQueryString($url, array $props): string
54+
{
55+
$queryString = http_build_query($props);
56+
57+
return preg_replace('/[?#].*/', '', $url).
58+
('' !== $queryString ? '?' : '').
59+
$queryString;
60+
}
61+
62+
// Keep the query parameters of the previous request
63+
private function getPreviousQueryParameters(string $query): array
64+
{
65+
parse_str($query, $previousQueryParams);
66+
67+
return $previousQueryParams;
68+
}
69+
70+
// Symfony router will set props in query if they do not match route parameter
71+
private function getRemnantProps(string $newUrl): array
72+
{
73+
parse_str(parse_url($newUrl)['query'] ?? '', $remnantQueryParams);
74+
75+
return $remnantQueryParams;
76+
}
77+
}

0 commit comments

Comments
 (0)