ux
ux copied to clipboard
[LiveComponent] Passing an entire object (not an entity) to listeners
I've a Notifier
component which is listening to the "show" event and accept a Notification
object:
#[LiveListener('show')]
public function show(#[LiveArg] Notification $notification)
{
$this->notification = $notification;
}
$this->emit('show', ['notification' => new Notification()], 'Notifier');
Right now, when I try to emit the event, an error occurred:
Could not resolve argument $notification of "App\Twig\Components\Notifier::show()", maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?
... because argoment is "translated" (serialized I assume) as an array
.
In the docs the example works because Product
is an entity, and the param converter system kicks in, transforming the int
passed to emit
back to an object
.
How to obtain the same result in my example?
As stated in the documentation, emit()
only accept some scalars in the array, so you won't be able to pass "new Notification()" like that (at least not a specific instance)
You can also pass extra (scalar) data to the listeners
So i see two potential options (there may be more):
- if you persist your Notification in any way, you can use ParamConverters as you identified
- you also could play with "hydrateWith / desydrateWith" (see this section in the documentation)
Last "option" / "alternative": if you dispatch this Event from PHP, and only listen to it in another PHP class... you could also use a standard EventDispatcher and avoid a maybe-unwanted roundtrip via the front-end (not sure if applicable in your case, but sometimes it can be a good idea)
@smnandre thanks for the idea. Do the hydrateWith / dehydrateWith process occurs also when using #[LiveArg]
? For what I understand from the docs, it seems that the process is used only when passing props to the component....
There may be a better way, but i tried something and i believe it suits your needs :
Twig Component
#[AsLiveComponent('Foo')]
class Foo extends AbstractController
{
use DefaultActionTrait;
use ComponentToolsTrait;
#[LiveAction]
public function bar(): void
{
$this->emit('FooBar', ['notification' => new Notification('Hello', 'World!')]);
}
#[LiveListener('FooBar')]
public function onFooBar(
#[LiveArg('notification')] #[ValueResolver(NotificationResolver::class)] Notification $notification
)
{
dump($notification);
}
}
Twig Template
<div {{ attributes.defaults({}) }}>
<button
type="button"
data-action="live#action"
data-action-name="bar()"
>BAR</button>
</div>
Notification DTO
class Notification
{
public function __construct(
public string $title,
public string $message,
) {
}
public function getTitle(): string
{
return $this->title;
}
public function getMessage(): string
{
return $this->message;
}
}
And a little ValueResolver
class NotificationResolver implements ValueResolverInterface
{
public function resolve(Request $request, ArgumentMetadata $argument): array
{
if (Notification::class !== $argument->getType()) {
return [];
}
if (!is_array($attribute = $request->attributes->get($argument->getName()))) {
return [];
}
if (!isset($attribute['title']) || !isset($attribute['message'])) {
return [];
}
if (!is_string($attribute['title']) || !is_string($attribute['message'])) {
throw new NotFoundHttpException(sprintf('The notification for the "%s" parameter is invalid.', $argument->getName()));
}
return [new Notification($attribute['title'], $attribute['message'])];
}
}
And when you click on the button, the live action is called, the event is emitted, and you can get the notification object as you wanted.
Requests | Debug |
---|---|