Skip to content

[Messenger] Add support for "recording" events from entities #28850

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

Closed
wants to merge 1 commit into from

Conversation

Nyholm
Copy link
Member

@Nyholm Nyholm commented Oct 13, 2018

Q A
Branch? master
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets
License MIT
Doc PR

This is dependent of #28849.

services:
    messenger.doctrine.entity_message_collector:
        class: Symfony\Bridge\Doctrine\Messenger\EventSubscriber\EntityMessageCollector
        public: false
        arguments: ['@messenger.bus.event']
        tags:
          - { name: 'doctrine.event_subscriber', connection: 'default' }
use Symfony\Bridge\Doctrine\EntityMessage\EntityMessageCollectionInterface;
use Symfony\Bridge\Doctrine\EntityMessage\MessageRecorderTrait;

class User implements EntityMessageCollectionInterface
{
    use MessageRecorderTrait;

    // ...
    
    public function setEmail(string $email)
    {
        $oldEmail = $this->email = $email;
        $this->email = $email;
        $this->record(new EmailChanged($this->id, $oldEmail, $email);
    }

@Nyholm Nyholm changed the title [Messenger ]Add support for "recording" events from entities [Messenger] Add support for "recording" events from entities Oct 13, 2018
@nicolas-grekas nicolas-grekas added this to the next milestone Oct 14, 2018
@Koc
Copy link
Contributor

Koc commented Oct 15, 2018

I'm afraid that this listener should be stateful and events should be dispatched on post flush event https://github.com/doctrine/doctrine2/blob/2.7/lib/Doctrine/ORM/UnitOfWork.php#L430 (because Doctrine's transaction may fall)

@Nyholm Nyholm force-pushed the messenger-transaction-doctrine branch from 9441919 to 305438e Compare February 22, 2019 14:01
@Nyholm Nyholm force-pushed the messenger-transaction-doctrine branch 3 times, most recently from 274e3dd to 5d75022 Compare March 19, 2019 05:45
@Nyholm
Copy link
Member Author

Nyholm commented Mar 19, 2019

This PR is rebased and squashed

@fabpot
Copy link
Member

fabpot commented Apr 8, 2019

/cc @sroze

Copy link
Contributor

@sroze sroze left a comment

Choose a reason for hiding this comment

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

I agree with @Koc, the dispatch of these events needs to happen at postFlush otherwise you might dispatch events from rollbacked entities.

But it's a fantastic feature ❤️

@Nyholm
Copy link
Member Author

Nyholm commented Aug 22, 2019

Thank you. I've updated the PR


if ($entity instanceof EntityMessageCollectionInterface) {
foreach ($entity->getRecordedMessages() as $message) {
$this->messageBus->dispatch($message, [new DispatchAfterCurrentBusStamp()]);
Copy link
Contributor

Choose a reason for hiding this comment

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

What if the message bus throws an exception?

Copy link
Member Author

Choose a reason for hiding this comment

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

That will bubble up. But the Doctrine transaction will still be committed.

Copy link
Contributor

Choose a reason for hiding this comment

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

So there's a risk some messages will be dispatched while some won't. Not sure if that's an issue yet, just pointing it out.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, If we dispatch 5 messages and number 2 fails, then the 3 other will never be handled. But that is an issue with the message bus and has nothing to do with this PR.

@Nyholm
Copy link
Member Author

Nyholm commented Sep 20, 2019

Maybe @weaverryan could have a look?

Copy link
Contributor

@sroze sroze left a comment

Choose a reason for hiding this comment

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

Looks good to me, sorry for the delay 👍.

I imagine you'll PR on DoctrineBundle to enable this listener from a configuration flag?

@sroze
Copy link
Contributor

sroze commented Sep 29, 2019

@Nyholm can you squad and push again? We should have Travis & AppVeyer rebuilding this :)

@Nyholm Nyholm force-pushed the messenger-transaction-doctrine branch from 87444c6 to 692448f Compare September 29, 2019 15:39
@Nyholm
Copy link
Member Author

Nyholm commented Sep 29, 2019

Comments are squashed. I will create a PR to doctrine bundle right away

@maxhelias
Copy link
Contributor

Nice feature! This should not be in 4.4 ?

@vudaltsov
Copy link
Contributor

vudaltsov commented Sep 30, 2019

What if we avoid leaking events and eliminate the getter on the entity?

public function dispatchEvents(callable $dispatcher): void
{
    $dispatcher($this->events);
    $this->events = [];
}

@Nyholm
Copy link
Member Author

Nyholm commented Sep 30, 2019

Hm. Callables makes the stack tree hard to follow.

@vudaltsov How would you configure an implementation like this to work with DI?

@vudaltsov
Copy link
Contributor

vudaltsov commented Sep 30, 2019

@Nyholm , this would be almost the same code you use:

private function collectEventsFromEntity(LifecycleEventArgs $message): void
{
    $entity = $message->getEntity();

    if (!$entity instanceof EntityMessageCollectionInterface) {
        return;
    }

    $entity->dispatchEvents(function (iterable $events): void {
        foreach ($events as $event) {
            $this->messageBus->dispatch($event, [new DispatchAfterCurrentBusStamp()]);
        }
    });
}

As you see, we only need one command method in this case, not query+command.

@vudaltsov
Copy link
Contributor

vudaltsov commented Sep 30, 2019

Also, what if we implement this as a middleware, so that we don't depend on Doctrine here? It could do the same thing as the subscriber after the doctrine_transaction middleware. It will operate on any message object, not on a Doctrine entity.

$this->collectEventsFromEntity($event);
}

private function collectEventsFromEntity(LifecycleEventArgs $message)
Copy link
Contributor

Choose a reason for hiding this comment

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

if the method is called collectEventsFromEntity, then it should accept object $entity as argument?

];
}

public function postFlush(LifecycleEventArgs $event)
Copy link
Contributor

Choose a reason for hiding this comment

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

This code will fail.
The postFlush event is not a lifecycle event. It passes an instance of Doctrine\ORM\Event\PostFlushEventArgs which is not a subclass of LifecycleEventArgs. See https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/events.html#postflush .

Copy link
Contributor

Choose a reason for hiding this comment

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

So this event cannot be used here at all, because at this point you do not know which entities were flushed.

@Nyholm
Copy link
Member Author

Nyholm commented May 3, 2020

Im closing this in favor of #34310

@Nyholm Nyholm closed this May 3, 2020
@nicolas-grekas nicolas-grekas modified the milestones: next, 5.1 May 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.