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,67 @@ 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 = [] ;
79
70
80
- $ dehydratedProps = $ this ->liveComponentHydrator ->dehydrate (
81
- $ mounted ->getComponent (),
82
- $ mounted ->getAttributes (),
83
- $ metadata
84
- );
71
+ $ mountedMetadata = $ this ->metadataFactory ->getMetadata ($ mounted ->getName ());
85
72
86
- $ values = $ dehydratedProps ->getProps ();
73
+ $ dehydratedProps = $ this ->liveComponentHydrator ->dehydrate ($ mounted ->getComponent (), $ mounted ->getAttributes (), $ mountedMetadata );
74
+ $ props = $ dehydratedProps ->getProps ();
87
75
88
- $ urlLiveProps = [
89
- 'path ' => [],
90
- 'query ' => [],
91
- ];
76
+ foreach ($ mountedMetadata ->getAllUrlMappings ($ mounted ->getComponent ()) as $ name => $ urlMapping ) {
77
+ if (\array_key_exists ($ name , $ props )) {
78
+ if ($ urlMapping ->mapPath ) {
79
+ $ pathProps [$ urlMapping ->as ?? $ name ] = $ props [$ name ];
80
+ } else {
81
+ $ queryProps [$ urlMapping ->as ?? $ name ] = $ props [$ name ];
82
+ }
83
+ }
84
+ }
85
+
86
+ return [$ pathProps , $ queryProps ];
87
+ }
92
88
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 ];
89
+ private function generateNewLiveUrl (string $ previousUrl , array $ pathProps , array $ queryProps ): string
90
+ {
91
+ $ previousUrlParsed = parse_url ($ previousUrl );
92
+ $ newUrl = $ previousUrlParsed ['path ' ];
93
+ $ newQueryString = $ previousUrlParsed ['query ' ] ?? '' ;
94
+
95
+ if ([] !== $ pathProps ) {
96
+ $ context = $ this ->router ->getContext ();
97
+ try {
98
+ // Create a temporary RouterContext pointing to the current controller
99
+ $ tmpContext = clone $ context ;
100
+ $ tmpContext ->setMethod ('GET ' );
101
+ $ this ->router ->setContext ($ tmpContext );
102
+
103
+ $ matched = $ this ->router ->match ($ previousUrlParsed ['path ' ]);
104
+
105
+ $ route = $ matched ['_route ' ];
106
+ unset($ matched ['_route ' ]);
107
+
108
+ $ newUrl = $ this ->router ->generate ($ route , $ pathProps + $ matched );
109
+ } catch (ResourceNotFoundException ) {
110
+ // reuse the previous URL path
111
+ } finally {
112
+ $ this ->router ->setContext ($ context );
96
113
}
97
114
}
98
115
99
- return $ urlLiveProps ;
116
+ if ([] !== $ queryProps ) {
117
+ $ previousQueryString = [];
118
+
119
+ if (isset ($ previousUrlParsed ['query ' ])) {
120
+ parse_str ($ previousUrlParsed ['query ' ], $ previousQueryString );
121
+ }
122
+
123
+ $ newQueryString = http_build_query ([...$ previousQueryString , ...$ queryProps ]);
124
+ }
125
+
126
+ return $ newUrl .($ newQueryString ? '? ' .$ newQueryString : '' );
100
127
}
101
128
}
0 commit comments