Zum Inhalt

Cron-Jobs

Über das Cron-System des JTL-Shop können wiederkehrende Aufgaben ausgeführt werden. Neben den vordefinierten ist es auch möglich, dem System neue Jobs per Plugin hinzuzufügen.

Cron-Job im System auswählbar machen

Um einen selbsdefinierten Cron-Job über die Cron-Verwaltung im Backend auswählbar zu machen, muss er zunächst in die Liste der verfügbaren Cron-Jobs eingefügt werden. Dies passiert über das Event GET_AVAILABLE_CRONJOBS (im Namespace \JTL\Events\Event) des EventDispatchers. Im Bootstrapper des Plugins wird dazu ein Listener für dieses Event registriert und der eigene Cron-Job-Type in die Liste der verfügbaren Job-Typen eingefügt.

public const CRON_TYPE = 'plugin:cronjob_plugin.testcron';

public function boot(Dispatcher $dispatcher): void
{
    parent::boot($dispatcher);
    //...
    $dispatcher->listen(\JTL\Events\Event::GET_AVAILABLE_CRONJOBS, [$this, 'availableCronjobType']);
    //...
}

public function availableCronjobType(array &$args): void
{
    if (!\in_array(self::CRON_TYPE, $args['jobs'], true)) {
        $args['jobs'][] = self::CRON_TYPE;
    }
}

Damit das Cron-System anhand des registrierten Cron-Types den richtigen Cron-Job aufrufen kann, ist noch ein Mapping auf eine Job-Klasse notwendig. Diese Klasse muss das Interface \JTL\Cron\JobInterface implementieren. Das Mapping passiert über das Event MAP_CRONJOB_TYPE des EventDispatchers. Im Bootstrapper des Plugins wird dazu ein weiterer Listener hinzugefügt und dort der eigene Cron-Job-Type auf eine Job-Klasse gemapped.

public const CRON_TYPE = 'plugin:cronjob_plugin.testcron';

public function boot(Dispatcher $dispatcher): void
{
    parent::boot($dispatcher);
    //...
    $dispatcher->listen(\JTL\Events\Event::GET_AVAILABLE_CRONJOBS, [$this, 'availableCronjobType']);
    $dispatcher->listen(\JTL\Events\Event::MAP_CRONJOB_TYPE, [$cronHelper, 'mappingCronjobType']);
    //...
}

public function availableCronjobType(array &$args): void
{
    if (!\in_array(self::CRON_TYPE, $args['jobs'], true)) {
        $args['jobs'][] = self::CRON_TYPE;
    }
}

public function mappingCronjobType(array &$args): void
{
    /** @var string $type */
    $type = $args['type'];
    if ($type === self::CRON_TYPE) {
        $args['mapping'] = MyCustomCronJob::class;
    }
}

Eigene Job-Klasse implementieren

Nach der Registrierung und dem Mapping kann der eigene Cron-Job über die Cron-Verwaltung ausgewählt, aktiviert und auch wieder gelöscht werden. Die konkrete Funktionalität wird in einer eigenen Job-Klasse implementiert. Diese Klasse muss das Interface JTL\Cron\JobInterface implementieren und kann dazu einfach die abstrakte Klasse JTL\Cron\Job erweitern. Es muss mindestens die Methode start(QueueEntry $queueEntry): JobInterface implementiert werden.

use JTL\Cron\Job;
use JTL\Cron\JobInterface;
use JTL\Cron\QueueEntry;

/**
 * Class CronJob
 */
class MyCustomCronJob extends Job
{
    public function start(QueueEntry $queueEntry): JobInterface
    {
        parent::start($queueEntry);
        $this->garbageCollect();
        $this->setFinished($this->doSomething());

        return $this;
    }

    private function doSomething(): bool
    {
        //... do whatever to do if cron job is called
        return true;
    }
}

Wann ist ein Cron-Job "fertig"?

Der Sinn von Cron-Jobs besteht - neben dem zeitgesteuerten Ausführen von Aufgaben - auch darin, größere Abläufe in kleinere "Häppchen" zu packen und diese in mehreren Durchläufen zu erledigen. Dazu wird ein Job, nachdem er zu einem bestimmten Zeitpunkt "gestartet" wurde, solange zyklisch aufgerufen, bis er über setFinished(true) signalisiert, dass er "fertig" ist. Um z.B. eine Liste an Einträgen in 10er-Schritten abzuarbeiten, könnte die Methode doSomething(): bool so implementiert werden:

private function doSomething(): bool
{
    $listLimit = 10;
    $listCount = $this->getListCount();
    $listItems = $this->getListItems($listLimit);

    foreach ($listItems as $listItem) {
        $this->finishListItem($listItem);
    }

    return $listCount <= $listLimit;
}

Eine detailiertere Info zum aktuellen Stand der Abarbeitung kann über die JTL\Cron\QueueEntry-Instanz $queueEntry der start-Methode realisiert werden.

Programmatisches Anlegen eines Cron-Jobs

Wenn der eigene Cron-Job nicht nur durch Nutzer-Interaktion im Backend des JTL-Shop angelegt werden soll, sondern auch automatisch bei einer bestimmten Plugin-Einstellung, so kann dieser über Methoden des JTL\Router\Controller\Backend\CronController angelegt und auch wieder gelöscht werden.

public function installCron(int $frequency = 6, string $startTime = '02:00'): void
{
    $controller = Shop::Container()->get(JTL\Router\Controller\Backend\CronController::class);
    $controller->addQueueEntry([
        'type'      => self::CRON_TYPE,
        'frequency' => $frequency,
        'time'      => $startTime,
        'date'      => (new DateTime())->format('Y-m-d H:i:s'),
    ]);
}

public function uninstallCron(): void
{
    $controller = Shop::Container()->get(JTL\Router\Controller\Backend\CronController::class);
    $cron       = \array_filter($controller->getJobs(), static function (JobInterface $job) {
        return $job->getType() === self::CRON_TYPE;
    });

    if (\count($cron) !== 0) {
        $controller->deleteQueueEntry(\array_shift($cron)->getCronID());
    }
}

Automatisches Anlegen bei Installation des Plugins

Soll ein Plugin-Cron-Job bei der Installation eines Plugins automatisch eingerichtet werden kann dafür die Methode installed des Bootstrappers genutzt werden.

public function installed(): void
{
    parent::installed();

    $cronJob = new PluginCronJob();
    $cronJob->installCron(3, '01:30');
}

Bei der Deinstallation des Plugins muss der Cron-Job dann über die Methode uninstalled wieder entfernt werden.

public function uninstalled(bool $deleteData = true): void
{
    parent::uninstalled($deleteData);

    $cronJob = new PluginCronJob();
    $cronJob->uninstallCron(3, '01:30');
}

Hint

Die komplette Implementation eines Plugin-Cron-Job ist im JTL-Demo Plugin realisisert. Das Plugin ist über Gitlab frei verfügbar.