Skip to content

Latest commit

 

History

History
266 lines (200 loc) · 7.47 KB

extending.md

File metadata and controls

266 lines (200 loc) · 7.47 KB

Extending

Custom Tasks

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')
;

Custom Extensions

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:

Example: Skip Schedule if in maintenance mode

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());
    }

    // ...
}

Events

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

Example: Add "withoutOverlapping" to all defined tasks

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.