diff --git a/src/Symfony/Component/Scheduler/CHANGELOG.md b/src/Symfony/Component/Scheduler/CHANGELOG.md index b26e862803bc..bec878751933 100644 --- a/src/Symfony/Component/Scheduler/CHANGELOG.md +++ b/src/Symfony/Component/Scheduler/CHANGELOG.md @@ -12,6 +12,7 @@ CHANGELOG * Make `ScheduledStamp` "send-able" * Add `ScheduledStamp` to `RedispatchMessage` * Allow modifying Schedule instances at runtime + * Add `MessageProviderInterface` to trigger unique messages at runtime 6.3 --- diff --git a/src/Symfony/Component/Scheduler/Command/DebugCommand.php b/src/Symfony/Component/Scheduler/Command/DebugCommand.php index 67c4e0cf2525..58fde9006221 100644 --- a/src/Symfony/Component/Scheduler/Command/DebugCommand.php +++ b/src/Symfony/Component/Scheduler/Command/DebugCommand.php @@ -18,7 +18,6 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use Symfony\Component\Messenger\Envelope; use Symfony\Component\Scheduler\RecurringMessage; use Symfony\Component\Scheduler\ScheduleProviderInterface; use Symfony\Contracts\Service\ServiceProviderInterface; @@ -95,7 +94,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int continue; } $io->table( - ['Message', 'Trigger', 'Next Run'], + ['Trigger', 'Provider', 'Next Run'], array_filter(array_map(self::renderRecurringMessage(...), $messages, array_fill(0, \count($messages), $date), array_fill(0, \count($messages), $input->getOption('all')))), ); } @@ -108,19 +107,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int */ private static function renderRecurringMessage(RecurringMessage $recurringMessage, \DateTimeImmutable $date, bool $all): ?array { - $message = $recurringMessage->getMessage(); $trigger = $recurringMessage->getTrigger(); - if ($message instanceof Envelope) { - $message = $message->getMessage(); - } - $next = $trigger->getNextRunDate($date)?->format('r') ?? '-'; if ('-' === $next && !$all) { return null; } - $name = $message instanceof \Stringable ? (string) $message : (new \ReflectionClass($message))->getShortName(); - return [$name, (string) $trigger, $next]; + return [(string) $trigger, $recurringMessage->getProvider()::class, $next]; } } diff --git a/src/Symfony/Component/Scheduler/Generator/MessageGenerator.php b/src/Symfony/Component/Scheduler/Generator/MessageGenerator.php index 5d29f1eced26..0e81e988f231 100644 --- a/src/Symfony/Component/Scheduler/Generator/MessageGenerator.php +++ b/src/Symfony/Component/Scheduler/Generator/MessageGenerator.php @@ -33,6 +33,9 @@ public function __construct( $this->waitUntil = new \DateTimeImmutable('@0'); } + /** + * @return \Generator + */ public function getMessages(): \Generator { $checkpoint = $this->checkpoint(); @@ -61,7 +64,6 @@ public function getMessages(): \Generator /** @var RecurringMessage $recurringMessage */ [$time, $index, $recurringMessage] = $heap->extract(); $id = $recurringMessage->getId(); - $message = $recurringMessage->getMessage(); $trigger = $recurringMessage->getTrigger(); $yield = true; @@ -77,7 +79,11 @@ public function getMessages(): \Generator } if ($yield) { - yield (new MessageContext($this->name, $id, $trigger, $time, $nextTime)) => $message; + $context = new MessageContext($this->name, $id, $trigger, $time, $nextTime); + foreach ($recurringMessage->getMessages($context) as $message) { + yield $context => $message; + } + $checkpoint->save($time, $index); } } diff --git a/src/Symfony/Component/Scheduler/RecurringMessage.php b/src/Symfony/Component/Scheduler/RecurringMessage.php index db2512486aa4..110fc215789b 100644 --- a/src/Symfony/Component/Scheduler/RecurringMessage.php +++ b/src/Symfony/Component/Scheduler/RecurringMessage.php @@ -12,18 +12,21 @@ namespace Symfony\Component\Scheduler; use Symfony\Component\Scheduler\Exception\InvalidArgumentException; +use Symfony\Component\Scheduler\Generator\MessageContext; use Symfony\Component\Scheduler\Trigger\CronExpressionTrigger; use Symfony\Component\Scheduler\Trigger\JitterTrigger; +use Symfony\Component\Scheduler\Trigger\MessageProviderInterface; use Symfony\Component\Scheduler\Trigger\PeriodicalTrigger; +use Symfony\Component\Scheduler\Trigger\StaticMessageProvider; use Symfony\Component\Scheduler\Trigger\TriggerInterface; -final class RecurringMessage +final class RecurringMessage implements MessageProviderInterface { private string $id; private function __construct( private readonly TriggerInterface $trigger, - private readonly object $message, + private readonly MessageProviderInterface $provider, ) { } @@ -37,35 +40,53 @@ private function __construct( * * A relative date format as supported by \DateInterval; * * A \DateInterval instance. * + * @param MessageProviderInterface|object $message A message provider that yields messages or a static message that will be dispatched on every trigger + * * @see https://en.wikipedia.org/wiki/ISO_8601#Durations * @see https://php.net/datetime.formats.relative */ public static function every(string|int|\DateInterval $frequency, object $message, string|\DateTimeImmutable $from = null, string|\DateTimeImmutable $until = new \DateTimeImmutable('3000-01-01')): self { - return new self(new PeriodicalTrigger($frequency, $from, $until), $message); + return self::trigger(new PeriodicalTrigger($frequency, $from, $until), $message); } + /** + * @param MessageProviderInterface|object $message A message provider that yields messages or a static message that will be dispatched on every trigger + */ public static function cron(string $expression, object $message, \DateTimeZone|string $timezone = null): self { if (!str_contains($expression, '#')) { - return new self(CronExpressionTrigger::fromSpec($expression, null, $timezone), $message); + return self::trigger(CronExpressionTrigger::fromSpec($expression, null, $timezone), $message); } if (!$message instanceof \Stringable) { throw new InvalidArgumentException('A message must be stringable to use "hashed" cron expressions.'); } - return new self(CronExpressionTrigger::fromSpec($expression, (string) $message, $timezone), $message); + return self::trigger(CronExpressionTrigger::fromSpec($expression, (string) $message, $timezone), $message); } + /** + * @param MessageProviderInterface|object $message A message provider that yields messages or a static message that will be dispatched on every trigger + */ public static function trigger(TriggerInterface $trigger, object $message): self { - return new self($trigger, $message); + if ($message instanceof MessageProviderInterface) { + return new self($trigger, $message); + } + + try { + $description = $message instanceof \Stringable ? (string) $message : serialize($message); + } catch (\Exception) { + $description = $message::class; + } + + return new self($trigger, new StaticMessageProvider([$message], $description)); } public function withJitter(int $maxSeconds = 60): self { - return new self(new JitterTrigger($this->trigger, $maxSeconds), $this->message); + return new self(new JitterTrigger($this->trigger, $maxSeconds), $this->provider); } /** @@ -77,23 +98,22 @@ public function getId(): string return $this->id; } - try { - $message = $this->message instanceof \Stringable ? (string) $this->message : serialize($this->message); - } catch (\Exception) { - $message = ''; - } - return $this->id = hash('crc32c', implode('', [ - $this->message::class, - $message, + $this->provider::class, + $this->provider->getId(), $this->trigger::class, (string) $this->trigger, ])); } - public function getMessage(): object + public function getMessages(MessageContext $context): iterable + { + return $this->provider->getMessages($context); + } + + public function getProvider(): MessageProviderInterface { - return $this->message; + return $this->provider; } public function getTrigger(): TriggerInterface diff --git a/src/Symfony/Component/Scheduler/Tests/Command/DebugCommandTest.php b/src/Symfony/Component/Scheduler/Tests/Command/DebugCommandTest.php index ffa0664af7bb..dfba7b917201 100644 --- a/src/Symfony/Component/Scheduler/Tests/Command/DebugCommandTest.php +++ b/src/Symfony/Component/Scheduler/Tests/Command/DebugCommandTest.php @@ -71,9 +71,9 @@ public function testExecuteWithScheduleWithoutTriggerDoesNotDisplayMessage() "schedule_name\n". "-------------\n". "\n". - " --------- --------- ---------- \n". - " Message Trigger Next Run \n". - " --------- --------- ---------- \n". + " --------- ---------- ---------- \n". + " Trigger Provider Next Run \n". + " --------- ---------- ---------- \n". "\n", $tester->getDisplay(true)); } @@ -106,11 +106,11 @@ public function testExecuteWithScheduleWithoutTriggerShowingNoNextRunWithAllOpti "schedule_name\n". "-------------\n". "\n". - " ---------- --------- ---------- \n". - " Message Trigger Next Run \n". - " ---------- --------- ---------- \n". - " stdClass test - \n". - " ---------- --------- ---------- \n". + " --------- ----------------------------------------------------------- ---------- \n". + " Trigger Provider Next Run \n". + " --------- ----------------------------------------------------------- ---------- \n". + " test Symfony\Component\Scheduler\Trigger\StaticMessageProvider - \n". + " --------- ----------------------------------------------------------- ---------- \n". "\n", $tester->getDisplay(true)); } @@ -143,11 +143,11 @@ public function testExecuteWithSchedule() "schedule_name\n". "-------------\n". "\n". - " ---------- ------------------------------- --------------------------------- \n". - " Message Trigger Next Run \n". - " ---------- ------------------------------- --------------------------------- \n". - " stdClass every first day of next month \w{3}, \d{1,2} \w{3} \d{4} \d{2}:\d{2}:\d{2} (\+|-)\d{4} \n". - " ---------- ------------------------------- --------------------------------- \n". + " ------------------------------- ----------------------------------------------------------- --------------------------------- \n". + " Trigger Provider Next Run \n". + " ------------------------------- ----------------------------------------------------------- --------------------------------- \n". + " every first day of next month Symfony\\\\Component\\\\Scheduler\\\\Trigger\\\\StaticMessageProvider \w{3}, \d{1,2} \w{3} \d{4} \d{2}:\d{2}:\d{2} (\+|-)\d{4} \n". + " ------------------------------- ----------------------------------------------------------- --------------------------------- \n". "\n/", $tester->getDisplay(true)); } } diff --git a/src/Symfony/Component/Scheduler/Tests/Trigger/CallbackMessageProviderTest.php b/src/Symfony/Component/Scheduler/Tests/Trigger/CallbackMessageProviderTest.php new file mode 100644 index 000000000000..3c155014b3ba --- /dev/null +++ b/src/Symfony/Component/Scheduler/Tests/Trigger/CallbackMessageProviderTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Scheduler\Tests\Trigger; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Scheduler\Generator\MessageContext; +use Symfony\Component\Scheduler\Trigger\CallbackMessageProvider; +use Symfony\Component\Scheduler\Trigger\TriggerInterface; + +class CallbackMessageProviderTest extends TestCase +{ + public function testToString() + { + $context = new MessageContext('test', 'test', $this->createMock(TriggerInterface::class), $this->createMock(\DateTimeImmutable::class)); + $messageProvider = new CallbackMessageProvider(fn () => []); + $this->assertEquals([], $messageProvider->getMessages($context)); + $this->assertEquals('', $messageProvider->getId()); + + $messageProvider = new CallbackMessageProvider(fn () => [new \stdClass()], ''); + $this->assertEquals([new \stdClass()], $messageProvider->getMessages($context)); + $this->assertSame('', $messageProvider->getId()); + + $messageProvider = new CallbackMessageProvider(fn () => yield new \stdClass(), 'foo'); + $this->assertInstanceOf(\Generator::class, $messageProvider->getMessages($context)); + $this->assertSame('foo', $messageProvider->getId()); + } +} diff --git a/src/Symfony/Component/Scheduler/Trigger/CallbackMessageProvider.php b/src/Symfony/Component/Scheduler/Trigger/CallbackMessageProvider.php new file mode 100644 index 000000000000..f3c4e329a3da --- /dev/null +++ b/src/Symfony/Component/Scheduler/Trigger/CallbackMessageProvider.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Scheduler\Trigger; + +use Symfony\Component\Scheduler\Generator\MessageContext; + +final class CallbackMessageProvider implements MessageProviderInterface +{ + private \Closure $callback; + + /** + * @param callable(MessageContext): iterable $callback + */ + public function __construct(callable $callback, private string $id = '') + { + $this->callback = $callback(...); + } + + public function getMessages(MessageContext $context): iterable + { + return ($this->callback)($context); + } + + public function getId(): string + { + return $this->id; + } +} diff --git a/src/Symfony/Component/Scheduler/Trigger/MessageProviderInterface.php b/src/Symfony/Component/Scheduler/Trigger/MessageProviderInterface.php new file mode 100644 index 000000000000..b6f03142cb14 --- /dev/null +++ b/src/Symfony/Component/Scheduler/Trigger/MessageProviderInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Scheduler\Trigger; + +use Symfony\Component\Scheduler\Generator\MessageContext; + +interface MessageProviderInterface +{ + /** + * @return iterable + */ + public function getMessages(MessageContext $context): iterable; + + public function getId(): string; +} diff --git a/src/Symfony/Component/Scheduler/Trigger/StaticMessageProvider.php b/src/Symfony/Component/Scheduler/Trigger/StaticMessageProvider.php new file mode 100644 index 000000000000..88f21e464a37 --- /dev/null +++ b/src/Symfony/Component/Scheduler/Trigger/StaticMessageProvider.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Scheduler\Trigger; + +use Symfony\Component\Scheduler\Generator\MessageContext; + +final class StaticMessageProvider implements MessageProviderInterface +{ + /** + * @param array $messages + */ + public function __construct( + private array $messages, + private string $id = '', + ) { + } + + public function getMessages(MessageContext $context): iterable + { + return $this->messages; + } + + public function getId(): string + { + return $this->id; + } +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy