Skip to content

[LiveComponent] Add the possibility to map LiveProp as a path parameter - #2673

Merged
Kocal merged 1 commit into
symfony:2.xfrom
mbuliard:live-url
Jul 24, 2025
Merged

Kocal merged 1 commit into
symfony:2.xfrom
mbuliard:live-url

Conversation

@mbuliard

@mbuliard mbuliard commented Apr 1, 2025 •

Copy link
Copy Markdown
Contributor
Q A
Bug fix? no
New feature? yes
Docs? yes
Issues No
License MIT

The purpose of this MR is to give the responsability of changing the URL to the backend, allowing to use Symfony Router and to have LiveProps in the path, not only in query parameters :
http://example.com/content?id=123 vs
http://example.com/content/123

To set the LiveProp in the path, a new option, mapPath has been added to the UrlMapping option of the LiveProp :

#[LiveProp(writable: true, url: new UrlMapping(mapPath: true))]
public int $id;

WORKFLOW

  1. When sending a request to the backend, the frontend add a new header X-Live-Url containing the current path and query parameters.
  2. On KernelResponse event, the new path and query string is calculated from the header received and the new props and put in the response header X-Live-Url.
  3. When the frontend receives the response, the current path and query are placed by those received, via history.replaceState.

BACKEND CHANGES

  • New UrlFactory service to generate URL from the previous one, the path-mapped props and the query-mapped props. The url is first generated by the Symfony Router, using the previous one and the path-mapped props. Then the query-mapped props and the previous query parameters are added.
  • QueryStringPropsExtractor is renamed to RequestPropsExtractor and now extract props from the request attributes and query parameters.
  • UrlMapping now has a new option mapPath, boolean, false by default.
  • LiveComponentMetadata has new method getAllUrlMappings returning urlMappings of all LiveProps.
  • QueryStringInitializeSubscriber is renamed to RequestInitializeSubscriber
  • new LiveUrlSubscriber, listening to KernelResponse, and setting the X-Live-Url of the response with the new URL, generated by the UrlFactory. To generate it, the previous location is extracted from the request and the props are extracted from metadata, hydrated with the values of _live_request_data and sorted between path-mapped and query-mapped.
  • LiveComponentSubscriber now add responseProps data to the _live_request_data attribute, containing the mounted component data when the action is not the default one. This change is made to take server-side changes into account.
  • LiveComponentMetadata has new method getAllUrlMappings returning urlMappings of all LiveProps.

FRONTEND CHANGES

  • Backend/RequestBuilder now add the current pathname and search as X-Live-Url header in the request.
  • Backend/BackendResponse has new property liveUrl, populated from the HTTP response X-Live-Url header.
  • Component/index.ts : performRequest now check for X-Live-Url header in response and, when found, do history.replace with the new url and the current hash and origin.
  • url_utils is removed.
  • Component/plugins/QueryStringPlugin is removed.

TODO

Review :-)

@carsonbot carsonbot added Feature New Feature Status: Needs Work Additional work is needed labels Apr 1, 2025
@github-actions

github-actions Bot commented Apr 1, 2025 •

Copy link
Copy Markdown
Contributor

📊 Packages dist files size difference

ℹ️ No difference in dist packagesFiles.

@mbuliard
mbuliard marked this pull request as draft April 1, 2025 19:23
@smnandre

smnandre commented Apr 4, 2025

Copy link
Copy Markdown
Member

(Had a quick chat with @mbuliard yesterday — he's making a few adjustments before opening the PR. Stay tuned! 😄 )

Comment thread src/LiveComponent/assets/src/Component/index.ts Outdated
@smnandre

smnandre commented May 4, 2025

Copy link
Copy Markdown
Member

Do you want/need any help here @mbuliard ?

@mbuliard

Copy link
Copy Markdown
Contributor Author

Do you want/need any help here @mbuliard ?

Thanks ! I needed free time, but the PR is almost done. Related tests are green, but I shall now add my own testing of the backend.

What took me some time was to the handling of custom actions, who may (and often do) update component props. I choose to store the updated component props in _live_request_data.responseProps, next to props and updated values.
I'm thinking to always populate these responseProps, even for default action and then simplifying my LiveUrlSubscriber.

@mbuliard
mbuliard marked this pull request as ready for review May 22, 2025 22:37
@mbuliard mbuliard changed the title [WIP] LiveUrl LiveProp in URL path May 22, 2025
@smnandre

Copy link
Copy Markdown
Member

Hey @squrious if you find a minute, i'd be happy to have your input (even a quick one) here :)

@carsonbot carsonbot added Status: Needs Review Needs to be reviewed and removed Status: Needs Work Additional work is needed labels May 30, 2025
@smnandre

smnandre commented Jun 9, 2025

Copy link
Copy Markdown
Member

To me it's 💯 👍 ..

@Kocal @kbond I'd like a second look on this!

@kbond kbond left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Couple questions in my review but also: Can/should you have multiple routes per component? Or should multiple LiveProp's with mapPath: true all be contained in a single route?

I think this should be clarified but also: it looks to me like this fully overrides the route for your component? If so, what about this concept being the feature?

Comment thread src/LiveComponent/doc/index.rst Outdated
Comment thread src/LiveComponent/doc/index.rst
@mbuliard

mbuliard commented Jun 10, 2025 •

Copy link
Copy Markdown
Contributor Author

@kbond
Thanks for your review !
Defining the Route in the Component would be an interesting idea but it would be far too big for this MR in my opinion.
For now, the mapPath option will only tell the LiveUrlSubscriber to try to set the LiveProp in the path instead of the query.

Can/should you have multiple routes per component? Or should multiple LiveProp's with mapPath: true all be contained in a single route?

The Route and the LiveProp are not programmatically linked to each other so there is no restriction : several LiveProps can be used for the same Route and each LiveProp can be used for several Routes.

Comment thread src/LiveComponent/src/EventListener/LiveUrlSubscriber.php Outdated
Comment thread src/LiveComponent/src/EventListener/LiveUrlSubscriber.php Outdated
Comment thread src/LiveComponent/src/EventListener/LiveUrlSubscriber.php Outdated
@kbond

kbond commented Jun 10, 2025

Copy link
Copy Markdown
Member

@mbuliard, sorry, I misunderstood the feature. It took me a bit to realize the docs showed the route/controller that renders the component initially.

@mbuliard

Copy link
Copy Markdown
Contributor Author

@mbuliard, sorry, I misunderstood the feature. It took me a bit to realize the docs showed the route/controller that renders the component initially.

My example was not clear enough, I added a line to clarify that it was a controller.

@smnandre
smnandre requested review from Kocal and kbond July 10, 2025 17:08
@smnandre

Copy link
Copy Markdown
Member

What do you think here @Kocal @kbond ? :)

Comment thread src/LiveComponent/CHANGELOG.md Outdated
Comment thread src/LiveComponent/assets/src/Backend/BackendResponse.ts Outdated
Comment thread src/LiveComponent/doc/index.rst Outdated
Comment thread src/LiveComponent/doc/index.rst Outdated
Comment thread src/LiveComponent/doc/index.rst Outdated
Comment thread src/LiveComponent/src/EventListener/LiveComponentSubscriber.php Outdated
Comment thread src/LiveComponent/src/Util/UrlFactory.php Outdated
Comment thread src/LiveComponent/src/Util/UrlFactory.php Outdated
Comment thread src/LiveComponent/tests/Unit/Util/UrlFactoryTest.php Outdated
Comment thread src/LiveComponent/tests/Unit/EventListener/LiveUrlSubscriberTest.php Outdated
@Kocal

Kocal commented Jul 10, 2025

Copy link
Copy Markdown
Member

Thanks for working on it! Just few nitpicking, the PR seems pretty mature right now

@mbuliard

Copy link
Copy Markdown
Contributor Author

Thanks for working on it! Just few nitpicking, the PR seems pretty mature right now

Thanks for your review ! I made the changes you suggested.

Comment thread src/LiveComponent/doc/index.rst Outdated
Comment thread src/LiveComponent/doc/index.rst Outdated
Comment thread src/LiveComponent/doc/index.rst Outdated
Comment thread src/LiveComponent/src/EventListener/LiveUrlSubscriber.php
Comment on lines +44 to +53
$newUrl = null;
if ($previousLocation = $request->headers->get(self::URL_HEADER)) {
$liveProps = $this->getLivePropsToMap($request);
$newUrl = $this->urlFactory->createFromPreviousAndProps($previousLocation, $liveProps['path'], $liveProps['query']);
}

if ($newUrl) {
$event->getResponse()->headers->set(self::URL_HEADER, $newUrl);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$newUrl = null;
if ($previousLocation = $request->headers->get(self::URL_HEADER)) {
$liveProps = $this->getLivePropsToMap($request);
$newUrl = $this->urlFactory->createFromPreviousAndProps($previousLocation, $liveProps['path'], $liveProps['query']);
}
if ($newUrl) {
$event->getResponse()->headers->set(self::URL_HEADER, $newUrl);
}
if ($previousLiveUrl = $request->headers->get(self::URL_HEADER)) {
$liveProps = $this->getLivePropsToMap($request);
$newLiveUrl = $this->urlFactory->createFromPreviousAndProps($previousLiveUrl, $liveProps['path'], $liveProps['query']);
$event->getResponse()->headers->set(self::URL_HEADER, $newUrl);
}

@mbuliard mbuliard Jul 16, 2025 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept two separated if to not set the header if the $newLiveUrl is null.
It can happen if :

  • the previous url cannot be parsed
  • the previous url does not match any known route
  • some mandatory parameter is missing to generate the url of the route

But maybe it would be better to throw exceptions ?
I was worried to break anything doing so

Comment thread src/LiveComponent/src/EventListener/LiveUrlSubscriber.php Outdated
Comment thread src/LiveComponent/src/EventListener/LiveUrlSubscriber.php Outdated
@Kocal Kocal added Status: Needs Work Additional work is needed and removed Status: Needs Review Needs to be reviewed labels Jul 14, 2025
@carsonbot carsonbot removed the Status: Needs Work Additional work is needed label Jul 16, 2025
@carsonbot carsonbot added the Status: Needs Review Needs to be reviewed label Jul 16, 2025
@smnandre

Copy link
Copy Markdown
Member

Can you rebase (a last time i hope) @mbuliard ?

Is that ok for you @Kocal ? Anything else to check ?

Comment thread src/LiveComponent/CHANGELOG.md Outdated
@carsonbot carsonbot added Status: Reviewed Has been reviewed by a maintainer and removed Status: Needs Review Needs to be reviewed labels Jul 23, 2025

@kbond kbond left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! The docs are much clearer.

Comment thread src/LiveComponent/doc/index.rst Outdated
@carsonbot carsonbot added Status: Needs Work Additional work is needed Status: Needs Review Needs to be reviewed and removed Status: Reviewed Has been reviewed by a maintainer Status: Needs Work Additional work is needed labels Jul 23, 2025
@mbuliard
mbuliard requested a review from kbond July 24, 2025 12:34
@carsonbot carsonbot added Status: Reviewed Has been reviewed by a maintainer and removed Status: Needs Review Needs to be reviewed labels Jul 24, 2025
@Kocal Kocal changed the title LiveProp in URL path [LiveComponent] Add the possibility to map LiveProp as a path parameter Jul 24, 2025
@Kocal

Kocal commented Jul 24, 2025

Copy link
Copy Markdown
Member

It took time, but here we go, this is in now. Thank you very much @mbuliard.

@Kocal
Kocal merged commit b51130b into symfony:2.x Jul 24, 2025
Kocal added a commit that referenced this pull request Aug 15, 2025
…ng Serializer (and Serializer attributes), and when using custom modifier (Kocal)

This PR was squashed before being merged into the 2.x branch.

Discussion
----------

[LiveComponent] Fix new URL calculation with `LiveProp` using Serializer (and Serializer attributes), and when using custom modifier

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| Docs?         | no <!-- required for new features -->
| Issues        | Fix #2971, #2991 <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead -->
| License       | MIT

<!--
Replace this notice by a description of your feature/bugfix.
This will help reviewers and should be a good start for the documentation.

Additionally (see https://symfony.com/releases):
 - Always add tests and ensure they pass.
 - For new features, provide some code snippets to help understand usage.
 - Features and deprecations must be submitted against branch main.
 - Update/add documentation as required (we can help!)
 - Changelog entry should follow https://symfony.com/doc/current/contributing/code/conventions.html#writing-a-changelog-entry
 - Never break backward compatibility (see https://symfony.com/bc).
-->

This PR fixes two bugs introduced #2673:
1. See #2971 (comment) for explanations
tl;dr: previously, query parameters were updated through `LiveComponentHydrator::dehydrate()` which use the Serializer (if `useSerializerForHydration: true`) and so also used Serializer attributes like `SerializedName`.
2. #2991, when having a `LiveProp(url: new UrlMapping(as: 'foo'))` with a custom modifier that returns a new `LiveProp` with a new `UrlMapping`, ex:
```php
    public function myModifier(LiveProp $liveProp): LiveProp
    {
        $urlMapping = $liveProp->url();
        if (!$urlMapping instanceof UrlMapping) {
            return $liveProp;
        }

        return $liveProp->withUrl(new UrlMapping(as: 'alias_' . $urlMapping->as));
    }
```
then the prop was persisted as `foo` and not `alias_foo` in the URL.

Commits
-------

19fe704 [LiveComponent] Fix new URL calculation when having `#[LiveProp]` with custom `modifier` which returns a new `LiveProp`
07e73bf [LiveComponent] Fix new URL calculation when having `#[LiveProp]` with `useSerializerForHydration: true` and `#[SerializedName]`
Kocal added a commit that referenced this pull request Aug 29, 2025
…` with custom `fieldName` (Kocal)

This PR was merged into the 2.x branch.

Discussion
----------

[LiveComponent] Fix new URL generation when using `LiveProp` with custom `fieldName`

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| Docs?         | no <!-- required for new features -->
| Issues        | Fix #... <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead -->
| License       | MIT

<!--
Replace this notice by a description of your feature/bugfix.
This will help reviewers and should be a good start for the documentation.

Additionally (see https://symfony.com/releases):
 - Always add tests and ensure they pass.
 - For new features, provide some code snippets to help understand usage.
 - Features and deprecations must be submitted against branch main.
 - Update/add documentation as required (we can help!)
 - Changelog entry should follow https://symfony.com/doc/current/contributing/code/conventions.html#writing-a-changelog-entry
 - Never break backward compatibility (see https://symfony.com/bc).
-->

Follows #2673

Related to https://symfony-devs.slack.com/archives/C01FN4EQNLX/p1756395137633869?thread_ts=1756320013.552729&cid=C01FN4EQNLX, I believe this is the ultimate last fix for the `X-Live-Url` generation based on `LiveProp` 😅

I confirm that in <2.28, the URL query params were using the `fieldName` value from `LiveProp` if available, but we missed it (I didn't even know about that, the feature is not documented).

Commits
-------

25c3e13 [LiveComponent] Fix new URL generation when using `LiveProp` with custom `fieldName`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature New Feature LiveComponent Status: Reviewed Has been reviewed by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants