Новое в Symfony 6.3 — Компонент Scheduler

Источник: «New in Symfony 6.3: Scheduler Component»
Symfony 6.3 включает новый компонент Scheduler тесно интегрирующийся с компонентом Messenger для создания сообщений, которые должны обрабатываться несколько раз по заранее определённому расписанию.

Распространённой потребностью при использовании компонента Messenger является отправка некоторых сообщений в определённую дату/время. Например, представьте себе проект, который позволяет пользователям подписаться на бесплатную пробную версию. Вы, вероятно, захотите отправить сообщение через 30 дней, чтобы напомнить им об окончании пробного периода.

Вы можете сделать это сегодня, добавив метаданные в сообщения:

$message = new EndOfTrialMessage($userId);
$endOfTrial = (new \DateTimeImmutable())->modify('+30 days');
$message = Envelope::wrap($msg)->with(DelayStamp::delayUntil($endOfTrial));

Это работает, но выглядит запутанным. Вот почему в Symfony 6.3 мы представляем новый компонент Scheduler, который тесно интегрируется с компонентом Messenger. Этот компонент позволяет создавать сообщения, которые должны обрабатываться несколько раз по предварительно заданному расписанию.

Внутри приложения Symfony вы сначала определяете новый провайдер расписания, который создаёт сообщения и определяет, как часто они должны обрабатываться. В этом примере расписание будет отправлять сообщение-напоминание каждые два дня для всех заказов, которые были созданы, но не оплачены:

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

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

Затем создайте PendingOrdersMessage и его обработчик так же, как и любые другие сообщения и обработчики в Messenger. Наконец, запустите потребитель сообщений, связанный с этим расписанием (например, через командную консоль, запущенную в рабочем потоке), чтобы сгенерировать сообщения:

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

И это всё. Внутри каждое расписание трансформируется в транспорт Messenger. Транспорты генерируют сообщения (т.е. они не отправляются), и эти сообщения обрабатываются немедленно (как sync транспорт).

Одной из лучших особенностей этого компонента является то, что он основан на инфраструктуре Messenger. Повторное использование одних и тех же понятий (сообщений, обработчиков, штампов и т.д.) позволяет быстро их освоить. Кроме того, повторное использование того же воркера, что и в Messenger, означает, что вы можете использовать те же ограничения по времени, управлению памятью, обработку сигналов и т.д.

Если вы попытаетесь отправить сообщения слишком быстро (их создание занимает больше времени, чем запланированная частота), некоторые сообщения будут удалены. Чтобы избежать этой проблемы, вы можете сделать расписания с отслеживанием состояния, а также использовать блокировки:

#[AsSchedule('default')]
class DefaultScheduleProvider implements ScheduleProviderInterface
{
public function __construct(
private CacheInterface $cache,
private LockFactory $lockFactory,
) {
}

public function getSchedule(): Schedule
{
$schedule = (new Schedule())
// ...
->stateful($this->cache)
->lock($this->lockFactory->createLock('default-scheduler'))
;
}
}

Частота сообщений может быть определена разными способами:

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

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

# запускать в строго определённое время каждый день
RecurringMessage::every('1 day', $msg, from: '13:47')
# вы также можете передавать полные объекты даты/времени
RecurringMessage::every('1 day', $msg,
from: new \DateTimeImmutable('13:47', new \DateTimeZone('Europe/Paris'))
)

# также определить конец обработки
RecurringMessage::every('1 day', $msg, until: '2023-09-21')

# вы можете использовать выражения Cron
RecurringMessage::cron('0 12 * * 1', $msg) // каждый Понедельник в 12:00
RecurringMessage::cron('#midnight', $msg)
RecurringMessage::cron('#weekly', $msg)

Мы всё ещё пишем документацию для этого нового компонента и надеемся, что они будут готовы вскоре после выпуска Symfony 6.3. Между тем, вы можете бесплатно посмотреть основное выступлениеФабьена на недавней конференции SymfonyLive Paris 2023 (видео на французском, а слайды на английском).

Дополнительные материалы

Предыдущая Статья

Проходя мимо Action классов в Laravel

Следующая Статья

Symfony 6.3 Добавлены новые возможности