ux icon indicating copy to clipboard operation
ux copied to clipboard

[LiveComponent] Passing an entire object (not an entity) to listeners

Open gremo opened this issue 4 months ago • 3 comments

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?

gremo avatar Feb 09 '24 13:02 gremo

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 avatar Feb 09 '24 20:02 smnandre

@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....

gremo avatar Feb 10 '24 13:02 gremo

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
Capture d’écran 2024-02-10 à 16 52 01 Capture d’écran 2024-02-10 à 16 52 10

smnandre avatar Feb 10 '24 15:02 smnandre