1414use Symfony \Component \EventDispatcher \EventSubscriberInterface ;
1515use Symfony \Component \HttpKernel \Event \ResponseEvent ;
1616use Symfony \Component \HttpKernel \KernelEvents ;
17+ use Symfony \Component \Routing \Exception \MethodNotAllowedException ;
18+ use Symfony \Component \Routing \Exception \NoConfigurationException ;
19+ use Symfony \Component \Routing \Exception \ResourceNotFoundException ;
20+ use Symfony \Component \Routing \Exception \RouteNotFoundException ;
21+ use Symfony \Component \Routing \RouterInterface ;
1722use Symfony \UX \LiveComponent \LiveComponentHydrator ;
1823use Symfony \UX \LiveComponent \Metadata \LiveComponentMetadataFactory ;
19- use Symfony \UX \LiveComponent \Util \UrlFactory ;
2024use Symfony \UX \TwigComponent \MountedComponent ;
2125
2226/**
@@ -29,35 +33,28 @@ class LiveUrlSubscriber implements EventSubscriberInterface
2933 public function __construct (
3034 private LiveComponentMetadataFactory $ metadataFactory ,
3135 private LiveComponentHydrator $ liveComponentHydrator ,
32- private UrlFactory $ urlFactory ,
36+ private RouterInterface $ router ,
3337 ) {
3438 }
3539
3640 public function onKernelResponse (ResponseEvent $ event ): void
3741 {
38- if (!$ event ->isMainRequest ()) {
39- return ;
40- }
41-
4242 $ request = $ event ->getRequest ();
43- if (!$ request ->attributes ->has ('_live_component ' )) {
43+ if (!$ event ->isMainRequest ()
44+ || !$ event ->getResponse ()->isSuccessful ()
45+ || !$ request ->attributes ->has ('_live_component ' )
46+ || !$ request ->attributes ->has ('_mounted_component ' )
47+ || !($ previousLiveUrl = $ request ->headers ->get (self ::URL_HEADER ))
48+ ) {
4449 return ;
4550 }
4651
47- if (!$ request ->attributes ->has ('_mounted_component ' )) {
48- return ;
49- }
52+ /** @var MountedComponent $mounted */
53+ $ mounted = $ request ->attributes ->get ('_mounted_component ' );
5054
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- }
55+ [$ pathProps , $ queryProps ] = $ this ->extractUrlLiveProps ($ mounted );
5756
58- if ($ newLiveUrl ) {
59- $ event ->getResponse ()->headers ->set (self ::URL_HEADER , $ newLiveUrl );
60- }
57+ $ event ->getResponse ()->headers ->set (self ::URL_HEADER , $ this ->generateNewLiveUrl ($ previousLiveUrl , $ pathProps , $ queryProps ));
6158 }
6259
6360 public static function getSubscribedEvents (): array
@@ -68,34 +65,67 @@ public static function getSubscribedEvents(): array
6865 }
6966
7067 /**
71- * @return array{
72- * path: array<string, mixed>,
73- * query: array<string, mixed>
74- * }
68+ * @return array{ array<string, mixed>, array<string, mixed> }
7569 */
76- private function getLiveProps (MountedComponent $ mounted ): array
70+ private function extractUrlLiveProps (MountedComponent $ mounted ): array
7771 {
78- $ metadata = $ this -> metadataFactory -> getMetadata ( $ mounted -> getName ()) ;
72+ $ pathProps = $ queryProps = [] ;
7973
80- $ dehydratedProps = $ this ->liveComponentHydrator ->dehydrate (
81- $ mounted ->getComponent (),
82- $ mounted ->getAttributes (),
83- $ metadata
84- );
74+ $ mountedMetadata = $ this ->metadataFactory ->getMetadata ($ mounted ->getName ());
8575
86- $ values = $ dehydratedProps ->getProps ();
76+ $ dehydratedProps = $ this ->liveComponentHydrator ->dehydrate ($ mounted ->getComponent (), $ mounted ->getAttributes (), $ mountedMetadata );
77+ $ props = $ dehydratedProps ->getProps ();
8778
88- $ urlLiveProps = [
89- 'path ' => [],
90- 'query ' => [],
91- ];
79+ foreach ($ mountedMetadata ->getAllUrlMappings ($ mounted ->getComponent ()) as $ name => $ urlMapping ) {
80+ if (array_key_exists ($ name , $ props )) {
81+ if ($ urlMapping ->mapPath ) {
82+ $ pathProps [$ urlMapping ->as ?? $ name ] = $ props [$ name ];
83+ } else {
84+ $ queryProps [$ urlMapping ->as ?? $ name ] = $ props [$ name ];
85+ }
86+ }
87+ }
88+
89+ return [$ pathProps , $ queryProps ];
90+ }
9291
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 ];
92+ private function generateNewLiveUrl (string $ previousUrl , array $ pathProps , array $ queryProps ): string
93+ {
94+ $ previousUrlParsed = parse_url ($ previousUrl );
95+ $ newUrl = $ previousUrlParsed ['path ' ];
96+ $ newQueryString = $ previousUrlParsed ['query ' ] ?? '' ;
97+
98+ if ([] !== $ pathProps ) {
99+ $ context = $ this ->router ->getContext ();
100+ try {
101+ // Create a temporary RouterContext pointing to the current controller
102+ $ tmpContext = clone $ context ;
103+ $ tmpContext ->setMethod ('GET ' );
104+ $ this ->router ->setContext ($ tmpContext );
105+
106+ $ matched = $ this ->router ->match ($ previousUrlParsed ['path ' ]);
107+
108+ $ route = $ matched ['_route ' ];
109+ unset($ matched ['_route ' ]);
110+
111+ $ newUrl = $ this ->router ->generate ($ route , $ pathProps + $ matched );
112+ } catch (ResourceNotFoundException ) {
113+ // re-use the previous URL path
114+ } finally {
115+ $ this ->router ->setContext ($ context );
96116 }
97117 }
98118
99- return $ urlLiveProps ;
119+ if ([] !== $ queryProps ) {
120+ $ previousQueryString = [];
121+
122+ if (isset ($ previousUrlParsed ['query ' ])) {
123+ parse_str ($ previousUrlParsed ['query ' ], $ previousQueryString );
124+ }
125+
126+ $ newQueryString = http_build_query ([...$ previousQueryString , ...$ queryProps ]);
127+ }
128+
129+ return $ newUrl . ($ newQueryString ? '? ' . $ newQueryString : '' );
100130 }
101131}
0 commit comments