Symfony Scheduler provides a viable replacement to hook_cron
wherein messages can be scheduled for dispatch at a predefined interval. Messages are dispatched the moment they are scheduled, and there is no message duplication, making tasks more reliable and efficient.
This post is part 4 in a series about Symfony Messenger.
- Introducing Symfony Messenger integrations with Drupal
- Symfony Messenger’ message and message handlers, and comparison with @QueueWorker
- Real-time: Symfony Messenger’ Consume command and prioritised messages
- Automatic message scheduling and replacing hook_cron
- Adding real-time processing to QueueWorker plugins
- Making Symfony Mailer asynchronous: integration with Symfony Messenger
- Displaying notifications when Symfony Messenger messages are processed
- Future of Symfony Messenger in Drupal
With this, the sm
worker provided by the SM project, the Symfony Messenger integration with Drupal, can be solely relied on. Rather than legacy runners such as Drupal web cron, request termination cron (automated_cron.module
), Drush cron, and Ultimate Cron.
Scheduler functionality is implemented by the Symfony Scheduler component. The Drupal integration is provided by the SM Scheduler module
Schedule provider
Create a message and message handler as usual, then create a Schedule Provider:
<?php
declare(strict_types = 1);
namespace Drupal\my_module\Messenger;
use Symfony\Component\Scheduler\Attribute\AsSchedule;
use Symfony\Component\Scheduler\RecurringMessage;
use Symfony\Component\Scheduler\Schedule;
use Symfony\Component\Scheduler\ScheduleProviderInterface;
#[AsSchedule('my_scheduler_name')]
final class MyScheduleProvider implements ScheduleProviderInterface {
/**
* {@inheritdoc}
*/
public function getSchedule(): Schedule {
return (new Schedule())->add(
RecurringMessage::every('5 minutes', new MyMessage()),
);
}
}
A schedule provider is:
- a class at the
Messenger\
namespace - with a
#[AsScheduler]
class attribute - implementing \
Symfony\Component\Scheduler\ScheduleProviderInterface
- implements an
getSchedule
method. This method returns a message instance and the schedule frequency.
For dependency injection, schedule providers have autowiring enabled.
What would normally be the contents of a hook_cron
hook would instead be added to the message handler. The message itself does not need to store any meaningful data.
Instead of intervals via RecurringMessage::every(...)
, crontab syntax can be used:
\Symfony\Component\Scheduler\RecurringMessage::cron('*/5 * * * *', new MyMessage());
Running the worker
Lastly, schedulers must be run via the consume command with a dedicated transport. The transport name is the schedule ID prefixed by scheduler_
. For example, given the scheduler ID my_scheduler_name
from above, the transport name will be scheduler_my_scheduler_name
.
The command finally becomes: sm messenger:consume scheduler_my_scheduler_name
.
Timing
Messages will be dispatched the moment their interval arrives. Normally intervals begin when the worker is initiated, however you can set a point in time to begin interval computation using the \Symfony\Component\Scheduler\RecurringMessage::every
$from
parameter.
The worker must be running at the time when a message is scheduled to be sent. The transport won't retroactively catch-up with messages not dispatched during the time it wasn't running.
The next post outlines how to intercept legacy Drupal @QueueWorker
items and insert them into the message bus.