The scheduler component manages task scheduling within your PHP application, like running a task each night at 3 AM, every two weeks except for holidays or any other custom schedule you might need.
This component is useful to:
From Symfony Scheduler documentation
$ composer require symfony/scheduler
$ composer require symfony/scheduler
OR
$ composer require scheduler
$ composer require scheduler
$ bin/console messenger:consume scheduler_default [OK] Consuming messages from transport "scheduler_default". // The worker will automatically exit once it has received a stop signal via // the messenger:stop-workers command. // Quit the worker with CONTROL-C. // Re-run the command with a -vv option to see logs about consumed messages.
$ bin/console messenger:consume scheduler_default [OK] Consuming messages from transport "scheduler_default". // The worker will automatically exit once it has received a stop signal via // the messenger:stop-workers command. // Quit the worker with CONTROL-C. // Re-run the command with a -vv option to see logs about consumed messages.
// src/Command/ClockCommand.php #[AsCommand(name: 'app:clock')] #[AsPeriodicTask(frequency: '1 minute', from: '2024-05-22', until: '2024-12-31')] // #[AsCronTask('* * * * *')] (require dragonmantank/cron-expression) final class ClockCommand extends Command { public function __construct( #[Autowire(env: 'CLOCK_MUSIC')] private readonly string $clockMusic ) { parent::__construct(); } protected function execute(InputInterface $input, OutputInterface $output): int { $command = sprintf('mplayer %s', $this->clockMusic); shell_exec($command); return Command::SUCCESS; } }
// src/Command/ClockCommand.php #[AsCommand(name: 'app:clock')] #[AsPeriodicTask(frequency: '1 minute', from: '2024-05-22', until: '2024-12-31')] // #[AsCronTask('* * * * *')] (require dragonmantank/cron-expression) final class ClockCommand extends Command { public function __construct( #[Autowire(env: 'CLOCK_MUSIC')] private readonly string $clockMusic ) { parent::__construct(); } protected function execute(InputInterface $input, OutputInterface $output): int { $command = sprintf('mplayer %s', $this->clockMusic); shell_exec($command); return Command::SUCCESS; } }
// src/Message/PlayMusicMessage.php final readonly class PlayMusicMessage { public function __construct( public string $clockMusic ) { } } // src/MessageHandler/PlayMusicMessageHandler.php #[AsMessageHandler] final class PlayMusicMessageHandler { public function __invoke(PlayMusicMessage $message): void { $command = sprintf('mplayer %s', $message->clockMusic); shell_exec($command); } } // src/Scheduler/ClockSchedule.php #[AsSchedule] final class ClockSchedule implements ScheduleProviderInterface { public function getSchedule(): Schedule { $from = new \DateTimeImmutable('07:30', new \DateTimeZone('Europe/Paris')); return (new Schedule()) ->add(RecurringMessage::every('1 day', new PlayMusicMessage($clockMusic), $from)) ; } }
// src/Message/PlayMusicMessage.php final readonly class PlayMusicMessage { public function __construct( public string $clockMusic ) { } } // src/MessageHandler/PlayMusicMessageHandler.php #[AsMessageHandler] final class PlayMusicMessageHandler { public function __invoke(PlayMusicMessage $message): void { $command = sprintf('mplayer %s', $message->clockMusic); shell_exec($command); } } // src/Scheduler/ClockSchedule.php #[AsSchedule] final class ClockSchedule implements ScheduleProviderInterface { public function getSchedule(): Schedule { $from = new \DateTimeImmutable('07:30', new \DateTimeZone('Europe/Paris')); return (new Schedule()) ->add(RecurringMessage::every('1 day', new PlayMusicMessage($clockMusic), $from)) ; } }
// src/Scheduler/Trigger/ExcludePublicHolidaysTrigger.php final class ExcludePublicHolidaysTrigger implements TriggerInterface { public function __construct( private readonly TriggerInterface $inner, // ... ) { } public function __toString(): string { return sprintf('%s (except public holidays)', $this->inner); } private function isPublicHoliday(\DateTimeImmutable $timestamp): bool { // check if it's a public holiday } public function getNextRunDate(\DateTimeImmutable $run): ?\DateTimeImmutable { $nextRun = $this->inner->getNextRunDate($run); if (null === $nextRun) { return null; } // loop until you get the next run date that is not a public holiday while ($this->isPublicHoliday($nextRun)) { $nextRun = $this->inner->getNextRunDate($nextRun); } return $nextRun; } } // src/Scheduler/ClockSchedule.php #[AsSchedule] final class ClockSchedule implements ScheduleProviderInterface { public function getSchedule(): Schedule { $from = new \DateTimeImmutable('07:30', new \DateTimeZone('Europe/Paris')); return $this->schedule ??= (new Schedule()) ->with( RecurringMessage::trigger( new ExcludePublicHolidaysTrigger( new PeriodicalTrigger('1 day', $from), // ... ), new PlayMusicMessage($clockMusic) ), ) ; } }
// src/Scheduler/Trigger/ExcludePublicHolidaysTrigger.php final class ExcludePublicHolidaysTrigger implements TriggerInterface { public function __construct( private readonly TriggerInterface $inner, // ... ) { } public function __toString(): string { return sprintf('%s (except public holidays)', $this->inner); } private function isPublicHoliday(\DateTimeImmutable $timestamp): bool { // check if it's a public holiday } public function getNextRunDate(\DateTimeImmutable $run): ?\DateTimeImmutable { $nextRun = $this->inner->getNextRunDate($run); if (null === $nextRun) { return null; } // loop until you get the next run date that is not a public holiday while ($this->isPublicHoliday($nextRun)) { $nextRun = $this->inner->getNextRunDate($nextRun); } return $nextRun; } } // src/Scheduler/ClockSchedule.php #[AsSchedule] final class ClockSchedule implements ScheduleProviderInterface { public function getSchedule(): Schedule { $from = new \DateTimeImmutable('07:30', new \DateTimeZone('Europe/Paris')); return $this->schedule ??= (new Schedule()) ->with( RecurringMessage::trigger( new ExcludePublicHolidaysTrigger( new PeriodicalTrigger('1 day', $from), // ... ), new PlayMusicMessage($clockMusic) ), ) ; } }
$ faketime '2024-05-22 18:30:30' bin/console debug:scheduler Scheduler ========= default ------- ---------------- ------------------------------------------------------------------- --------------------------------- Trigger Provider Next Run ---------------- ------------------------------------------------------------------- --------------------------------- every 1 minute Symfony\Component\Console\Messenger\RunCommandMessage (app:clock) Wed, 22 May 2024 18:31:00 +0200 ---------------- ------------------------------------------------------------------- ---------------------------------
$ faketime '2024-05-22 18:30:30' bin/console debug:scheduler Scheduler ========= default ------- ---------------- ------------------------------------------------------------------- --------------------------------- Trigger Provider Next Run ---------------- ------------------------------------------------------------------- --------------------------------- every 1 minute Symfony\Component\Console\Messenger\RunCommandMessage (app:clock) Wed, 22 May 2024 18:31:00 +0200 ---------------- ------------------------------------------------------------------- ---------------------------------
$ faketime '2024-05-19 09:00:00' bin/console debug:scheduler Scheduler ========= default ------- ------------- ------------------------------ --------------------------------- Trigger Provider Next Run ------------- ------------------------------ --------------------------------- every 1 day App\Message\PlayMusicMessage Mon, 20 May 2024 07:30:00 +0200 ------------- ------------------------------ ---------------------------------
$ faketime '2024-05-19 09:00:00' bin/console debug:scheduler Scheduler ========= default ------- ------------- ------------------------------ --------------------------------- Trigger Provider Next Run ------------- ------------------------------ --------------------------------- every 1 day App\Message\PlayMusicMessage Mon, 20 May 2024 07:30:00 +0200 ------------- ------------------------------ ---------------------------------
$ faketime '2024-05-19 09:00:00' bin/console debug:scheduler Scheduler ========= default ------- -------------------------------------- ------------------------------ --------------------------------- Trigger Provider Next Run -------------------------------------- ------------------------------ --------------------------------- every 1 day (except public holidays) App\Message\PlayMusicMessage Tue, 21 May 2024 07:30:00 +0200 -------------------------------------- ------------------------------ ---------------------------------
$ faketime '2024-05-19 09:00:00' bin/console debug:scheduler Scheduler ========= default ------- -------------------------------------- ------------------------------ --------------------------------- Trigger Provider Next Run -------------------------------------- ------------------------------ --------------------------------- every 1 day (except public holidays) App\Message\PlayMusicMessage Tue, 21 May 2024 07:30:00 +0200 -------------------------------------- ------------------------------ ---------------------------------
RecurringMessage::every('1 second', new Message()); RecurringMessage::every('15 days', new Message()); # relative format RecurringMessage::every('next friday', new Message()); RecurringMessage::every('first Monday of next month', new Message()); # run at a very specific time every day RecurringMessage::every('1 day', new Message(), from: '14:42'); # you can pass full date/time objects too RecurringMessage::every('1 day', new Message(), from: new \DateTimeImmutable('14:42', new \DateTimeZone('Europe/Paris'))); # define the end of the handling too RecurringMessage::every('1 day', new Message(), until: '2023-09-21'); # you can even use cron expressions RecurringMessage::cron('* * * * *', new Message()); // every minute RecurringMessage::cron('42 14 * * 2', new Message()); // every Tuesday at 14:42
RecurringMessage::every('1 second', new Message()); RecurringMessage::every('15 days', new Message()); # relative format RecurringMessage::every('next friday', new Message()); RecurringMessage::every('first Monday of next month', new Message()); # run at a very specific time every day RecurringMessage::every('1 day', new Message(), from: '14:42'); # you can pass full date/time objects too RecurringMessage::every('1 day', new Message(), from: new \DateTimeImmutable('14:42', new \DateTimeZone('Europe/Paris'))); # define the end of the handling too RecurringMessage::every('1 day', new Message(), until: '2023-09-21'); # you can even use cron expressions RecurringMessage::cron('* * * * *', new Message()); // every minute RecurringMessage::cron('42 14 * * 2', new Message()); // every Tuesday at 14:42
0 2 * * *
0 2 * * *
30 4 * * *
30 4 * * *
4 2 * * *
4 2 * * *
38 4 * * *
38 4 * * *
public function getNextRunDate(\DateTimeImmutable $run): ?\DateTimeImmutable { if (!$nextRun = $this->trigger->getNextRunDate($run)) { return null; } return $nextRun->add(new \DateInterval(sprintf('PT%sS', random_int(0, $this->maxSeconds)))); }
public function getNextRunDate(\DateTimeImmutable $run): ?\DateTimeImmutable { if (!$nextRun = $this->trigger->getNextRunDate($run)) { return null; } return $nextRun->add(new \DateInterval(sprintf('PT%sS', random_int(0, $this->maxSeconds)))); }