You can define your own task types. Tasks consist of a task object that extends
Task
and a runner that implements
TaskRunner
. The runner is responsible
for running the command and returning a Result
.
The runner must be a service with the schedule.task_runner
tag (this is autoconfigurable).
Runners must implement the supports()
method which should return true when passed the task
it handles.
As an example, let's create a Task that sends a Message to your MessageBus (symfony/messenger
required).
NOTE: There is now a MessageTask in core.
First, let's create the task:
// src/Schedule/MessageTask.php
use Zenstruck\ScheduleBundle\Schedule\Task;
class MessageTask extends Task
{
private $message;
public function __construct(object $message)
{
$this->message = $message;
// be sure to call the parent constructor with a default description
parent::__construct(get_class($message));
}
public function getMessage(): object
{
return $this->message;
}
}
Next, let's create the runner:
// src/Schedule/MessageTaskRunner.php
use Symfony\Component\Messenger\MessageBusInterface;
use Zenstruck\ScheduleBundle\Schedule\Task;
use Zenstruck\ScheduleBundle\Schedule\Task\Result;
use Zenstruck\ScheduleBundle\Schedule\Task\TaskRunner;
class MessageTaskRunner implements TaskRunner
{
private $bus;
public function __construct(MessageBusInterface $bus)
{
$this->bus = $bus;
}
/**
* @param MessageTask $task
*/
public function __invoke(Task $task): Result
{
$this->bus->dispatch($task->getMessage());
return Result::successful($task);
}
public function supports(Task $task) : bool
{
return $task instanceof MessageTask;
}
}
Finally, use this task in your schedule:
use App\Message\DoSomething;
use App\Schedule\MessageTask;
/* @var $schedule \Zenstruck\ScheduleBundle\Schedule */
$schedule->add(new MessageTask(new DoSomething()))
->daily()
->at('13:30')
;
The primary way of hooking into schedule/task events is with extensions. Extensions
can be added to both tasks and the schedule as a whole. Extensions are plain objects
and require a handler that extends
ExtensionHandler
.
The handler must be a service with the schedule.extension_handler
tag (this is
autoconfigurable). Extension handlers must implement the supports()
method which
should return true when passed the extension it handles.
If your extension is applicable to the schedule, you can auto-add it by registering
it as a service and adding the schedule.extension
tag (autoconfiguration is not
available).
Making your extension stringable by implementing __toString
shows this value in the
schedule:list
command.
Below are some examples of custom extensions:
Say your application has the concept of maintenance mode. You want to prevent the schedule from running in maintenance mode.
This example assumes your Kernel
has an isInMaintenanceMode()
method.
The extension:
// src/Schedule/Extension/NotInMaintenanceMode.php
class NotInMaintenanceMode
{
public function __toString(): string
{
return 'Do not run in maintenance mode.';
}
}
The handler service:
// src/Schedule/Extension/NotInMaintenanceModeHandler.php
use App\Kernel;
use Zenstruck\ScheduleBundle\Schedule\ScheduleRunContext;
use Zenstruck\ScheduleBundle\Schedule\Exception\SkipSchedule;
use Zenstruck\ScheduleBundle\Schedule\Extension\ExtensionHandler;
class NotInMaintenanceModeHandler extends ExtensionHandler
{
private $kernel;
public function __construct(Kernel $kernel)
{
$this->kernel = $kernel;
}
public function supports(object $extension) : bool
{
return $extension instanceof NotInMaintenanceMode;
}
/**
* @param NotInMaintenanceMode $extension
*/
public function filterSchedule(ScheduleRunContext $context, object $extension): void
{
if ($this->kernel->isInMaintenanceMode()) {
throw new SkipSchedule('Does not run in maintenance mode.');
}
}
}
The easiest way to add this extension to your schedule is to register the extension
(App\Schedule\Extension\NotInMaintenanceMode
) as a service and tag it with
schedule.extension
.
Alternatively, you can add it to the schedule in PHP:
use App\Schedule\Extension\NotInMaintenanceMode;
/* @var $schedule \Zenstruck\ScheduleBundle\Schedule */
$schedule->addExtension(new NotInMaintenanceMode());
NOTE: This is an example to show creating/registering a custom extension. In
a real world application, all that would be needed to accomplish the above
example would be the following in your Kernel
:
// src/Kernel.php
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
use Zenstruck\ScheduleBundle\Schedule;
use Zenstruck\ScheduleBundle\Schedule\ScheduleBuilder;
class Kernel extends BaseKernel implements ScheduleBuilder
{
public function isInMaintenanceMode(): bool
{
// return true if in maintenance mode...
}
public function buildSchedule(Schedule $schedule): void
{
$schedule->skip('Does not run in maintenance mode.', $this->isInMaintenanceMode());
}
// ...
}
The following Symfony events are available:
Event | Description |
---|---|
BeforeScheduleEvent |
Runs before the schedule runs |
AfterScheduleEvent |
Runs after the schedule runs |
BeforeTaskEvent |
Runs before a due task runs |
AfterTaskEvent |
Runs after a due task runs |
BuildScheduleEvent |
Define/manipulate tasks/schedule |
Let's configure all our tasks to have the withoutOverlapping extension added.
The subscriber service:
// src/EventSubscriber/ScheduleWithoutOverlappingSubscriber.php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Zenstruck\ScheduleBundle\Event\BuildScheduleEvent;
class ScheduleWithoutOverlappingSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
BuildScheduleEvent::class => [
'onBuildSchedule',
/*
* The actual building of the schedule happens at priority "0".
* We set to a lower priority to ensure all tasks have been defined.
*/
-100,
],
];
}
public function onBuildSchedule(BuildScheduleEvent $event): void
{
foreach ($event->getSchedule()->all() as $task) {
$task->withoutOverlapping();
}
}
}
NOTE: If autoconfiguration is not enabled, add the kernel.event_subscriber
tag to the service.