Skip to content

[DoctrineBridge][Messenger] Add MessageRecordingEntity functionality #34310

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 7 commits into from
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\Messenger;

use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\PostFlushEventArgs;
use Doctrine\ORM\Events;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Stamp\DispatchAfterCurrentBusStamp;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Matthias Noback <matthiasnoback@gmail.com>
* @author Valentin Udaltsov <udaltsov.valentin@gmail.com>
*/
final class DispatchEntityMessagesDoctrineSubscriber implements EventSubscriber
{
private $bus;
private $dispatcher;

public function __construct(MessageBusInterface $bus, EventDispatcherInterface $dispatcher)
{
$this->bus = $bus;
$this->dispatcher = $dispatcher;
}

/**
* {@inheritdoc}
*/
public function getSubscribedEvents(): array
{
return [
Events::postFlush,
];
}

public function postFlush(PostFlushEventArgs $args): void
{
foreach ($args->getEntityManager()->getUnitOfWork()->getIdentityMap() as $entities) {
foreach ($entities as $entity) {
if (!$entity instanceof MessageRecordingEntityInterface) {
continue;
}

$entity->dispatchMessages(function (array $messages) use ($entity): void {
foreach ($messages as $message) {
$envelope = Envelope::wrap($message, [new DispatchAfterCurrentBusStamp()]);
$event = new EntityMessagePreDispatchEvent($entity, $envelope);
$this->dispatcher->dispatch($event);
$this->bus->dispatch($event->getEnvelope());
}
});
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\Messenger;

use Symfony\Component\Messenger\Envelope;

final class EntityMessagePreDispatchEvent
Copy link
Member

Choose a reason for hiding this comment

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

I don't quite understand the naming of this class. Can you explain? This seems to me like a ... EntityMessageEvent - but I don't understand the "Pre" part - it happens postFlush().

{
private $entity;
private $envelope;

public function __construct(object $entity, Envelope $envelope)
{
$this->entity = $entity;
$this->envelope = $envelope;
}

public function getEntity(): object
{
return $this->entity;
}

public function getEnvelope(): Envelope
{
return $this->envelope;
}

public function setEnvelope(Envelope $envelope): void
{
$this->envelope = $envelope;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\Messenger;

/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Matthias Noback <matthiasnoback@gmail.com>
* @author Valentin Udaltsov <udaltsov.valentin@gmail.com>
*/
interface MessageRecordingEntityInterface
{
/**
* @param callable $dispatcher callable(object[] $messages): void
Copy link
Member

Choose a reason for hiding this comment

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

Probably need some phpdoc explanation on the purpose / usage of this method.

*/
public function dispatchMessages(callable $dispatcher): void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\Messenger;

/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
* @author Matthias Noback <matthiasnoback@gmail.com>
* @author Valentin Udaltsov <udaltsov.valentin@gmail.com>
*/
trait MessageRecordingEntityTrait
{
/**
* @var object[]
*/
private $messages = [];

/**
* @see MessageRecordingEntityInterface::dispatchMessages()
*/
final public function dispatchMessages(callable $dispatcher): void
{
[$messages, $this->messages] = [$this->messages, []];
$dispatcher($messages);
}

final protected function recordMessage(object $message): void
{
$this->messages[] = $message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\Tests\Fixtures;

use Symfony\Bridge\Doctrine\Messenger\MessageRecordingEntityInterface;
use Symfony\Bridge\Doctrine\Messenger\MessageRecordingEntityTrait;

final class MessageRecordingEntity implements MessageRecordingEntityInterface
{
use MessageRecordingEntityTrait;

public function doRecordMessage(object $message): void
Copy link
Member

Choose a reason for hiding this comment

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

Do we need a public method (by default) in the entity?

{
$this->recordMessage($message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\Tests\Messenger;

use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\PostFlushEventArgs;
use Doctrine\ORM\UnitOfWork;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Doctrine\Messenger\DispatchEntityMessagesDoctrineSubscriber;
use Symfony\Bridge\Doctrine\Messenger\EntityMessagePreDispatchEvent;
use Symfony\Bridge\Doctrine\Tests\Fixtures\MessageRecordingEntity;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Stamp\DispatchAfterCurrentBusStamp;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

class DispatchEntityMessagesDoctrineSubscriberTest extends TestCase
{
public function testMessagesAreDispatched(): void
{
$entity = new MessageRecordingEntity();
$message1 = new \stdClass();
$message2 = new \stdClass();
$message2->a = 1;
$entity->doRecordMessage($message1);
$entity->doRecordMessage($message2);

$bus = $this->createMock(MessageBusInterface::class);
$bus
->expects(static::exactly(2))
->method('dispatch')
->withConsecutive(
[Envelope::wrap($message1, [new DispatchAfterCurrentBusStamp()])],
[Envelope::wrap($message2, [new DispatchAfterCurrentBusStamp()])]
)
->willReturn(new Envelope(new \stdClass()))
;

$dispatcher = $this->createMock(EventDispatcherInterface::class);

$args = $this->createPostFlushArgs([$entity]);

$subscriber = new DispatchEntityMessagesDoctrineSubscriber($bus, $dispatcher);
$subscriber->postFlush($args);
}

public function testEventIsDispatched(): void
{
$entity = new MessageRecordingEntity();
$message = new \stdClass();
$entity->doRecordMessage($message);

$bus = $this->createMock(MessageBusInterface::class);
$bus->method('dispatch')->willReturn(new Envelope(new \stdClass()));

$dispatcher = $this->createMock(EventDispatcherInterface::class);
$dispatcher
->expects(static::once())
->method('dispatch')
->with(new EntityMessagePreDispatchEvent($entity, Envelope::wrap($message, [
new DispatchAfterCurrentBusStamp(),
])))
;

$args = $this->createPostFlushArgs([$entity]);

$subscriber = new DispatchEntityMessagesDoctrineSubscriber($bus, $dispatcher);
$subscriber->postFlush($args);
}

private function createPostFlushArgs(array $entities): PostFlushEventArgs
{
$uow = $this->createMock(UnitOfWork::class);
$uow
->expects(static::once())
->method('getIdentityMap')
->willReturn([$entities])
;

$em = $this->createMock(EntityManagerInterface::class);
$em
->expects(static::once())
->method('getUnitOfWork')
->willReturn($uow)
;

return new PostFlushEventArgs($em);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Doctrine\Tests\Messenger;

use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Doctrine\Tests\Fixtures\MessageRecordingEntity;

class MessageRecordingEntityTraitTest extends TestCase
{
public function testDispatch(): void
{
$entity = new MessageRecordingEntity();
$message = new \stdClass();

$entity->doRecordMessage($message);

$entity->dispatchMessages(static function (array $messages) use ($message): void {
static::assertSame([$message], $messages);
});
}

public function testMessagesClearedAfterDispatch(): void
{
$entity = new MessageRecordingEntity();
$entity->doRecordMessage(new \stdClass());

$entity->dispatchMessages(static function (): void {
});

$entity->dispatchMessages(static function (array $messages): void {
static::assertCount(0, $messages);
});
}

public function testMessagesClearedOnDispatchFailure(): void
{
$entity = new MessageRecordingEntity();
$entity->doRecordMessage(new \stdClass());

try {
$entity->dispatchMessages(static function (): void {
throw new \Exception();
});
} catch (\Exception $exception) {
}

$entity->dispatchMessages(static function (array $messages): void {
static::assertCount(0, $messages);
});
}
}