Creating a custom scheduler task 

Scheduler task registration 

Custom scheduler tasks can be registered in EXT:my_extension/ext_localconf.php in the TYPO3 configuration value $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'] :

EXT:my_extension/ext_localconf.php
<?php

declare(strict_types=1);

use MyVendor\MyExtension\Tasks\MyTask;
use MyVendor\MyExtension\Tasks\MyTaskAdditionalFieldProvider;

defined('TYPO3') or die();

$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][MyTask::class] = [
    'extension' => 'scheduler',
    'title' => 'Some title or LLL:EXT reference',
    'description' => 'Some description or LLL:EXT reference',
    'additionalFields' => MyTaskAdditionalFieldProvider::class,
];
Copied!

Working with serialized objects 

When a task is registered with the Scheduler the corresponding object instance is serialized and stored in the database (see Appendix A for more details). This is not a very common practice. There are advantages but also some pitfalls, so please read this section carefully.

A serialized object may happen to be "out of sync" with its class if the class changes some of its variables or methods. If a variable's name is changed or if variables are added or removed, the serialized object will not reflect these changes. The same goes if a method is renamed, added or deleted. Problems will also arise if the number or order of arguments used to call a method are changed. In such cases weird errors may appear, which can be very difficult to track. The only solution is to delete the registered task and register it anew.

To minimize such risks it is worth to consider implementing the business logic in a separate class, so that the task class itself changes as little as possible. The execute() should be as simple as possible. Consider the following:

packages/my_extension/Classes/MyTask.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Tasks;

use MyVendor\MyExtension\BusinessLogic;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Scheduler\Task\AbstractTask;

final class MyTask extends AbstractTask
{
    public function execute(): bool
    {
        # Dependency injection cannot be used in scheduler tasks
        $businessLogic = GeneralUtility::makeInstance(BusinessLogic::class);
        return $businessLogic->run('arg1', 'arg2', '…');
    }
}
Copied!

In such a setup the execute() is kept to the strict minimum and the operations themselves are handled by a separate class.

Also remember that the constructor is not called when unserializing an object. If some operations need to be run upon unserialization, implement a __wakeup() method instead.

Saving a task's state 

The task's state is saved automatically at the start of its execution. If you need to save a task's state at some point during its execution, you can simply call the task's own save() method.

Providing additional fields for scheduler task 

If the task should provide additional fields for configuration options in the backend module, you need to implement a second class, extending AbstractAdditionalFieldProvider .

This class needs to be configured in the scheduler task registration:

EXT:my_extension/ext_localconf.php
<?php

declare(strict_types=1);

use MyVendor\MyExtension\Tasks\MyTask;

defined('TYPO3') or die();

$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][MyTask::class] = [
    'extension' => 'scheduler',
    'title' => 'Some title or LLL:EXT reference',
    'description' => 'Some description or LLL:EXT reference',
];
Copied!

And implemented to provide the desired fields and their validation:

packages/my_extension/Classes/MyTask.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Tasks;

use TYPO3\CMS\Scheduler\AbstractAdditionalFieldProvider;
use TYPO3\CMS\Scheduler\Controller\SchedulerModuleController;
use TYPO3\CMS\Scheduler\Task\AbstractTask;

final class MyTaskAdditionalFieldProvider extends AbstractAdditionalFieldProvider
{
    public function getAdditionalFields(
        array &$taskInfo,
        $task, SchedulerModuleController $schedulerModule,
    ): array {
        $fieldId = 'task_myextension_someField';
        $fieldName = 'tx_scheduler[scheduler_myextension_someField]';
        $fieldHtml = '<input class="form-select" name="' . $fieldName . '" id="' . $fieldId . '" />';
        return [
            $fieldId => [
                'code' => $fieldHtml,
                'label' => 'Label or LLL:EXT reference',
                'type' => 'select',
            ]
        ];
    }

    public function validateAdditionalFields(
        array &$submittedData,
        SchedulerModuleController $schedulerModule,
    ): bool {
        if ($submittedData === []) {
            return false;
        }
        // do some other tests
        return true;
    }

    public function saveAdditionalFields(
        array $submittedData,
        AbstractTask $task,
    ): void {
        $task->setTaskParameters([
            'someField' => $submittedData['scheduler_myextension_someField'] ?? '',
        ]);
    }
}
Copied!