0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Symfony ComponentAdvent Calendar 2023

Day 14

指定した間隔で繰り返しジョブ実行、"Scheduler"

Last updated at Posted at 2023-12-13

Symfony Component Advent Calendar 2023の14日目の記事です。

指定した間隔で繰り返しジョブ実行、"Scheduler"

Schedulerは、事前に指定したスケジュールでジョブを実行するコンポーネントです。

インストール

composer require symfony/scheduler

使い方

スケジュールに合わせてジョブを実行するには3つ必要になります。

  • 送信するMessage
  • MessageをハンドリングするMessageHandler
  • スケジュールの設定をするScheduleProvider

Message, MessageHandlerの詳細については去年のMessengerを見ていただくとして、ここでは簡易的なものを記述します。

SomeMessage.php
namespace App\Message;

final class SomeMessage
{
}
SomeMessageHandler.php
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からはさらに便利に使うことができるようになったので、定期実行をこちらに乗り換えるのもありかもしれません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?