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,73 @@ 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 = [];
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+ }
7987
80- $ dehydratedProps = $ this ->liveComponentHydrator ->dehydrate (
81- $ mounted ->getComponent (),
82- $ mounted ->getAttributes (),
83- $ metadata
84- );
88+ return [$ pathProps , $ queryProps ];
89+ }
8590
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 ($ k === '_route ' || $ k === '_controller ' ) {
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+ }
87121
88- $ urlLiveProps = [
89- 'path ' => [],
90- 'query ' => [],
91- ];
122+ if ([] !== $ queryProps ) {
123+ $ previousQueryString = [];
92124
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 );
96127 }
128+
129+ $ newQueryString = http_build_query ([...$ previousQueryString , ...$ queryProps ]);
97130 }
98131
99- return $ urlLiveProps ;
132+ return $ newUrl .( $ newQueryString ? ' ? ' . $ newQueryString : '' ) ;
100133 }
101134}
0 commit comments