New in Symfony 6.3: Scheduler Component



Contributed by Sergey Rabochiy
and Fabien Potencier
in #47112.

A common need for a project is to do tasks on a regular basis like sending end of trial emails. In the Unix world,
you can use "cron" for such recurring tasks. Coupled with Symfony commands, you have everything you need.
But crons come with limitations. That's why in Symfony 6.3 we're introducing
a new Scheduler component. This component allows you to trigger messages that should be sent
on a predefined schedule. It reuses the Messenger concepts you're already familiar with.
Inside a Symfony application, you first define a new schedule provider that
creates the messages and defines how often they should be sent. In this
example, the schedule will send a message every 2 days that will signal your application
to do something about orders that have been created but not paid yet:

use Symfony\Component\Scheduler\Attribute\AsSchedule;
use Symfony\Component\Scheduler\RecurringMessage;
// ...

#[AsSchedule('default')]
class DefaultScheduleProvider implements ScheduleProviderInterface
{
public function getSchedule(): Schedule
{
return (new Schedule())->add(
RecurringMessage::every('2 days', new PendingOrdersMessage())
);
}
}

Then, create the PendingOrdersMessage and its handler, in the same way as
any other messages and handlers in Messenger. Finally, run the message
consumer associated to this schedule (e.g. via a command console run in a
worker) to generate the messages:

# the '_default' suffix in the scheduler name is
# the value you defined before in the #[AsSchedule] attribute
$ symfony console messenger:consume -v scheduler_default

And that's all. Internally, each schedule is transformed into a Messenger
transport. Transports generate the messages (i.e. they are not dispatched) and
those messages are handled immediately (like the sync transport).
One of the best features of this component is that it's based on Messenger infrastructure.
Reusing the same concepts (messages, handlers, stamps, etc.) allows you to learn
it fast. Besides, reusing the same worker as Messenger means that you can use
the same time limits, memory management, signal handling, etc.
The message frequency can be defined in many different ways:

RecurringMessage::every('10 seconds', $msg)
RecurringMessage::every('1 day', $msg)

RecurringMessage::every('next tuesday', $msg)
RecurringMessage::every('first monday of next month', $msg)

# run at a very specific time every day
RecurringMessage::every('1 day', $msg, from: '13:47')
# you can pass full date/time objects too
RecurringMessage::every('1 day', $msg,
from: new \DateTimeImmutable('13:47', new \DateTimeZone('Europe/Paris'))
)

# define the end of the handling too
RecurringMessage::every('1 day', $msg, until: '2023-09-21')

# you can even use Cron expressions
RecurringMessage::cron('0 12 * * 1', $msg) // every Monday at 12:00
RecurringMessage::cron('#midnight', $msg)
RecurringMessage::cron('#weekly', $msg)

We're still writing the docs for this new component and we hope to have them
ready soon after the Symfony 6.3 release. Meanwhile, you can watch for free the keynote
that Fabien delivered during the recent SymfonyLive Paris 2023 conference (the
video is in French and the slides are in English).

Sponsor the Symfony project.