From 3664d0847104bc4d50e81c8ad4e74a4380935d1e Mon Sep 17 00:00:00 2001 From: Tugdual Saunier Date: Fri, 21 Apr 2023 16:43:31 -0400 Subject: [PATCH] [Scheduler] Allow MessageGenerator to provide a MessageContext And add it to the ScheduledStamp in case of use within Messenger. Replaces 50096 Fix 50085 This approach allows to provie some context that the message consumers can use or not depending on its use case --- .../Scheduler/Generator/MessageContext.php | 29 ++++++++++++++ .../Scheduler/Generator/MessageGenerator.php | 2 +- .../Generator/MessageGeneratorInterface.php | 2 +- .../Scheduler/Messenger/ScheduledStamp.php | 4 ++ .../Messenger/SchedulerTransport.php | 4 +- .../Tests/Generator/MessageGeneratorTest.php | 38 ++++++++++++++++++- .../Messenger/SchedulerTransportTest.php | 10 ++++- 7 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 src/Symfony/Component/Scheduler/Generator/MessageContext.php diff --git a/src/Symfony/Component/Scheduler/Generator/MessageContext.php b/src/Symfony/Component/Scheduler/Generator/MessageContext.php new file mode 100644 index 0000000000000..51caf94e9e042 --- /dev/null +++ b/src/Symfony/Component/Scheduler/Generator/MessageContext.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Scheduler\Generator; + +use Symfony\Component\Scheduler\Trigger\TriggerInterface; + +/** + * @author Tugdual Saunier + * + * @experimental + */ +final class MessageContext +{ + public function __construct( + public readonly TriggerInterface $trigger, + public readonly \DateTimeImmutable $triggeredAt, + public readonly \DateTimeImmutable|null $nextTriggerAt = null, + ) { + } +} diff --git a/src/Symfony/Component/Scheduler/Generator/MessageGenerator.php b/src/Symfony/Component/Scheduler/Generator/MessageGenerator.php index 2f9743c59bfc2..ffa5618332624 100644 --- a/src/Symfony/Component/Scheduler/Generator/MessageGenerator.php +++ b/src/Symfony/Component/Scheduler/Generator/MessageGenerator.php @@ -70,7 +70,7 @@ public function getMessages(): \Generator } if ($yield) { - yield $message; + yield (new MessageContext($trigger, $time, $nextTime)) => $message; $this->checkpoint->save($time, $index); } } diff --git a/src/Symfony/Component/Scheduler/Generator/MessageGeneratorInterface.php b/src/Symfony/Component/Scheduler/Generator/MessageGeneratorInterface.php index 8b2f7eeacc4ce..64835e19d6138 100644 --- a/src/Symfony/Component/Scheduler/Generator/MessageGeneratorInterface.php +++ b/src/Symfony/Component/Scheduler/Generator/MessageGeneratorInterface.php @@ -17,7 +17,7 @@ interface MessageGeneratorInterface { /** - * @return iterable + * @return iterable */ public function getMessages(): iterable; } diff --git a/src/Symfony/Component/Scheduler/Messenger/ScheduledStamp.php b/src/Symfony/Component/Scheduler/Messenger/ScheduledStamp.php index 4b1b5cf1b577d..79efc1121e581 100644 --- a/src/Symfony/Component/Scheduler/Messenger/ScheduledStamp.php +++ b/src/Symfony/Component/Scheduler/Messenger/ScheduledStamp.php @@ -12,10 +12,14 @@ namespace Symfony\Component\Scheduler\Messenger; use Symfony\Component\Messenger\Stamp\NonSendableStampInterface; +use Symfony\Component\Scheduler\Generator\MessageContext; /** * @experimental */ final class ScheduledStamp implements NonSendableStampInterface { + public function __construct(public readonly MessageContext $messageContext) + { + } } diff --git a/src/Symfony/Component/Scheduler/Messenger/SchedulerTransport.php b/src/Symfony/Component/Scheduler/Messenger/SchedulerTransport.php index 9625e8c41bba9..414945e617c83 100644 --- a/src/Symfony/Component/Scheduler/Messenger/SchedulerTransport.php +++ b/src/Symfony/Component/Scheduler/Messenger/SchedulerTransport.php @@ -28,8 +28,8 @@ public function __construct( public function get(): iterable { - foreach ($this->messageGenerator->getMessages() as $message) { - yield Envelope::wrap($message, [new ScheduledStamp()]); + foreach ($this->messageGenerator->getMessages() as $context => $message) { + yield Envelope::wrap($message, [new ScheduledStamp($context)]); } } diff --git a/src/Symfony/Component/Scheduler/Tests/Generator/MessageGeneratorTest.php b/src/Symfony/Component/Scheduler/Tests/Generator/MessageGeneratorTest.php index 8b6900b8bb037..1926bbb70fb7f 100644 --- a/src/Symfony/Component/Scheduler/Tests/Generator/MessageGeneratorTest.php +++ b/src/Symfony/Component/Scheduler/Tests/Generator/MessageGeneratorTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Cache\Adapter\ArrayAdapter; use Symfony\Component\Clock\ClockInterface; +use Symfony\Component\Scheduler\Generator\MessageContext; use Symfony\Component\Scheduler\Generator\MessageGenerator; use Symfony\Component\Scheduler\RecurringMessage; use Symfony\Component\Scheduler\Schedule; @@ -43,14 +44,47 @@ public function testGetMessages(string $startTime, array $runs, array $schedule) $scheduler = new MessageGenerator($schedule, 'dummy', $clock); // Warmup. The first run is always returns nothing. - $this->assertSame([], iterator_to_array($scheduler->getMessages())); + $this->assertSame([], iterator_to_array($scheduler->getMessages(), false)); foreach ($runs as $time => $expected) { $now = self::makeDateTime($time); - $this->assertSame($expected, iterator_to_array($scheduler->getMessages())); + $this->assertSame($expected, iterator_to_array($scheduler->getMessages(), false)); } } + public function testYieldedContext() + { + // for referencing + $now = self::makeDateTime('22:12:00'); + + $clock = $this->createMock(ClockInterface::class); + $clock->method('now')->willReturnReference($now); + + $message = $this->createMessage((object) ['id' => 'message'], '22:13:00', '22:14:00', '22:16:00'); + $schedule = (new Schedule())->add($message); + $schedule->stateful(new ArrayAdapter()); + + $scheduler = new MessageGenerator($schedule, 'dummy', $clock); + + // Warmup. The first run is alw ays returns nothing. + $this->assertSame([], iterator_to_array($scheduler->getMessages(), false)); + + $now = self::makeDateTime('22:14:10'); + + $iterator = $scheduler->getMessages(); + + $this->assertInstanceOf(MessageContext::class, $context = $iterator->key()); + $this->assertSame($message->getTrigger(), $context->trigger); + $this->assertEquals(self::makeDateTime('22:13:00'), $context->triggeredAt); + $this->assertEquals(self::makeDateTime('22:14:00'), $context->nextTriggerAt); + + $iterator->next(); + $this->assertInstanceOf(MessageContext::class, $context = $iterator->key()); + $this->assertSame($message->getTrigger(), $context->trigger); + $this->assertEquals(self::makeDateTime('22:14:00'), $context->triggeredAt); + $this->assertEquals(self::makeDateTime('22:16:00'), $context->nextTriggerAt); + } + public static function messagesProvider(): \Generator { $first = (object) ['id' => 'first']; diff --git a/src/Symfony/Component/Scheduler/Tests/Messenger/SchedulerTransportTest.php b/src/Symfony/Component/Scheduler/Tests/Messenger/SchedulerTransportTest.php index 09f980c07ba2f..315048e9fbe0e 100644 --- a/src/Symfony/Component/Scheduler/Tests/Messenger/SchedulerTransportTest.php +++ b/src/Symfony/Component/Scheduler/Tests/Messenger/SchedulerTransportTest.php @@ -14,9 +14,11 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Scheduler\Exception\LogicException; +use Symfony\Component\Scheduler\Generator\MessageContext; use Symfony\Component\Scheduler\Generator\MessageGeneratorInterface; use Symfony\Component\Scheduler\Messenger\ScheduledStamp; use Symfony\Component\Scheduler\Messenger\SchedulerTransport; +use Symfony\Component\Scheduler\Trigger\TriggerInterface; class SchedulerTransportTest extends TestCase { @@ -26,9 +28,15 @@ public function testGetFromIterator() (object) ['id' => 'first'], (object) ['id' => 'second'], ]; - $generator = $this->createConfiguredMock(MessageGeneratorInterface::class, [ + $generator = $this->createMock(MessageGeneratorInterface::class, [ 'getMessages' => $messages, ]); + $generator->method('getMessages')->willReturnCallback(function () use ($messages): \Generator { + $trigger = $this->createMock(TriggerInterface::class); + $triggerAt = new \DateTimeImmutable('2020-02-20T02:00:00', new \DateTimeZone('UTC')); + yield (new MessageContext($trigger, $triggerAt)) => $messages[0]; + yield (new MessageContext($trigger, $triggerAt)) => $messages[1]; + }); $transport = new SchedulerTransport($generator); foreach ($transport->get() as $envelope) {