Skip to content

Commit fa83bdb

Browse files
committed
trigger unique messages at runtime using MessageProvider
1 parent 73a6b4b commit fa83bdb

File tree

6 files changed

+146
-27
lines changed

6 files changed

+146
-27
lines changed

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

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Symfony\Component\Console\Output\OutputInterface;
2020
use Symfony\Component\Console\Style\SymfonyStyle;
2121
use Symfony\Component\Messenger\Envelope;
22+
use Symfony\Component\Scheduler\Generator\MessageContext;
2223
use Symfony\Component\Scheduler\RecurringMessage;
2324
use Symfony\Component\Scheduler\ScheduleProviderInterface;
2425
use Symfony\Contracts\Service\ServiceProviderInterface;
@@ -96,7 +97,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
9697
}
9798
$io->table(
9899
['Message', 'Trigger', 'Next Run'],
99-
array_filter(array_map(self::renderRecurringMessage(...), $messages, array_fill(0, count($messages), $date), array_fill(0, count($messages), $input->getOption('all')))),
100+
array_filter(array_map(self::renderRecurringMessage(...), $messages, array_fill(0, count($messages), $name), array_fill(0, count($messages), $date), array_fill(0, count($messages), $input->getOption('all')))),
100101
);
101102
}
102103

@@ -106,20 +107,30 @@ protected function execute(InputInterface $input, OutputInterface $output): int
106107
/**
107108
* @return array{0:string,1:string,2:string}|null
108109
*/
109-
private static function renderRecurringMessage(RecurringMessage $recurringMessage, \DateTimeImmutable $date, bool $all): ?array
110+
private static function renderRecurringMessage(RecurringMessage $recurringMessage, string $name, \DateTimeImmutable $date, bool $all): ?array
110111
{
111-
$message = $recurringMessage->getMessage();
112-
$trigger = $recurringMessage->getTrigger();
112+
$trigger = $recurringMessage->getTrigger();
113+
114+
$next = $trigger->getNextRunDate($date)?->format('r') ?? '-';
115+
if ('-' === $next && !$all) {
116+
return null;
117+
}
118+
119+
$messages = $recurringMessage->getMessages(new MessageContext($name, $recurringMessage->getId(), $trigger, $date));
120+
$message = null;
121+
if (is_array($messages) && count($messages) === 1) {
122+
$message = reset($messages);
123+
}
113124

114125
if ($message instanceof Envelope) {
115126
$message = $message->getMessage();
116127
}
117128

118-
$next = $trigger->getNextRunDate($date)?->format('r') ?? '-';
119-
if ('-' === $next && !$all) {
120-
return null;
129+
if ($message) {
130+
$name = $message instanceof \Stringable ? (string) $message : (new \ReflectionClass($message))->getShortName();
131+
} else {
132+
$name = get_debug_type($messages);
121133
}
122-
$name = $message instanceof \Stringable ? (string) $message : (new \ReflectionClass($message))->getShortName();
123134

124135
return [$name, (string) $trigger, $next];
125136
}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ public function getMessages(): \Generator
5353
/** @var RecurringMessage $recurringMessage */
5454
[$time, $index, $recurringMessage] = $heap->extract();
5555
$id = $recurringMessage->getId();
56-
$message = $recurringMessage->getMessage();
5756
$trigger = $recurringMessage->getTrigger();
5857
$yield = true;
5958

@@ -69,7 +68,11 @@ public function getMessages(): \Generator
6968
}
7069

7170
if ($yield) {
72-
yield (new MessageContext($this->name, $id, $trigger, $time, $nextTime)) => $message;
71+
$context = new MessageContext($this->name, $id, $trigger, $time, $nextTime);
72+
foreach ($recurringMessage->getMessages($context) as $message) {
73+
yield $context => $message;
74+
}
75+
7376
$checkpoint->save($time, $index);
7477
}
7578
}

src/Symfony/Component/Scheduler/RecurringMessage.php

Lines changed: 29 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;
16+
use Symfony\Component\Scheduler\Trigger\CallbackMessageProvider;
1517
use Symfony\Component\Scheduler\Trigger\CronExpressionTrigger;
1618
use Symfony\Component\Scheduler\Trigger\JitterTrigger;
19+
use Symfony\Component\Scheduler\Trigger\MessageProvider;
1720
use Symfony\Component\Scheduler\Trigger\PeriodicalTrigger;
1821
use Symfony\Component\Scheduler\Trigger\TriggerInterface;
1922

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

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

@@ -42,30 +45,40 @@ private function __construct(
4245
*/
4346
public static function every(string|int|\DateInterval $frequency, object $message, string|\DateTimeImmutable $from = new \DateTimeImmutable(), string|\DateTimeImmutable $until = new \DateTimeImmutable('3000-01-01')): self
4447
{
45-
return new self(new PeriodicalTrigger($frequency, $from, $until), $message);
48+
return self::trigger(new PeriodicalTrigger($frequency, $from, $until), $message);
4649
}
4750

4851
public static function cron(string $expression, object $message, \DateTimeZone|string $timezone = null): self
4952
{
5053
if (!str_contains($expression, '#')) {
51-
return new self(CronExpressionTrigger::fromSpec($expression, null, $timezone), $message);
54+
return self::trigger(CronExpressionTrigger::fromSpec($expression, null, $timezone), $message);
5255
}
5356

5457
if (!$message instanceof \Stringable) {
5558
throw new InvalidArgumentException('A message must be stringable to use "hashed" cron expressions.');
5659
}
5760

58-
return new self(CronExpressionTrigger::fromSpec($expression, (string) $message, $timezone), $message);
61+
return self::trigger(CronExpressionTrigger::fromSpec($expression, (string) $message, $timezone), $message);
5962
}
6063

6164
public static function trigger(TriggerInterface $trigger, object $message): self
6265
{
63-
return new self($trigger, $message);
66+
if ($message instanceof MessageProvider) {
67+
return new self($trigger, $message);
68+
}
69+
70+
try {
71+
$description = $message instanceof \Stringable ? (string) $message : serialize($message);
72+
} catch (\Exception) {
73+
$description = $message::class;
74+
}
75+
76+
return new self($trigger, new CallbackMessageProvider(static fn (): array => [$message], $description));
6477
}
6578

6679
public function withJitter(int $maxSeconds = 60): self
6780
{
68-
return new self(new JitterTrigger($this->trigger, $maxSeconds), $this->message);
81+
return new self(new JitterTrigger($this->trigger, $maxSeconds), $this->provider);
6982
}
7083

7184
/**
@@ -77,23 +90,22 @@ public function getId(): string
7790
return $this->id;
7891
}
7992

80-
try {
81-
$message = $this->message instanceof \Stringable ? (string) $this->message : serialize($this->message);
82-
} catch (\Exception) {
83-
$message = '';
84-
}
85-
8693
return $this->id = hash('crc32c', implode('', [
87-
$this->message::class,
88-
$message,
94+
$this->provider::class,
95+
$this->provider instanceof \Stringable ? (string) $this->provider : '',
8996
$this->trigger::class,
9097
(string) $this->trigger,
9198
]));
9299
}
93100

94-
public function getMessage(): object
101+
public function getMessages(MessageContext $context): iterable
102+
{
103+
return $this->provider->getMessages($context);
104+
}
105+
106+
public function getProvider(): MessageProvider
95107
{
96-
return $this->message;
108+
return $this->provider;
97109
}
98110

99111
public function getTrigger(): TriggerInterface
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 <fabien@symfony.com>
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 DateTimeImmutable;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\Component\Scheduler\Generator\MessageContext;
17+
use Symfony\Component\Scheduler\Trigger\CallbackMessageProvider;
18+
use Symfony\Component\Scheduler\Trigger\TriggerInterface;
19+
20+
class CallbackMessageProviderTest extends TestCase
21+
{
22+
public function testToString()
23+
{
24+
$context = new MessageContext('test', 'test', $this->createMock(TriggerInterface::class), $this->createMock(DateTimeImmutable::class));
25+
$messageProvider = new CallbackMessageProvider(fn () => []);
26+
$this->assertEquals([], $messageProvider->getMessages($context));
27+
$this->assertMatchesRegularExpression('/^[\da-f]{32}$/', (string) $messageProvider);
28+
29+
$messageProvider = new CallbackMessageProvider(fn () => [new \stdClass()], '');
30+
$this->assertEquals([new \stdClass()], $messageProvider->getMessages($context));
31+
$this->assertSame('', (string) $messageProvider);
32+
33+
$messageProvider = new CallbackMessageProvider(fn () => yield new \stdClass(), 'foo');
34+
$this->assertInstanceOf(\Generator::class, $messageProvider->getMessages($context));
35+
$this->assertSame('foo', (string) $messageProvider);
36+
}
37+
}
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 <fabien@symfony.com>
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 MessageProvider, \Stringable
17+
{
18+
private \Closure $callback;
19+
20+
private ?string $description;
21+
22+
public function __construct(callable $callback, string $description = null)
23+
{
24+
$this->callback = $callback(...);
25+
$this->description = $description ?? spl_object_hash($this->callback);
26+
}
27+
28+
public function getMessages(MessageContext $context): iterable
29+
{
30+
return ($this->callback)($context);
31+
}
32+
33+
public function __toString()
34+
{
35+
return $this->description;
36+
}
37+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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 MessageProvider
17+
{
18+
public function getMessages(MessageContext $context): iterable;
19+
}

0 commit comments

Comments
 (0)