Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit513e13a

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{ // ...}``````yamlservices: some_other_service: class: # ... tags: - name: scheduler.task trigger: cron expression: '0 9-17 * * *' method: 'someMethod' transports: [async]````bin/console debug:schedule` output:```bashScheduler=========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. tests2. docs3. validate the approach (creating `RecurringMessage`s using DI)Commits-------ed27b20 [Messenger][Scheduler] Add AsCronTask & AsPeriodicTask attributes
2 parents8700763 +ed27b20 commit513e13a

File tree

11 files changed

+279
-3
lines changed

11 files changed

+279
-3
lines changed

‎src/Symfony/Bundle/FrameworkBundle/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',

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@
145145
useSymfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer;
146146
useSymfony\Component\RemoteEvent\RemoteEvent;
147147
useSymfony\Component\Routing\Loader\AnnotationClassLoader;
148+
useSymfony\Component\Scheduler\Attribute\AsCronTask;
149+
useSymfony\Component\Scheduler\Attribute\AsPeriodicTask;
148150
useSymfony\Component\Scheduler\Attribute\AsSchedule;
149151
useSymfony\Component\Scheduler\Messenger\SchedulerTransportFactory;
150152
useSymfony\Component\Security\Core\AuthenticationEvents;
@@ -702,6 +704,26 @@ public function load(array $configs, ContainerBuilder $container)
702704
$container->registerAttributeForAutoconfiguration(AsSchedule::class,staticfunction (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+
staticfunction (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 ($reflectorinstanceof \ReflectionMethod) {
718+
if (isset($tagAttributes['method'])) {
719+
thrownewLogicException(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

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/scheduler.php‎

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

1414
useSymfony\Component\Scheduler\Messenger\SchedulerTransportFactory;
15+
useSymfony\Component\Scheduler\Messenger\ServiceCallMessageHandler;
1516

1617
returnstaticfunction (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+
namespaceSymfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger;
4+
5+
useSymfony\Component\Scheduler\Attribute\AsCronTask;
6+
useSymfony\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+
publicstaticarray$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+
publicfunctionattributesOnMethod(string$a,int$b):void
19+
{
20+
self::$calls[__FUNCTION__][] = [$a,$b];
21+
}
22+
23+
publicfunction__call(string$name,array$arguments)
24+
{
25+
self::$calls[$name][] =$arguments;
26+
}
27+
}

‎src/Symfony/Bundle/FrameworkBundle/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

‎src/Symfony/Component/Messenger/Message/RedispatchMessage.php‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,22 @@
1313

1414
useSymfony\Component\Messenger\Envelope;
1515

16-
finalclass RedispatchMessage
16+
finalclass RedispatchMessageimplements \Stringable
1717
{
1818
/**
19-
* @param object|Envelope $message The message or the message pre-wrapped in an envelope
19+
* @param object|Envelope $envelope The message or the message pre-wrapped in an envelope
2020
* @param string[]|string $transportNames Transport names to be used for the message
2121
*/
2222
publicfunction__construct(
2323
publicreadonlyobject$envelope,
2424
publicreadonlyarray|string$transportNames = [],
2525
) {
2626
}
27+
28+
publicfunction__toString():string
29+
{
30+
$message =$this->envelopeinstanceof Envelope ?$this->envelope->getMessage() :$this->envelope;
31+
32+
returnsprintf('%s via %s',$messageinstanceof \Stringable ? (string)$message :$message::class,implode(',', (array)$this->transportNames));
33+
}
2734
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
namespaceSymfony\Component\Scheduler\Attribute;
13+
14+
/**
15+
* A marker to call a service method from scheduler.
16+
*
17+
* @author valtzu <valtzu@gmail.com>
18+
*/
19+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
20+
class AsCronTask
21+
{
22+
publicfunction__construct(
23+
publicreadonlystring$expression,
24+
publicreadonly ?string$timezone =null,
25+
publicreadonly ?int$jitter =null,
26+
publicreadonlyarray|string|null$arguments =null,
27+
publicreadonlystring$schedule ='default',
28+
publicreadonly ?string$method =null,
29+
publicreadonlyarray|string|null$transports =null,
30+
) {
31+
}
32+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
namespaceSymfony\Component\Scheduler\Attribute;
13+
14+
/**
15+
* A marker to call a service method from scheduler.
16+
*
17+
* @author valtzu <valtzu@gmail.com>
18+
*/
19+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
20+
class AsPeriodicTask
21+
{
22+
publicfunction__construct(
23+
publicreadonlystring|int$frequency,
24+
publicreadonly ?string$from =null,
25+
publicreadonly ?string$until =null,
26+
publicreadonly ?int$jitter =null,
27+
publicreadonlyarray|string|null$arguments =null,
28+
publicreadonlystring$schedule ='default',
29+
publicreadonly ?string$method =null,
30+
publicreadonlyarray|string|null$transports =null,
31+
) {
32+
}
33+
}

‎src/Symfony/Component/Scheduler/DependencyInjection/AddScheduleMessengerPass.php‎

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,17 @@
1111

1212
namespaceSymfony\Component\Scheduler\DependencyInjection;
1313

14+
useSymfony\Component\Console\Messenger\RunCommandMessage;
1415
useSymfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1516
useSymfony\Component\DependencyInjection\ContainerBuilder;
1617
useSymfony\Component\DependencyInjection\Definition;
18+
useSymfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1719
useSymfony\Component\DependencyInjection\Reference;
20+
useSymfony\Component\Messenger\Message\RedispatchMessage;
1821
useSymfony\Component\Messenger\Transport\TransportInterface;
22+
useSymfony\Component\Scheduler\Messenger\ServiceCallMessage;
23+
useSymfony\Component\Scheduler\RecurringMessage;
24+
useSymfony\Component\Scheduler\Schedule;
1925

2026
/**
2127
* @internal
@@ -29,8 +35,69 @@ public function process(ContainerBuilder $container): void
2935
$receivers[$tags[0]['alias']] =true;
3036
}
3137

32-
foreach ($container->findTaggedServiceIds('scheduler.schedule_provider')as$tags) {
38+
$scheduleProviderIds = [];
39+
foreach ($container->findTaggedServiceIds('scheduler.schedule_provider')as$serviceId =>$tags) {
3340
$name =$tags[0]['name'];
41+
$scheduleProviderIds[$name] =$serviceId;
42+
}
43+
44+
$tasksPerSchedule = [];
45+
foreach ($container->findTaggedServiceIds('scheduler.task')as$serviceId =>$tags) {
46+
foreach ($tagsas$tagAttributes) {
47+
$serviceDefinition =$container->getDefinition($serviceId);
48+
$scheduleName =$tagAttributes['schedule'] ??'default';
49+
50+
if ($serviceDefinition->hasTag('console.command')) {
51+
$message =newDefinition(RunCommandMessage::class, [$serviceDefinition->getClass()::getDefaultName().(empty($tagAttributes['arguments']) ?'' :"{$tagAttributes['arguments']}")]);
52+
}else {
53+
$message =newDefinition(ServiceCallMessage::class, [$serviceId,$tagAttributes['method'] ??'__invoke', (array) ($tagAttributes['arguments'] ?? [])]);
54+
}
55+
56+
if ($tagAttributes['transports'] ??null) {
57+
$message =newDefinition(RedispatchMessage::class, [$message,$tagAttributes['transports']]);
58+
}
59+
60+
$taskArguments = [
61+
'$message' =>$message,
62+
] +array_filter(match ($tagAttributes['trigger'] ??thrownewInvalidArgumentException("Tag 'scheduler.task' is missing attribute 'trigger' on service$serviceId.")) {
63+
'every' => [
64+
'$frequency' =>$tagAttributes['frequency'] ??thrownewInvalidArgumentException("Tag 'scheduler.task' is missing attribute 'frequency' on service$serviceId."),
65+
'$from' =>$tagAttributes['from'] ??null,
66+
'$until' =>$tagAttributes['until'] ??null,
67+
],
68+
'cron' => [
69+
'$expression' =>$tagAttributes['expression'] ??thrownewInvalidArgumentException("Tag 'scheduler.task' is missing attribute 'expression' on service$serviceId."),
70+
'$timezone' =>$tagAttributes['timezone'] ??null,
71+
],
72+
},fn ($value) =>null !==$value);
73+
74+
$tasksPerSchedule[$scheduleName][] =$taskDefinition = (newDefinition(RecurringMessage::class))
75+
->setFactory([RecurringMessage::class,$tagAttributes['trigger']])
76+
->setArguments($taskArguments);
77+
78+
if ($tagAttributes['jitter'] ??false) {
79+
$taskDefinition->addMethodCall('withJitter', [$tagAttributes['jitter']],true);
80+
}
81+
}
82+
}
83+
84+
foreach ($tasksPerScheduleas$scheduleName =>$tasks) {
85+
$id ="scheduler.provider.$scheduleName";
86+
$schedule = (newDefinition(Schedule::class))->addMethodCall('add',$tasks);
87+
88+
if (isset($scheduleProviderIds[$scheduleName])) {
89+
$schedule
90+
->setFactory([newReference('.inner'),'getSchedule'])
91+
->setDecoratedService($scheduleProviderIds[$scheduleName]);
92+
}else {
93+
$schedule->addTag('scheduler.schedule_provider', ['name' =>$scheduleName]);
94+
$scheduleProviderIds[$scheduleName] =$id;
95+
}
96+
97+
$container->setDefinition($id,$schedule);
98+
}
99+
100+
foreach (array_keys($scheduleProviderIds)as$name) {
34101
$transportName ='scheduler_'.$name;
35102

36103
// allows to override the default transport registration
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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+
namespaceSymfony\Component\Scheduler\Messenger;
13+
14+
/**
15+
* Represents a service call.
16+
*
17+
* @author valtzu <valtzu@gmail.com>
18+
*/
19+
class ServiceCallMessageimplements \Stringable
20+
{
21+
publicfunction__construct(
22+
privatereadonlystring$serviceId,
23+
privatereadonlystring$method ='__invoke',
24+
privatereadonlyarray$arguments = [],
25+
) {
26+
}
27+
28+
publicfunctiongetServiceId():string
29+
{
30+
return$this->serviceId;
31+
}
32+
33+
publicfunctiongetMethod():string
34+
{
35+
return$this->method;
36+
}
37+
38+
publicfunctiongetArguments():array
39+
{
40+
return$this->arguments;
41+
}
42+
43+
publicfunction__toString():string
44+
{
45+
return"@$this->serviceId".('__invoke' !==$this->method ?"::$this->method" :'');
46+
}
47+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp