1414use Symfony \Component \EventDispatcher \EventSubscriberInterface ;
1515use Symfony \Component \HttpKernel \Event \ResponseEvent ;
1616use Symfony \Component \HttpKernel \KernelEvents ;
17+ use Symfony \Component \Routing \Exception \ResourceNotFoundException ;
18+ use Symfony \Component \Routing \RouterInterface ;
1719use Symfony \UX \LiveComponent \LiveComponentHydrator ;
1820use Symfony \UX \LiveComponent \Metadata \LiveComponentMetadataFactory ;
19- use Symfony \UX \LiveComponent \Util \UrlFactory ;
2021use Symfony \UX \TwigComponent \MountedComponent ;
2122
2223/**
@@ -29,35 +30,28 @@ class LiveUrlSubscriber implements EventSubscriberInterface
2930 public function __construct (
3031 private LiveComponentMetadataFactory $ metadataFactory ,
3132 private LiveComponentHydrator $ liveComponentHydrator ,
32- private UrlFactory $ urlFactory ,
33+ private RouterInterface $ router ,
3334 ) {
3435 }
3536
3637 public function onKernelResponse (ResponseEvent $ event ): void
3738 {
38- if (!$ event ->isMainRequest ()) {
39- return ;
40- }
41-
4239 $ 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+ ) {
4446 return ;
4547 }
4648
47- if (!$ request ->attributes ->has ('_mounted_component ' )) {
48- return ;
49- }
49+ /** @var MountedComponent $mounted */
50+ $ mounted = $ request ->attributes ->get ('_mounted_component ' );
5051
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 );
5753
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 ));
6155 }
6256
6357 public static function getSubscribedEvents (): array
@@ -68,34 +62,67 @@ public static function getSubscribedEvents(): array
6862 }
6963
7064 /**
71- * @return array{
72- * path: array<string, mixed>,
73- * query: array<string, mixed>
74- * }
65+ * @return array{ array<string, mixed>, array<string, mixed> }
7566 */
76- private function getLiveProps (MountedComponent $ mounted ): array
67+ private function extractUrlLiveProps (MountedComponent $ mounted ): array
7768 {
78- $ metadata = $ this -> metadataFactory -> getMetadata ( $ mounted -> getName ()) ;
69+ $ pathProps = $ queryProps = [] ;
7970
80- $ dehydratedProps = $ this ->liveComponentHydrator ->dehydrate (
81- $ mounted ->getComponent (),
82- $ mounted ->getAttributes (),
83- $ metadata
84- );
71+ $ mountedMetadata = $ this ->metadataFactory ->getMetadata ($ mounted ->getName ());
8572
86- $ values = $ dehydratedProps ->getProps ();
73+ $ dehydratedProps = $ this ->liveComponentHydrator ->dehydrate ($ mounted ->getComponent (), $ mounted ->getAttributes (), $ mountedMetadata );
74+ $ props = $ dehydratedProps ->getProps ();
8775
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+ }
9288
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 );
96113 }
97114 }
98115
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 : '' );
100127 }
101128}
0 commit comments