Skip to content

Commit b13212d

Browse files
committed
feature #51525 [Messenger][Scheduler] Add AsCronTask & AsPeriodicTask attributes (valtzu)
This PR was squashed before being merged into the 6.4 branch. Discussion ---------- [Messenger][Scheduler] Add AsCronTask & AsPeriodicTask attributes | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | Fix #51432 | License | MIT | Doc PR | symfony/symfony-docs#... _// todo_ Simplify scheduler usage by allowing to declare an attribute `AsCronTask` / `AsPeriodicTask` on any registered & autoconfigured service. Example usage: ```php #[AsPeriodicTask(frequency: 5, jitter: 1, arguments: ['Hello from periodic trigger'])] class BusinessLogic { public function __invoke(string $message): void { echo "$message\n"; } } ``` ```php #[AsCronTask('* * * * *', arguments: 'hello -v')] #[AsCommand('app:do-stuff')] class DoStuffCommand extends Command { // ... } ``` ```yaml services: some_other_service: class: # ... tags: - name: scheduler.task trigger: cron expression: '0 9-17 * * *' method: 'someMethod' transports: [async] ``` `bin/console debug:schedule` output: ```bash Scheduler ========= default ------- ------------------------------------------- ---------------------------------------- --------------------------------- Message Trigger Next Run ------------------------------------------- ---------------------------------------- --------------------------------- `@App`\BusinessLogic every 5 seconds with 0-1 second jitter Sat, 02 Sep 2023 10:55:36 +0000 app:do-stuff hello -v * * * * * Sat, 02 Sep 2023 13:56:00 +0300 `@some_other_service`::someMethod via async 0 9-17 * * * Sat, 02 Sep 2023 14:00:00 +0300 ------------------------------------------- ---------------------------------------- --------------------------------- ``` And then run `bin/console messenger:consume scheduler_default` to run the scheduler, like the usual. --- **To-do (help needed):** 1. tests 2. docs 3. validate the approach (creating `RecurringMessage`s using DI) Commits ------- ed27b20c1a [Messenger][Scheduler] Add AsCronTask & AsPeriodicTask attributes
2 parents 1155b08 + 13b0060 commit b13212d

File tree

5 files changed

+59
-0
lines changed

5 files changed

+59
-0
lines changed

DependencyInjection/Compiler/UnusedTagsPass.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class UnusedTagsPass implements CompilerPassInterface
8484
'routing.loader',
8585
'routing.route_loader',
8686
'scheduler.schedule_provider',
87+
'scheduler.task',
8788
'security.authenticator.login_linker',
8889
'security.expression_language_provider',
8990
'security.remember_me_handler',

DependencyInjection/FrameworkExtension.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@
145145
use Symfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer;
146146
use Symfony\Component\RemoteEvent\RemoteEvent;
147147
use Symfony\Component\Routing\Loader\AnnotationClassLoader;
148+
use Symfony\Component\Scheduler\Attribute\AsCronTask;
149+
use Symfony\Component\Scheduler\Attribute\AsPeriodicTask;
148150
use Symfony\Component\Scheduler\Attribute\AsSchedule;
149151
use Symfony\Component\Scheduler\Messenger\SchedulerTransportFactory;
150152
use Symfony\Component\Security\Core\AuthenticationEvents;
@@ -702,6 +704,26 @@ public function load(array $configs, ContainerBuilder $container)
702704
$container->registerAttributeForAutoconfiguration(AsSchedule::class, static function (ChildDefinition $definition, AsSchedule $attribute): void {
703705
$definition->addTag('scheduler.schedule_provider', ['name' => $attribute->name]);
704706
});
707+
foreach ([AsPeriodicTask::class, AsCronTask::class] as $taskAttributeClass) {
708+
$container->registerAttributeForAutoconfiguration(
709+
$taskAttributeClass,
710+
static function (ChildDefinition $definition, AsPeriodicTask|AsCronTask $attribute, \ReflectionClass|\ReflectionMethod $reflector): void {
711+
$tagAttributes = get_object_vars($attribute) + [
712+
'trigger' => match ($attribute::class) {
713+
AsPeriodicTask::class => 'every',
714+
AsCronTask::class => 'cron',
715+
},
716+
];
717+
if ($reflector instanceof \ReflectionMethod) {
718+
if (isset($tagAttributes['method'])) {
719+
throw new LogicException(sprintf('%s attribute cannot declare a method on "%s::%s()".', $attribute::class, $reflector->class, $reflector->name));
720+
}
721+
$tagAttributes['method'] = $reflector->getName();
722+
}
723+
$definition->addTag('scheduler.task', $tagAttributes);
724+
}
725+
);
726+
}
705727

706728
if (!$container->getParameter('kernel.debug')) {
707729
// remove tagged iterator argument for resource checkers

Resources/config/scheduler.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,15 @@
1212
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
1313

1414
use Symfony\Component\Scheduler\Messenger\SchedulerTransportFactory;
15+
use Symfony\Component\Scheduler\Messenger\ServiceCallMessageHandler;
1516

1617
return static function (ContainerConfigurator $container) {
1718
$container->services()
19+
->set('scheduler.messenger.service_call_message_handler', ServiceCallMessageHandler::class)
20+
->args([
21+
tagged_locator('scheduler.task'),
22+
])
23+
->tag('messenger.message_handler')
1824
->set('scheduler.messenger_transport_factory', SchedulerTransportFactory::class)
1925
->args([
2026
tagged_locator('scheduler.schedule_provider', 'name'),
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger;
4+
5+
use Symfony\Component\Scheduler\Attribute\AsCronTask;
6+
use Symfony\Component\Scheduler\Attribute\AsPeriodicTask;
7+
8+
#[AsCronTask(expression: '* * * * *', arguments: [1], schedule: 'dummy')]
9+
#[AsCronTask(expression: '0 * * * *', timezone: 'Europe/Berlin', arguments: ['2'], schedule: 'dummy', method: 'method2')]
10+
#[AsPeriodicTask(frequency: 5, arguments: [3], schedule: 'dummy')]
11+
#[AsPeriodicTask(frequency: 'every day', from: '00:00:00', jitter: 60, arguments: ['4'], schedule: 'dummy', method: 'method4')]
12+
class DummyTask
13+
{
14+
public static array $calls = [];
15+
16+
#[AsPeriodicTask(frequency: 'every hour', from: '09:00:00', until: '17:00:00', arguments: ['b' => 6, 'a' => '5'], schedule: 'dummy')]
17+
#[AsCronTask(expression: '0 0 * * *', arguments: ['7', 8], schedule: 'dummy')]
18+
public function attributesOnMethod(string $a, int $b): void
19+
{
20+
self::$calls[__FUNCTION__][] = [$a, $b];
21+
}
22+
23+
public function __call(string $name, array $arguments)
24+
{
25+
self::$calls[$name][] = $arguments;
26+
}
27+
}

Tests/Functional/app/Scheduler/config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ services:
1010
Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummySchedule:
1111
autoconfigure: true
1212

13+
Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyTask:
14+
autoconfigure: true
15+
1316
clock:
1417
synthetic: true
1518

0 commit comments

Comments
 (0)