Eigenen Task erstellen
TYPO3 4.3Vorwort
Aufgaben, sogenannte Tasks, werden in TYPO3 in der Tabelle tx_scheduler_task
abgelegt. Ganz übel dabei: Die PHP-Klasse, die für die Ausführung der Aufgabe zuständig ist, wird mittels PHP:serialize()
auch mit in dieser Tabelle abgelegt. Spalte serialized_task_object
. Dieses Vorgehen führt zu gewaltigen Problemen, wenn der Task später mal aktualisiert wird und weitere Objekte dem Task hinzugefügt werden. Ihr könnt den Task noch sooft erweitern, wie ihr wollt, es wird bei Ausführung der Aufgabe immer das deserialisierte Objekt aus der Tabelle tx_scheduler_task
verwenden. Eure neu hinzugefügten Services, Referenzen und andere Objekte werden überhaupt nicht berücksichtigt. Die Aufgabe muss zunächst gelöscht und wieder neu angelegt werden.
Dieses Problem ist so omnipräsent, dass der TYPO3 Core in seiner Dokumentation Creating a new task die Verwendung von CLI Kommandos bevorzugt, die als ausführbare Aufgabe deklariert wurden. In Bezug auf scheduler
Aufgaben wird nur noch mitgeteilt, dass jegliche Logik in ausgelagerte Services zu überführen ist, um Referenzen in der Aufgabe auf ein absolutes Minimum zu reduzieren.
Ich habe noch nichts Offizielles dazu gelesen, aber ich glaube, dass die scheduler
Extension in der aktuellen Fassung ausgedient hat und eine gewaltige Umstrukturierung dieser Extension in Planung ist. Es gibt schon alternative Ansätze von Helmut Hummel die scheduler
Extension zu ersetzen. Daher rate auch ich dazu möglichst auf schedulable CLI Kommandos umzusteigen.
Eigenen Task registrieren
Im Folgenden ein Beispiel zur Erstellung einer Aufgabe, um einen Server anzupingen.
Seit TYPO3 4.3 werden Aufgaben für die scheduler
Extension in der ext_localconf.php
registriert. Folgendes Beispiel funktioniert ab TYPO3 11.5.
<?php
if (!defined('TYPO3')) {
die('Access denied.');
}
use StefanFroemken\SitePackage\Task\PingerTask;
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][PingerTask::class] = [
'extension' => 'site_package',
'title' => 'Ping Server',
'description' => 'Hinterlegt eine IP-Adresse, die dieser Task mit jedem Intervall anpingen soll.',
];
Der Task wird immer mit seinem vollständigen Klassennamen registriert (siehe ::class
oben). Diese Klasse muss nun angelegt werden. Ich empfehle Aufgaben (Tasks) im Extension-Verzeichnis unter Classes/Task/*
abzulegen. Erstellt dort die Datei PingerTask.php
mit folgendem Inhalt:
<?php
declare(strict_types=1);
/*
* This file is part of the package stefanfroemken/site-package.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
namespace StefanFroemken\SitePackage\Task;
use TYPO3\CMS\Scheduler\Task\AbstractTask;
/**
* Scheduler task to ping a server
*/
class PingerTask extends AbstractTask
{
public function execute(): bool
{
$time = $this->ping('typo3lexikon.de', 15);
return $time !== 0.0;
}
/**
* @return float Return time until answer in seconds
*/
private function ping(string $ip, int $timeout = 10): float
{
$start = microtime(true);
$file = @fsockopen($ip, 80, $errno, $errstr, $timeout);
$stop = microtime(true);
if (!$file) {
// Site is offline
return 0.0;
}
fclose($file);
return round(($stop - $start) * 1000);
}
}
Jeder Task muss von der abstrakten Klasse AbstractTask
erben. Ohne diese Klasse kann die benötigte execute()
Methode nicht aufgerufen werden. Die execute()
Methode erlaubt einen booleschen Wert als Rückgabe. Gebt false
zurück und der Task wird in der Aufgabenübersicht entsprechend als fehlerhaft markiert.
Das @-Zeichen vor PHP:fsockopen()
ist dafür da, dass ihr nicht die PHP-Warnungen seht, wenn ihr den Task per Hand ausführt und der Server nicht erreichbar sein sollte.
Wechselt nun ins Installtool und wählt unter Wartung
den Punkt Flush Caches
. Der Task kann nun über das Planer Modul hinzugefügt, konfiguriert und auch manuell ausgeführt werden.
Dynamische Werte
Anstatt mit fest vorgegebenen Werten im PingerTask
zu arbeiten, sollten diese Werte besser konfigurierbar gemacht werden. Dazu bietet TYPO3 bei der Registrierung des Tasks in der ext_localconf.php
eine weitere Option additionalFields
an, um eine weitere PHP Klasse für die Erstellung und Verwaltung von zusätzlichen Feldern zu hinterlegen:
<?php
if (!defined('TYPO3')) {
die('Access denied.');
}
use StefanFroemken\SitePackage\Task\PingerTask;
use StefanFroemken\SitePackage\Task\PingerTaskAdditionalFieldProvider;
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['scheduler']['tasks'][PingerTask::class] = [
'extension' => 'site_package',
'title' => 'Ping Server',
'description' => 'Hinterlegt eine IP-Adresse, die dieser Task mit jedem Intervall anpingen soll.',
'additionalFields' => PingerTaskAdditionalFieldProvider::class,
];
Diese weitere PHP Klasse PingerTaskAdditionalFieldProvider
muss alle Methoden aus dem AdditionalFieldProviderInterface
implementieren, um die Felder für die Benutzereingaben zu erstellen, zu überprüfen und die Werte an unseren Task zu überführen. In unserem Fall erben wir jedoch von der Klasse AbstractAdditionalFieldProvider
, da diese zusätzlich eine Methode zur Ausgabe von Fehlermeldungen mitbringt.
<?php
declare(strict_types=1);
/*
* This file is part of the package stefanfroemken/site-package.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
namespace StefanFroemken\SitePackage\Task;
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Scheduler\AbstractAdditionalFieldProvider;
use TYPO3\CMS\Scheduler\Controller\SchedulerModuleController;
use TYPO3\CMS\Scheduler\Task\AbstractTask;
use TYPO3\CMS\Scheduler\Task\Enumeration\Action;
/**
* Scheduler task to ping a server
*/
class PingerTaskAdditionalFieldProvider extends AbstractAdditionalFieldProvider
{
/**
* @param PingerTask|null $task
*/
public function getAdditionalFields(array &$taskInfo, $task, SchedulerModuleController $schedulerModule): array
{
$additionalFields = [];
$fieldHtml = '<input class="form-control form-control-clearable t3js-clearable" type="text" name="tx_scheduler[%s]" id="%s" value="%s" size="30" />';
$currentSchedulerModuleAction = $schedulerModule->getCurrentAction();
if (!isset($taskInfo['scheduler_pinger_ip'])) {
$taskInfo['scheduler_pinger_ip'] = '';
if ($currentSchedulerModuleAction->equals(Action::EDIT)) {
$taskInfo['scheduler_pinger_ip'] = $task->ip;
}
}
if (!isset($taskInfo['scheduler_pinger_port'])) {
$taskInfo['scheduler_pinger_port'] = 80;
if ($currentSchedulerModuleAction->equals(Action::EDIT)) {
$taskInfo['scheduler_pinger_port'] = $task->port;
}
}
// Write the code for the field "ip"
$fieldId = 'task_ip';
$additionalFields[$fieldId] = [
'code' => sprintf($fieldHtml, 'scheduler_pinger_ip', $fieldId, $taskInfo['scheduler_pinger_ip']),
'label' => 'IP-Adresse/Webseite'
];
// Write the code for the field "port"
$fieldId = 'task_port';
$additionalFields[$fieldId] = [
'code' => sprintf($fieldHtml, 'scheduler_pinger_port', $fieldId, $taskInfo['scheduler_pinger_port']),
'label' => 'Port'
];
return $additionalFields;
}
public function validateAdditionalFields(array &$submittedData, SchedulerModuleController $schedulerModule): bool
{
$result = true;
if (!GeneralUtility::validIP($submittedData['scheduler_pinger_ip'])) {
$result = false;
$this->addMessage('Please enter a valid IP address', ContextualFeedbackSeverity::ERROR);
}
if (!MathUtility::canBeInterpretedAsInteger($submittedData['scheduler_pinger_port'])) {
$result = false;
$this->addMessage('Port must be integer', ContextualFeedbackSeverity::ERROR);
}
return $result;
}
/**
* @param PingerTask|AbstractTask $task
*/
public function saveAdditionalFields(array $submittedData, AbstractTask $task): void
{
$task->ip = (string)$submittedData['scheduler_pinger_ip'];
$task->port = (int)$submittedData['scheduler_pinger_port'];
}
}
getAdditionalFields
Im oberen Bereich setzen wir Standardwerte für unsere neuen Optionen. Wenn wir uns im Bearbeiten-Modus befinden werden unsere Optionen mit den Werten aus der Tabelle tx_scheduler_task
wieder befüllt.
Im unteren Bereich wird das HTML Grundgerüst für die Optionen gebaut. Hier gibt es kein Fluid oder andere Vorgaben. Ihr seid hier auch euch allein gestellt. Ich empfehle euch die TYPO3 eigenen AdditionalFieldProvider für Beispiele anzuschauen.
validateAdditionalFields
Bei Bedarf könnt ihr hier die vom Benutzer eingetragenen Werte überprüfen lassen. Sofern ihr vom AbstractAdditionalFieldProvider
erbt, könnt ihr bei Fehlern direkt eine Ausgabe für die Aufgabe im Planer Modul erstellen lassen.
saveAdditionalFields
Hier werden die Benutzereingaben an den Task übergeben und somit auch in der Tabelle tx_scheduler_task
in serialisierter Form abgelegt. Evtl. Typ-Anpassungen (cast) sollten hier vorgenommen werden
PingerTask anpassen
Die beiden neuen Optionen müssen in unserem PingerTask
hinzugefügt werden
<?php
declare(strict_types=1);
/*
* This file is part of the package stefanfroemken/site-package.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
namespace StefanFroemken\SitePackage\Task;
use TYPO3\CMS\Scheduler\Task\AbstractTask;
/**
* Scheduler task to ping a server
*/
class PingerTask extends AbstractTask
{
public string $ip = '';
public int $port = 80;
public function execute(): bool
{
$time = $this->ping($this->ip, $this->port);
return $time !== 0.0;
}
/**
* @return float Return time until answer in seconds
*/
private function ping(string $ip, int $port): float
{
$start = microtime(true);
$file = @fsockopen($ip, $port, $errno, $errstr, 10);
$stop = microtime(true);
if (!$file) {
// Site is offline
return 0.0;
}
fclose($file);
return round(($stop - $start) * 1000);
}
}
Nach diesem Update kann kein Hostname mehr für den Pinger verwendet werden. Es muss nun eine gültige IPv4 oder IPv6 Adresse angegeben werden. Den Timeout habe ich auf 10 Sekunden festgesetzt, dafür ist nun der Port “anpingbar”. Ja, sorry, eigentlich haben wir hier jetzt eher einen Port-Checker gebaut ;-)