-
-
Notifications
You must be signed in to change notification settings - Fork 356
[LiveComponent] Add modifier option to LiveProp #1507
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[LiveComponent] Add modifier option to LiveProp #1507
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, just let me summarize the approch here:
When a method have a modifier attribute, we execute the modifier function, get the modified value, we create a clone of the PropMetada with the new value. All of that happen during the hydratation.
What about the dehydratation?
uasort($allMetadata, static function (LivePropMetadata $a, LivePropMetadata $b) { | ||
if ($a->hasModifier() && !$b->hasModifier()) { | ||
return 1; | ||
} elseif (!$a->hasModifier() && $b->hasModifier()) { | ||
return -1; | ||
} else { | ||
return 0; | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to put the method with a modifier in priority of others?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Metadata with a modifier are considered "greater" than others, and the array is sorted in asc direction. I'd say this code prioritize metadata without modifier?
I actually had a need recently to only enable live props that update the URL under certain conditions. I believe this PR would allow that, correct? |
Yes totally!
Modifiers are not only used on hydration. Actually this is used a bit like |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very tricky PR - I'm hoping we can make this work - excellent job 🤞
src/LiveComponent/doc/index.rst
Outdated
.. caution:: | ||
|
||
You must not rely on props that also use a modifier in other modifiers methods, as their modifiers will not | ||
necessarily be applied in the correct order when hydrating components. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You must not rely on properties that also use a modifier in other modifiers methods. This is because
those properties may not have been hydrated by this point.
@@ -66,6 +66,7 @@ public function dehydrate(object $component, ComponentAttributes $attributes, Li | |||
|
|||
$dehydratedProps = new DehydratedProps(); | |||
foreach ($componentMetadata->getAllLivePropsMetadata() as $propMetadata) { | |||
$propMetadata = $propMetadata->withModifier($component); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the riskiest part of this PR. Currently, if you want the LivePropMetadata[]
for a component, there is one source of truth: getAllLivePropsMetadata()
. But now, someone needs to know to loop over these LivePropMetadata
and call withModifier($component)
to get the final version. That bothers me :/. I understand the "chicken and egg" problem, though: we need to hydrate the component (or as much of it as possible) before we can execute the modifier callbacks. I wish there was a way around that, but I can't think of anything more clever than your sorting algo.
However, to minimize the problem I mentioned above, would it be possible to add an object $component
argument to getAllLivePropsMetadata()
and make it return an iterable? Internally, it would do the sorting, then, one-by-one, call ->withModifier()
and yield the final LiveProp
. Would that work? Then everywhere in the code would call $componentMetadata->getAllLivePropsMetadata($component)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just tested this, and it works perfectly!
But I suggest that we sort these LivePropMetadata
in the LiveComponentMetadata
constructor, so they are sorted once. As the modifier
option is not editable, this should be fine, WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, I think that's ok 👍
20ead2c
to
3ef4ace
Compare
Modifiers are now applied each time I also had to move the listener for query string initialization to Some tests failed after rebasing, but I don't think this is related to this PR. |
if ($livePropMetadata->queryStringMapping()) { | ||
$frontendName = $livePropMetadata->calculateFieldName($mounted, $livePropMetadata->getName()); | ||
$frontendName = $livePropMetadata->calculateFieldName($mounted->getComponent(), $livePropMetadata->getName()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused by this change. Rather, it looks right now, but the old code looks totally wrong. Was this an unrelated bug that snuck in? And is it not tested?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was confused by the (my) old code too. It's a bug that wasn't covered because I used a string field name in tests, and the component object is only used when the field name is computed by a method... Not directly related to this PR though, but I thought fixing the typo was worth it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added another prop in test component to test with the method for field name
It sounds nice... but I don't know if it will make enough difference. Most components will not have a modifier, so the extra work will be a |
Yup, that's probably too much for the moment. I was wondering about modifiers that could use services, maybe heavy, to get configuration etc. But that could be cached in the component class directly. In the end, no need to generalize it 😄 |
11b1ebc
to
3bcc5c2
Compare
Thanks Nicolas for your hard and careful work on this - well done! |
Hi!
Following discussions in #1396, here is a proposal to allow a
LiveProp
to be modified at runtime.The feature adds a
modifier
option toLiveProp
, so users can use a custom method to modify theLiveProp
's optionsdepending on the context.
This context could be a service, or even another prop within the component.
Usage
Here is the example given in the doc:
Changes overview
First, we have to make the
LiveProp
attribute editable in some way. I opted out for immutable methods, so caching is preserved when applicable.Then add a
LivePropMetadata::withModifier(object $component)
method that returns a new instance modified by the user-defined method. This is called in a few places, similarly toLiveComponentMetadata::calculateFieldName()
.The tricky part is component hydration. If we use props in modifiers to change other
LiveProp
options, they have to be hydrated before the modifier is applied. This is only useful for options related to hydration, but it concerns most of them... The trick is to sort metadata, so modified ones are hydrated at the end.