Symfony Component Advent Calendar 2023の14日目の記事です。
指定した間隔で繰り返しジョブ実行、"Scheduler"
Schedulerは、事前に指定したスケジュールでジョブを実行するコンポーネントです。
インストール
composer require symfony/scheduler
使い方
スケジュールに合わせてジョブを実行するには3つ必要になります。
- 送信する
Message
-
Message
をハンドリングするMessageHandler
- スケジュールの設定をする
ScheduleProvider
Message, MessageHandlerの詳細については去年のMessengerを見ていただくとして、ここでは簡易的なものを記述します。
namespace App\Message;
final class SomeMessage
{
}
namespace App\MessageHandler;
use App\Message\SomeMessage;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
final class SomeMessageHandler
{
public function __construct(private readonly ClockInterface $clock)
{
}
public function __invoke(SomeMessage $message)
{
$now = $this->clock->now()->format('Y-m-d H:i:s');
echo ("{$now}\n"); // 定期的に実行してるかわかりやすいように現在の時間を出力
}
}
namespace App\ScheduleProvider;
use App\Message\SomeMessage;
use Symfony\Component\Scheduler\Attribute\AsSchedule;
use Symfony\Component\Scheduler\RecurringMessage;
use Symfony\Component\Scheduler\Schedule;
use Symfony\Component\Scheduler\ScheduleProviderInterface;
#[AsSchedule('sample')]
class SampleScheduleProvider implements ScheduleProviderInterface
{
public function __construct()
{
}
public function getSchedule(): Schedule
{
$schedule = new Schedule();
$schedule->add(
RecurringMessage::every('10 seconds', new SomeMessage())
);
return $schedule;
}
}
3つのクラスを作成し、Messengerを起動します。ここも詳しくはMessanger
の記事を見ていただくとして、ポイントは引数にスケジュール名を渡します。SampleScheduleProvider
に#[AsSchedule]
アトリビュートをつけていますが、そこに指定した名前をscheduler_{指定した名前}
といったようにscheduler_
を頭につけて実行します。#[AsSchedule]
で名前を指定しない場合はdefault
になるので、scheduler_default
となります。
bin/console messenger:consume -v scheduler_sample
上記を実行すると
2023-12-14 10:00:08
2023-12-14 10:00:18
2023-12-14 10:00:28
といった結果になり、10秒間隔でジョブが実行されていることがわかります。ScheduleProvider
もオートワイヤリングが使えるので、必要なものを取得してMessageに渡すことができます。
定期的に実行されるMessageHandlerには常に同じMessageオブジェクトが渡されます。
また、RecurringMessage::cron("クロンタブのような設定")
を行えば、クロンタブと同じ感覚て定期的に実行させることができます。
cron()にする場合は別途dragonmantank/cron-expression
が必要です。
composer require dragonmantank/cron-expression
Symfony6.4以降の新しい使い方
というのがSymfony6.3の話でした。Symfony6.4から、こんなやり方でなくても実行できるようになってました。Message, MessageHandler, ScheduleProviderが必要なくなりました。代わりに以下を持ったクラスを用意します。
-
#[AsPeriodicTask()]
もしくは#[AsCronTask()]
アトリビュート -
__invoke()
メソッド、もしくはCommand
を継承
例えば、
use Symfony\Component\Scheduler\Attribute\AsPeriodicTask;
#[AsPeriodicTask(frequency: 10, arguments: ['From PeriodTask.'])]
class PeriodTask
{
public function __construct(private readonly ClockInterface $clock)
{
}
public function __invoke(string $arg)
{
$now = $this->clock->now()->format('Y-m-d H:i:s');
echo "{$now}: {$arg}\n";
}
}
このクラスを作って、
bin/console messenger:consume -v scheduler_default
を実行すれば
2023-12-14 10:00:10: From PeriodTask.
2023-12-14 10:00:20: From PeriodTask.
2023-12-14 10:00:30: From PeriodTask.
と、先のどの例と同じように定期的に実行することができます。アトリビュートでarguments
を指定すれば、__invoke
に引数として値を渡すことができます。
なお、コマンドの場合はこのようにarguments
に文字列で引数を渡します。
#[AsCommand(name: 'app:schedule')]
#[AsCronTask('* * * * *', arguments: 'Symfony --option=test')]
class ScheduleCommand extends Command
{
public function __construct(private readonly ClockInterface $clock)
{
parent::__construct();
}
public function configure()
{
$this->addArgument('name')
->addOption('option', mode: InputOption::VALUE_OPTIONAL);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$name = $input->getArgument('name');
$option = $input->getOption('option');
$now = $this->clock->now()->format('Y-m-d H:i:s');
echo "{$now}: {$name} {$option}\n";
return Command::SUCCESS;
}
}
実行結果
2023-12-10 13:56:00: Symfony test
まとめ
今回はSchehduler
を紹介しました。Symfony6.4からはさらに便利に使うことができるようになったので、定期実行をこちらに乗り換えるのもありかもしれません。