Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 06b16bd

Browse files
Jeroenyfabpot
authored andcommitted
[Scheduler] Trigger unique messages at runtime
1 parent efc6929 commit 06b16bd

File tree

9 files changed

+194
-41
lines changed

9 files changed

+194
-41
lines changed

src/Symfony/Component/Scheduler/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ CHANGELOG
1212
* Make `ScheduledStamp` "send-able"
1313
* Add `ScheduledStamp` to `RedispatchMessage`
1414
* Allow modifying Schedule instances at runtime
15+
* Add `MessageProviderInterface` to trigger unique messages at runtime
1516

1617
6.3
1718
---

src/Symfony/Component/Scheduler/Command/DebugCommand.php

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
use Symfony\Component\Console\Input\InputOption;
1919
use Symfony\Component\Console\Output\OutputInterface;
2020
use Symfony\Component\Console\Style\SymfonyStyle;
21-
use Symfony\Component\Messenger\Envelope;
2221
use Symfony\Component\Scheduler\RecurringMessage;
2322
use Symfony\Component\Scheduler\ScheduleProviderInterface;
2423
use Symfony\Contracts\Service\ServiceProviderInterface;
@@ -95,7 +94,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
9594
continue;
9695
}
9796
$io->table(
98-
['Message', 'Trigger', 'Next Run'],
97+
['Trigger', 'Provider', 'Next Run'],
9998
array_filter(array_map(self::renderRecurringMessage(...), $messages, array_fill(0, \count($messages), $date), array_fill(0, \count($messages), $input->getOption('all')))),
10099
);
101100
}
@@ -108,19 +107,13 @@ protected function execute(InputInterface $input, OutputInterface $output): int
108107
*/
109108
private static function renderRecurringMessage(RecurringMessage $recurringMessage, \DateTimeImmutable $date, bool $all): ?array
110109
{
111-
$message = $recurringMessage->getMessage();
112110
$trigger = $recurringMessage->getTrigger();
113111

114-
if ($message instanceof Envelope) {
115-
$message = $message->getMessage();
116-
}
117-
118112
$next = $trigger->getNextRunDate($date)?->format('r') ?? '-';
119113
if ('-' === $next && !$all) {
120114
return null;
121115
}
122-
$name = $message instanceof \Stringable ? (string) $message : (new \ReflectionClass($message))->getShortName();
123116

124-
return [$name, (string) $trigger, $next];
117+
return [(string) $trigger, $recurringMessage->getProvider()::class, $next];
125118
}
126119
}

src/Symfony/Component/Scheduler/Generator/MessageGenerator.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ public function __construct(
3333
$this->waitUntil = new \DateTimeImmutable('@0');
3434
}
3535

36+
/**
37+
* @return \Generator<MessageContext, object>
38+
*/
3639
public function getMessages(): \Generator
3740
{
3841
$checkpoint = $this->checkpoint();
@@ -61,7 +64,6 @@ public function getMessages(): \Generator
6164
/** @var RecurringMessage $recurringMessage */
6265
[$time, $index, $recurringMessage] = $heap->extract();
6366
$id = $recurringMessage->getId();
64-
$message = $recurringMessage->getMessage();
6567
$trigger = $recurringMessage->getTrigger();
6668
$yield = true;
6769

@@ -77,7 +79,11 @@ public function getMessages(): \Generator
7779
}
7880

7981
if ($yield) {
80-
yield (new MessageContext($this->name, $id, $trigger, $time, $nextTime)) => $message;
82+
$context = new MessageContext($this->name, $id, $trigger, $time, $nextTime);
83+
foreach ($recurringMessage->getMessages($context) as $message) {
84+
yield $context => $message;
85+
}
86+
8187
$checkpoint->save($time, $index);
8288
}
8389
}

src/Symfony/Component/Scheduler/RecurringMessage.php

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,21 @@
1212
namespace Symfony\Component\Scheduler;
1313

1414
use Symfony\Component\Scheduler\Exception\InvalidArgumentException;
15+
use Symfony\Component\Scheduler\Generator\MessageContext;
1516
use Symfony\Component\Scheduler\Trigger\CronExpressionTrigger;
1617
use Symfony\Component\Scheduler\Trigger\JitterTrigger;
18+
use Symfony\Component\Scheduler\Trigger\MessageProviderInterface;
1719
use Symfony\Component\Scheduler\Trigger\PeriodicalTrigger;
20+
use Symfony\Component\Scheduler\Trigger\StaticMessageProvider;
1821
use Symfony\Component\Scheduler\Trigger\TriggerInterface;
1922

20-
final class RecurringMessage
23+
final class RecurringMessage implements MessageProviderInterface
2124
{
2225
private string $id;
2326

2427
private function __construct(
2528
private readonly TriggerInterface $trigger,
26-
private readonly object $message,
29+
private readonly MessageProviderInterface $provider,
2730
) {
2831
}
2932

@@ -37,35 +40,53 @@ private function __construct(
3740
* * A relative date format as supported by \DateInterval;
3841
* * A \DateInterval instance.
3942
*
43+
* @param MessageProviderInterface|object $message A message provider that yields messages or a static message that will be dispatched on every trigger
44+
*
4045
* @see https://en.wikipedia.org/wiki/ISO_8601#Durations
4146
* @see https://php.net/datetime.formats.relative
4247
*/
4348
public static function every(string|int|\DateInterval $frequency, object $message, string|\DateTimeImmutable $from = null, string|\DateTimeImmutable $until = new \DateTimeImmutable('3000-01-01')): self
4449
{
45-
return new self(new PeriodicalTrigger($frequency, $from, $until), $message);
50+
return self::trigger(new PeriodicalTrigger($frequency, $from, $until), $message);
4651
}
4752

53+
/**
54+
* @param MessageProviderInterface|object $message A message provider that yields messages or a static message that will be dispatched on every trigger
55+
*/
4856
public static function cron(string $expression, object $message, \DateTimeZone|string $timezone = null): self
4957
{
5058
if (!str_contains($expression, '#')) {
51-
return new self(CronExpressionTrigger::fromSpec($expression, null, $timezone), $message);
59+
return self::trigger(CronExpressionTrigger::fromSpec($expression, null, $timezone), $message);
5260
}
5361

5462
if (!$message instanceof \Stringable) {
5563
throw new InvalidArgumentException('A message must be stringable to use "hashed" cron expressions.');
5664
}
5765

58-
return new self(CronExpressionTrigger::fromSpec($expression, (string) $message, $timezone), $message);
66+
return self::trigger(CronExpressionTrigger::fromSpec($expression, (string) $message, $timezone), $message);
5967
}
6068

69+
/**
70+
* @param MessageProviderInterface|object $message A message provider that yields messages or a static message that will be dispatched on every trigger
71+
*/
6172
public static function trigger(TriggerInterface $trigger, object $message): self
6273
{
63-
return new self($trigger, $message);
74+
if ($message instanceof MessageProviderInterface) {
75+
return new self($trigger, $message);
76+
}
77+
78+
try {
79+
$description = $message instanceof \Stringable ? (string) $message : serialize($message);
80+
} catch (\Exception) {
81+
$description = $message::class;
82+
}
83+
84+
return new self($trigger, new StaticMessageProvider([$message], $description));
6485
}
6586

6687
public function withJitter(int $maxSeconds = 60): self
6788
{
68-
return new self(new JitterTrigger($this->trigger, $maxSeconds), $this->message);
89+
return new self(new JitterTrigger($this->trigger, $maxSeconds), $this->provider);
6990
}
7091

7192
/**
@@ -77,23 +98,22 @@ public function getId(): string
7798
return $this->id;
7899
}
79100

80-
try {
81-
$message = $this->message instanceof \Stringable ? (string) $this->message : serialize($this->message);
82-
} catch (\Exception) {
83-
$message = '';
84-
}
85-
86101
return $this->id = hash('crc32c', implode('', [
87-
$this->message::class,
88-
$message,
102+
$this->provider::class,
103+
$this->provider->getId(),
89104
$this->trigger::class,
90105
(string) $this->trigger,
91106
]));
92107
}
93108

94-
public function getMessage(): object
109+
public function getMessages(MessageContext $context): iterable
110+
{
111+
return $this->provider->getMessages($context);
112+
}
113+
114+
public function getProvider(): MessageProviderInterface
95115
{
96-
return $this->message;
116+
return $this->provider;
97117
}
98118

99119
public function getTrigger(): TriggerInterface

src/Symfony/Component/Scheduler/Tests/Command/DebugCommandTest.php

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ public function testExecuteWithScheduleWithoutTriggerDoesNotDisplayMessage()
7171
"schedule_name\n".
7272
"-------------\n".
7373
"\n".
74-
" --------- --------- ---------- \n".
75-
" Message Trigger Next Run \n".
76-
" --------- --------- ---------- \n".
74+
" --------- ---------- ---------- \n".
75+
" Trigger Provider Next Run \n".
76+
" --------- ---------- ---------- \n".
7777
"\n", $tester->getDisplay(true));
7878
}
7979

@@ -106,11 +106,11 @@ public function testExecuteWithScheduleWithoutTriggerShowingNoNextRunWithAllOpti
106106
"schedule_name\n".
107107
"-------------\n".
108108
"\n".
109-
" ---------- --------- ---------- \n".
110-
" Message Trigger Next Run \n".
111-
" ---------- --------- ---------- \n".
112-
" stdClass test - \n".
113-
" ---------- --------- ---------- \n".
109+
" --------- ----------------------------------------------------------- ---------- \n".
110+
" Trigger Provider Next Run \n".
111+
" --------- ----------------------------------------------------------- ---------- \n".
112+
" test Symfony\Component\Scheduler\Trigger\StaticMessageProvider - \n".
113+
" --------- ----------------------------------------------------------- ---------- \n".
114114
"\n", $tester->getDisplay(true));
115115
}
116116

@@ -143,11 +143,11 @@ public function testExecuteWithSchedule()
143143
"schedule_name\n".
144144
"-------------\n".
145145
"\n".
146-
" ---------- ------------------------------- --------------------------------- \n".
147-
" Message Trigger Next Run \n".
148-
" ---------- ------------------------------- --------------------------------- \n".
149-
" 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".
150-
" ---------- ------------------------------- --------------------------------- \n".
146+
" ------------------------------- ----------------------------------------------------------- --------------------------------- \n".
147+
" Trigger Provider Next Run \n".
148+
" ------------------------------- ----------------------------------------------------------- --------------------------------- \n".
149+
" 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".
150+
" ------------------------------- ----------------------------------------------------------- --------------------------------- \n".
151151
"\n/", $tester->getDisplay(true));
152152
}
153153
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Scheduler\Tests\Trigger;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Scheduler\Generator\MessageContext;
16+
use Symfony\Component\Scheduler\Trigger\CallbackMessageProvider;
17+
use Symfony\Component\Scheduler\Trigger\TriggerInterface;
18+
19+
class CallbackMessageProviderTest extends TestCase
20+
{
21+
public function testToString()
22+
{
23+
$context = new MessageContext('test', 'test', $this->createMock(TriggerInterface::class), $this->createMock(\DateTimeImmutable::class));
24+
$messageProvider = new CallbackMessageProvider(fn () => []);
25+
$this->assertEquals([], $messageProvider->getMessages($context));
26+
$this->assertEquals('', $messageProvider->getId());
27+
28+
$messageProvider = new CallbackMessageProvider(fn () => [new \stdClass()], '');
29+
$this->assertEquals([new \stdClass()], $messageProvider->getMessages($context));
30+
$this->assertSame('', $messageProvider->getId());
31+
32+
$messageProvider = new CallbackMessageProvider(fn () => yield new \stdClass(), 'foo');
33+
$this->assertInstanceOf(\Generator::class, $messageProvider->getMessages($context));
34+
$this->assertSame('foo', $messageProvider->getId());
35+
}
36+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Scheduler\Trigger;
13+
14+
use Symfony\Component\Scheduler\Generator\MessageContext;
15+
16+
final class CallbackMessageProvider implements MessageProviderInterface
17+
{
18+
private \Closure $callback;
19+
20+
/**
21+
* @param callable(MessageContext): iterable<object> $callback
22+
*/
23+
public function __construct(callable $callback, private string $id = '')
24+
{
25+
$this->callback = $callback(...);
26+
}
27+
28+
public function getMessages(MessageContext $context): iterable
29+
{
30+
return ($this->callback)($context);
31+
}
32+
33+
public function getId(): string
34+
{
35+
return $this->id;
36+
}
37+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Scheduler\Trigger;
13+
14+
use Symfony\Component\Scheduler\Generator\MessageContext;
15+
16+
interface MessageProviderInterface
17+
{
18+
/**
19+
* @return iterable<object>
20+
*/
21+
public function getMessages(MessageContext $context): iterable;
22+
23+
public function getId(): string;
24+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Scheduler\Trigger;
13+
14+
use Symfony\Component\Scheduler\Generator\MessageContext;
15+
16+
final class StaticMessageProvider implements MessageProviderInterface
17+
{
18+
/**
19+
* @param array<object> $messages
20+
*/
21+
public function __construct(
22+
private array $messages,
23+
private string $id = '',
24+
) {
25+
}
26+
27+
public function getMessages(MessageContext $context): iterable
28+
{
29+
return $this->messages;
30+
}
31+
32+
public function getId(): string
33+
{
34+
return $this->id;
35+
}
36+
}

0 commit comments

Comments
 (0)