14
14
use Symfony \Component \EventDispatcher \EventSubscriberInterface ;
15
15
use Symfony \Component \HttpKernel \Event \ResponseEvent ;
16
16
use Symfony \Component \HttpKernel \KernelEvents ;
17
+ use Symfony \Component \Routing \Exception \ResourceNotFoundException ;
18
+ use Symfony \Component \Routing \RouterInterface ;
17
19
use Symfony \UX \LiveComponent \LiveComponentHydrator ;
18
20
use Symfony \UX \LiveComponent \Metadata \LiveComponentMetadataFactory ;
19
- use Symfony \UX \LiveComponent \Util \UrlFactory ;
20
21
use Symfony \UX \TwigComponent \MountedComponent ;
21
22
22
23
/**
@@ -29,35 +30,28 @@ class LiveUrlSubscriber implements EventSubscriberInterface
29
30
public function __construct (
30
31
private LiveComponentMetadataFactory $ metadataFactory ,
31
32
private LiveComponentHydrator $ liveComponentHydrator ,
32
- private UrlFactory $ urlFactory ,
33
+ private RouterInterface $ router ,
33
34
) {
34
35
}
35
36
36
37
public function onKernelResponse (ResponseEvent $ event ): void
37
38
{
38
- if (!$ event ->isMainRequest ()) {
39
- return ;
40
- }
41
-
42
39
$ request = $ event ->getRequest ();
43
- if (!$ request ->attributes ->has ('_live_component ' )) {
40
+ if (!$ event ->isMainRequest ()
41
+ || !$ event ->getResponse ()->isSuccessful ()
42
+ || !$ request ->attributes ->has ('_live_component ' )
43
+ || !$ request ->attributes ->has ('_mounted_component ' )
44
+ || !($ previousLiveUrl = $ request ->headers ->get (self ::URL_HEADER ))
45
+ ) {
44
46
return ;
45
47
}
46
48
47
- if (!$ request ->attributes ->has ('_mounted_component ' )) {
48
- return ;
49
- }
49
+ /** @var MountedComponent $mounted */
50
+ $ mounted = $ request ->attributes ->get ('_mounted_component ' );
50
51
51
- $ newLiveUrl = null ;
52
- if ($ previousLiveUrl = $ request ->headers ->get (self ::URL_HEADER )) {
53
- $ mounted = $ request ->attributes ->get ('_mounted_component ' );
54
- $ liveProps = $ this ->getLiveProps ($ mounted );
55
- $ newLiveUrl = $ this ->urlFactory ->createFromPreviousAndProps ($ previousLiveUrl , $ liveProps ['path ' ], $ liveProps ['query ' ]);
56
- }
52
+ [$ pathProps , $ queryProps ] = $ this ->extractUrlLiveProps ($ mounted );
57
53
58
- if ($ newLiveUrl ) {
59
- $ event ->getResponse ()->headers ->set (self ::URL_HEADER , $ newLiveUrl );
60
- }
54
+ $ event ->getResponse ()->headers ->set (self ::URL_HEADER , $ this ->generateNewLiveUrl ($ previousLiveUrl , $ pathProps , $ queryProps ));
61
55
}
62
56
63
57
public static function getSubscribedEvents (): array
@@ -68,34 +62,73 @@ public static function getSubscribedEvents(): array
68
62
}
69
63
70
64
/**
71
- * @return array{
72
- * path: array<string, mixed>,
73
- * query: array<string, mixed>
74
- * }
65
+ * @return array{ array<string, mixed>, array<string, mixed> }
75
66
*/
76
- private function getLiveProps (MountedComponent $ mounted ): array
67
+ private function extractUrlLiveProps (MountedComponent $ mounted ): array
77
68
{
78
- $ metadata = $ this ->metadataFactory ->getMetadata ($ mounted ->getName ());
69
+ $ pathProps = $ queryProps = [];
70
+
71
+ $ mountedMetadata = $ this ->metadataFactory ->getMetadata ($ mounted ->getName ());
72
+
73
+ if ([] !== $ urlMappings = $ mountedMetadata ->getAllUrlMappings ($ mounted ->getComponent ())) {
74
+ $ dehydratedProps = $ this ->liveComponentHydrator ->dehydrate ($ mounted ->getComponent (), $ mounted ->getAttributes (), $ mountedMetadata );
75
+ $ props = $ dehydratedProps ->getProps ();
76
+
77
+ foreach ($ urlMappings as $ name => $ urlMapping ) {
78
+ if (\array_key_exists ($ name , $ props )) {
79
+ if ($ urlMapping ->mapPath ) {
80
+ $ pathProps [$ urlMapping ->as ?? $ name ] = $ props [$ name ];
81
+ } else {
82
+ $ queryProps [$ urlMapping ->as ?? $ name ] = $ props [$ name ];
83
+ }
84
+ }
85
+ }
86
+ }
79
87
80
- $ dehydratedProps = $ this ->liveComponentHydrator ->dehydrate (
81
- $ mounted ->getComponent (),
82
- $ mounted ->getAttributes (),
83
- $ metadata
84
- );
88
+ return [$ pathProps , $ queryProps ];
89
+ }
85
90
86
- $ values = $ dehydratedProps ->getProps ();
91
+ private function generateNewLiveUrl (string $ previousUrl , array $ pathProps , array $ queryProps ): string
92
+ {
93
+ $ previousUrlParsed = parse_url ($ previousUrl );
94
+ $ newUrl = $ previousUrlParsed ['path ' ];
95
+ $ newQueryString = $ previousUrlParsed ['query ' ] ?? '' ;
96
+
97
+ if ([] !== $ pathProps ) {
98
+ $ context = $ this ->router ->getContext ();
99
+ try {
100
+ // Re-create a context for the URL rendering the current LiveComponent
101
+ $ tmpContext = clone $ context ;
102
+ $ tmpContext ->setMethod ('GET ' );
103
+ $ this ->router ->setContext ($ tmpContext );
104
+
105
+ $ routeMatched = $ this ->router ->match ($ previousUrlParsed ['path ' ]);
106
+ $ routeParams = [];
107
+ foreach ($ routeMatched as $ k => $ v ) {
108
+ if ('_route ' === $ k || '_controller ' === $ k ) {
109
+ continue ;
110
+ }
111
+ $ routeParams [$ k ] = \array_key_exists ($ k , $ pathProps ) ? $ pathProps [$ k ] : $ v ;
112
+ }
113
+
114
+ $ newUrl = $ this ->router ->generate ($ routeMatched ['_route ' ], $ routeParams );
115
+ } catch (ResourceNotFoundException ) {
116
+ // reuse the previous URL path
117
+ } finally {
118
+ $ this ->router ->setContext ($ context );
119
+ }
120
+ }
87
121
88
- $ urlLiveProps = [
89
- 'path ' => [],
90
- 'query ' => [],
91
- ];
122
+ if ([] !== $ queryProps ) {
123
+ $ previousQueryString = [];
92
124
93
- foreach ($ metadata ->getAllUrlMappings ($ mounted ->getComponent ()) as $ name => $ urlMapping ) {
94
- if (isset ($ values [$ name ]) && $ urlMapping ) {
95
- $ urlLiveProps [$ urlMapping ->mapPath ? 'path ' : 'query ' ][$ urlMapping ->as ?? $ name ] = $ values [$ name ];
125
+ if (isset ($ previousUrlParsed ['query ' ])) {
126
+ parse_str ($ previousUrlParsed ['query ' ], $ previousQueryString );
96
127
}
128
+
129
+ $ newQueryString = http_build_query ([...$ previousQueryString , ...$ queryProps ]);
97
130
}
98
131
99
- return $ urlLiveProps ;
132
+ return $ newUrl .( $ newQueryString ? ' ? ' . $ newQueryString : '' ) ;
100
133
}
101
134
}
0 commit comments