Fai in modo che il tuo sito esegua attività periodicamente
Lo Scheduler delle attività di Joomla può eseguire attività a intervalli regolari. Tali attività sono definite nei plugin di attività. Le attività possono essere di qualsiasi tipo, ad esempio:
- Pulire i dati obsoleti.
- Notificare agli utenti eventi o situazioni specifiche sul server.
- Aggiungere informazioni esterne al sito web.
- Eseguire attività che richiedono molto tempo quando il server è meno occupato, oppure distribuirle su un periodo di tempo più lungo.
- Generare report periodici.
- Verificare e ripristinare l'integrità dei dati.
Consulta una panoramica delle possibilità esistenti nell'articolo di Marc Dechèvre pubblicato sulla rivista 2023 Magazine sulle attività pianificate.
Puoi definire le tue attività. Non è troppo difficile. Questo articolo ti mostra come fare.
Configurazione generale di un plugin
Quasi tutti i plugin hanno la stessa struttura di base con almeno i seguenti file e cartelle:
- File manifest: il file xml con informazioni generali sul plugin.
- Fornitore di servizi: file provider.php nella cartella /services, per istanziare il plugin e iniettare le dipendenze.
- File di estensione: il file php che contiene il plugin vero e proprio. Va inserito nella cartella /src/Extension. Il nome di questo file php è esattamente lo stesso del nome della classe, con le stesse maiuscole.
La maggior parte dei plugin di attività richiede una cartella /language, contenente i file ini delle lingue. Un plugin di attività spesso dispone anche di un file xml con una definizione di modulo, memorizzato in una cartella /forms.
Questa sarebbe la configurazione per un plugin di attività MyTask:
plg_task_mytask
├─── forms
│ └─── myForm.xml
├─── language
│ └─── en-GB
│ ├─── plg_task_mytask.ini
│ └─── plg_task_mytask.sys.ini
├─── services
│ └─── provider.php
├─── src
│ └─── Extension
│ └─── MyTask.php
└─── mytask.xml
Alcune caratteristiche specifiche dei plugin di attività
I plugin nell'attuale Joomla gestiscono i cosiddetti “eventi”. Gli eventi sono oggetti che contengono tutti i dati da e verso il plugin. Tutti i tipi di eventi hanno le loro caratteristiche: quali dati forniscono e, se del caso, quali dati sono attesi in ritorno.
Ci sono 3 eventi che svolgono un ruolo nella maggior parte dei plugin di attività:
- L'evento onExecuteTask viene gestito per eseguire l'attività. Nel
$resultdell'evento, lo scheduler si aspetta in risposta un array$snapshot, che registra come è andata l'esecuzione dell'attività. - L'evento onTaskOptionsList viene gestito per fornire allo scheduler una panoramica di tutti i diversi “tipi” di attività (chiamati anche ‘opzioni’ o “routine”) che questo plugin è in grado di gestire. Tale elenco di opzioni di attività viene rinviato allo scheduler. Un esempio di plugin di attività di base che gestisce più opzioni di attività è il plugin di attività SiteStatus. In quel singolo plugin è possibile gestire tre tipi di attività correlate: impostare il sito offline, impostare il sito online o commutare il sito tra online e offline.

- L'evento opzionale onContentPrepareForm viene utilizzato per arricchire il modulo con parametri per gestire l'evento dell'attività.
Per semplificare il tutto, esiste un TaskPluginTrait che elimina gran parte del codice necessario per gestire tutto questo. Nel metodo getSubscribedEvents() si fa quindi in modo che i 3 eventi puntino a metodi standard nel trait:
public static function getSubscribedEvents(): array
{
return [
‘onTaskOptionsList’ => ‘advertiseRoutines’,
‘onExecuteTask’ => ‘standardRoutineHandler’,
‘onContentPrepareForm’ => 'enhanceTaskItemForm'
];
}
Per far sapere a quei metodi standard dove trovare gli elementi specifici per gestire l'attività, esiste una costante TASKS_MAP, contenente tutte le informazioni su dove trovare ogni cosa per ogni opzione dell'attività:
private const TASKS_MAP = [
‘mytask’ => [
‘langConstPrefix’ => ‘PLG_TASK_MYTASK’,
‘method’ => ‘myEventHandler’,
‘form’ => ‘myForm’,
],
];
In quanto sopra:
- L'opzione task ha un id ‘mytask’.
- C'è una costante di lingua per visualizzare il nome leggibile dell'opzione task.
- La chiave “method” si riferisce al metodo chiamato da standardRoutineHandler per eseguire l'ExecuteTaskEvent. Questo metodo dovrebbe restituire uno stato che viene elaborato automaticamente dai metodi standard del trait.
- La chiave opzionale “form” si riferisce al nome del modulo in cui sono definiti i parametri dell'attività.
Se sono presenti più opzioni di attività, come nel plugin dell'attività SiteStatus, l'array TASKS_MAP contiene più voci di questo tipo. A titolo di esempio, ecco il TASKS_MAP del plugin dell'attività J2Commerce, con 4 opzioni di attività:
private const TASKS_MAP = [
‘j2commerce.removeNewOrders’ => [
‘langConstPrefix’ => 'PLG_TASK_J2COMMERCE_REMOVE_NEW_ORDERS',
‘method’ => ‘removeNewOrders’,
‘form’ => ‘removeNewOrders’,
],
‘j2commerce.processQueue’ => [
‘langConstPrefix’ => 'PLG_TASK_J2COMMERCE_PROCESS_QUEUE',
‘method’ => ‘processQueue’,
‘form’ => ‘processQueue’,
],
‘j2commerce.cleanupQueueLogs’ => [
‘langConstPrefix’ => 'PLG_TASK_J2COMMERCE_CLEANUP_QUEUE_LOGS',
‘method’ => ‘cleanupQueueLogs’,
‘form’ => ‘cleanupQueueLogs’,
],
‘j2commerce.updateCurrencyRates’ => [
‘langConstPrefix’ => 'PLG_TASK_J2COMMERCE_UPDATE_CURRENCY_RATES',
‘method’ => ‘updateCurrencyRates’,
‘form’ => ‘updateCurrencyRates’,
],
];
Ora che abbiamo collegato tutto, possiamo passare al lavoro vero e proprio!
Gestione dell'evento dell'attività
Il metodo handler contiene il codice per eseguire l'attività. Nel TASKS_MAP sopra riportato, con una singola opzione di attività, si tratta del metodo myEventHandler(). L'handler ha come parametro ExecuteTaskEvent:
private function myEventHandler(ExecuteTaskEvent $event): int
{
// Ed è qui che succede tutto!
}
Ma, ehi, aspetta un attimo… Com'è possibile? Come può essere un metodo privato? E perché c'è un valore di ritorno? Pensavo che il metodo handler dovesse essere pubblico e non restituisse nulla quando si implementa l'interfaccia SubscriberInterface? Sì, hai ragione, ma ricorda come nel metodo getSubscribedEvents() abbiamo collegato onExecuteTask al metodo standardRoutineHandler() del TaskPluginTrait. E infatti, quel metodo standardRoutineHandler() è pubblico e non restituisce nulla.
Questo metodo privato myEventHandler() viene chiamato dal metodo standardRoutineHandler(), che si occupa di elaborare lo stato (quell'intero che viene restituito) nell'array $snapshot che viene inserito nel $result dell'evento. La gestione dell'evento tramite il metodo standardRoutineHandler() di TaskPluginTrait è tipica di un plugin di task.
Se tutto procede correttamente, il gestore ExecuteTaskEvent restituisce una costante di stato OK:
return Status::OK;
Queste costanti Joomla\Component\Scheduler\Administrator\Task\Status sono mappate su numeri interi. Ecco tutti i valori possibili:
OK= 0: codice di uscita in caso di successo.NO_EXIT= -1: utilizzato quando una routine non restituisce un codice di uscita.INVALID_EXIT= -2: utilizzato quando una routine restituisce un codice di uscita non valido (non intero).RUNNING= 1: utilizzato quando la routine è appena iniziata. Non è inteso come codice di uscita.NO_LOCK= 2: utilizzato in caso di fallimento nell'acquisizione di uno pseudo-blocco.NO_RUN= 3: utilizzato in caso di fallimento nell'esecuzione dell'attività.NO_RELEASE= 4: utilizzato in caso di errore nel rilasciare, bloccare o aggiornare il record.KNOCKOUT= 5: utilizzato quando una routine viene “messa fuori gioco” da un'eccezione o incontra un'eccezione che non è in grado di gestire correttamente.WILL_RESUME= 123: utilizzato quando un'attività deve essere ripresa. Utilizzarlo per attività di lunga durata per suddividerle in batch più piccoli. Quando l'ultimo batch viene eseguito, restituire Status::OK.TIMEOUT= 124: utilizzato quando un'attività va in timeout.NO_TASK= 125: utilizzato quando un'attività non esiste.NO_ROUTINE= 127: utilizzato quando manca un'opzione dell'attività.
Modulo con parametri
È possibile aggiungere parametri personalizzati all'ExecuteTaskEvent. Se, come sopra, in getSubscribedEvents() l'evento onContentPrepareForm punta al metodo standard enhanceTaskItemForm(), e in TASKS_MAP la chiave ‘form’ fa riferimento a ‘myForm’, allora è necessario avere una definizione del modulo myForm.xml nella cartella /forms.
I campi definiti in quel modulo verranno aggiunti ai parametri di ExecuteTaskEvent.
Nel gestore ExecuteTaskEvent è possibile ottenere il valore del parametro con $event->getArgument(‘params’)->nameOfParameter.
Iniezione delle dipendenze
Nella prima parte di questa serie sui plugin personalizzati ho già spiegato come il plugin viene istanziato e come le dipendenze vengono iniettate al suo interno nel file del provider di servizi. Per la maggior parte delle dipendenze standard, come il database, gli utenti o il Mailer, possiamo utilizzare i tratti standard e l'iniezione tramite setter. Per altre dipendenze specifiche del tuo plugin personalizzato, puoi anche utilizzare l'iniezione tramite costruttore, come mostreremo qui di seguito.
Iniezione tramite setter
Con l'iniezione tramite setter nel tuo file del provider di servizi, prima istanzi il plugin e poi imposti le dipendenze. Per le dipendenze standard, esistono trait standard con setter e getter da inserire nel tuo plugin.
Ad esempio, se nel plugin è necessario un database, in genere si utilizza il Joomla\Database\DatabaseAwareTrait nel plugin:
final class MyTask extends CMSPlugin implements SubscriberInterface, DatabaseAwareInterface
{
use DatabaseAwareTrait;
use TaskPluginTrait;
Questo trait dispone dei metodi setDatabase() e getDatabase(). La best practice consiste nel dichiarare quale interfaccia viene implementata quando si utilizza un trait di questo tipo; in questo caso Joomla\Database\DatabaseAwareInterface.
Nel proprio service provider, dopo aver istanziato il plugin, si imposta il database e, con ciò, si inietta l'oggetto database nel plugin:
$plugin->setDatabase($container->get(DatabaseInterface::class));
Se si desidera utilizzare quell'oggetto database in un metodo del proprio plugin, è sufficiente chiamare il getter:
$db = $this->getDatabase();
Il getter e il setter per l'oggetto Application sono già implementati nel classe padre Joomla\CMS\Plugin\CMSPlugin. Non è necessario aggiungere un trait per impostare o ottenere l'Application.
Alcuni altri oggetti molto utilizzati per i quali sono disponibili nel core dei trait standard con setter e getter sono:
Joomla\CMS\User\UserFactoryAwareTrait, che implementaJoomla\CMS\User\UserFactoryAwareInterface, per gestire gli utenti nel proprio plugin.Joomla\CMS\Mail\MailerFactoryAwareTrait, che implementaJoomla\CMS\Mail\MailerFactoryInterface, per utilizzare il Mailer (per inviare notifiche via e-mail).
Se hai altre dipendenze, per le quali non esiste un trait standard, puoi aggiungere getter e setter al tuo plugin per impostare la tua dipendenza nel provider di servizi e per ottenerla in un metodo del tuo plugin. Oppure puoi usare l'iniezione tramite costruttore (vedi la sezione successiva).
Iniezione tramite costruttore
Con l'iniezione nel costruttore si aggiungono parametri extra al proprio plugin e si iniettano le dipendenze durante l'istanziazione del plugin nel proprio provider di servizi. Nel proprio plugin si aggiunge quindi un costruttore per recuperare i parametri extra.
Ad esempio, si desidera aggiungere Joomla\Http\HttpFactory al proprio plugin per poter chiamare alcune API di un altro sito. Nel proprio provider di servizi lo si aggiunge come parametro aggiuntivo durante l'istanziazione del plugin:
$plugin = new MyTask(
(array) PluginHelper::getPlugin(‘task’, ‘mytask’),
new HttpFactory()
);
Come vedi: normalmente un CMSPlugin ha un solo parametro, l'array $config. Ora ne abbiamo aggiunto un secondo, un nuovo Joomla\Http\HttpFactory.
Nel tuo plugin aggiungi una proprietà HttpFactory:
/**
* L'Http Factory per creare un oggetto Http
*
* @var HttpFactory
*/
private $httpFactory;
E nel costruttore del tuo plugin inizializzi quella proprietà:
public function __construct(array $config, HttpFactory $httpFactory)
{
parent::__construct($config);
$this->httpFactory = $httpFactory;
}
A partire da PHP 8.0 abbiamo la cosiddetta promozione del costruttore, quindi invece dei due frammenti di codice sopra riportati possiamo semplicemente scrivere:
public function __construct(array $config, private HttpFactory $httpFactory)
{
parent::__construct($config);
}
In un metodo del tuo plugin ora puoi usare questa HttpFactory con $this->httpFactory. Ad esempio, per effettuare una richiesta GET a qualche API esterna puoi scrivere:
$response = $this->httpFactory->getHttp([])->get($url, $headers, $timeout);
Questo esempio è una riga tratta dal plugin core Request task. Anche quel plugin aggiunge HttpFactory e un'altra variabile tramite l'iniezione nel costruttore.
Alcuni frammenti di codice utili per i gestori di attività
Se vuoi vedere alcuni esempi di gestori di attività, puoi dare un'occhiata al codice dei plugin di attività di base. Anche diversi sviluppatori di terze parti offrono plugin di attività che vale la pena esaminare, ad esempio nelle versioni Pro di Akeeba Backup e Admin Tools.
In quel plugin di attività di Admin Tools vengono gestite nove diverse opzioni di attività, dove ciascuno dei gestori di attività si trova in un trait separato, memorizzato in una cartella SubTask; penso che la parola “subtask” sia interessante e più chiara rispetto a “task option”, “task type” e “routine” che vengono usate nel codice di base. Ma non oserei proporre un nuovo standard 🤣:

Alcuni altri plugin per le attività sono elencati nell'articolo di Marc Dechèvre sulle attività pianificate, e alcuni sono elencati nel Joomla Extensions Directory. Per un plugin gratuito con diverse opzioni di attività, puoi anche dare un'occhiata al plugin J2Commerce per le attività, con opzioni per rimuovere nuovi ordini, elaborare una coda, ripulire i log della coda o aggiornare i tassi di cambio.
Ecco alcuni frammenti di codice che possono essere utili quando scrivi il tuo gestore di attività:
Usa il database
// DatabaseDriver è stato iniettato (usa DatabaseAwareTrait)
$db = $this->getDatabase();
$query = $db->createQuery();
// … crea ed elabora la query …
Invia una notifica via e-mail
// MailerFactory è stato iniettato (usa MailerFactoryAwareTrait)
// Ottieni un oggetto mailer da quella MailerFactory
$mailer = $this->getMailerFactory()->createMailer();
// … Crea il tuo messaggio, l'indirizzo del mittente, l'indirizzo del destinatario, ecc …
// Crea il messaggio e-mail
$mailer->addRecipient($to_addr);
$mailer->setSender(array($from_addr, $from_name));
$mailer->setSubject($subject);
$mailer->setBody($message);
$mailer->IsHTML(1);
// Invia l'e-mail di notifica
$mailer->Send();
Usa il modello di un componente
// L'applicazione è stata iniettata
$app = $this->getApplication();
/** @var MVCFactoryInterface $mvcFactory */
$mvcFactory = $app->bootComponent(‘com_somecomponent’)->getMVCFactory();
/** @var SomeModel $someModel */
$someModel = $mvcFactory->createModel(‘SomeModel’, ‘Administrator’, [‘ignore_request’ => true]);
$someModel->someMethod($settings);
Chiama un'API di servizio web
// HttpFactory è stata iniettata (tramite iniezione del costruttore)
// Ottieni un oggetto Http da quella HttpFactory
$http = $this->httpFactory->getHttp([]);
// … preparare url, intestazioni (token), corpo della richiesta ecc …
// Esempio: richiesta GET
$response = $http->get($url, $headers, $timeout);
Elabora una quantità limitata di una coda per batch
In questo esempio contiamo semplicemente alla rovescia da un parametro ‘max_times’ e non usiamo la costante di stato WILL_RESUME. Un conteggio alla rovescia di questo tipo viene utilizzato anche nel plugin di attività CheckFiles del core (per ridimensionare una quantità limitata di immagini, impostata nel parametro $numImages).
$params = $event->getArgument(‘params’);
$maxTimes = $params->max_times;
// … eseguire una query per ottenere gli elementi da elaborare da una coda …
// elabora $maxTimes elementi
foreach($items as $item) {
// … elabora l'elemento e, se l'operazione ha esito positivo, contrassegnalo come completato nella coda …
--$maxTimes;
// Elaboriamo un numero limitato di elementi per ogni esecuzione
if ($maxTimes == 0) {
break;
}
}
Generazione di plugin
La parte più specifica del codice personalizzato del tuo plugin si trova nel gestore ExecuteTaskEvent nella classe del plugin in /Extension. Il resto serve più che altro a collegare tutto insieme. Per concentrarmi meglio sull'essenziale, sto lavorando a un generatore di plugin che produca il codice boilerplate, incluse le parti specifiche per diversi tipi di plugin. Il primo è il plugin task, che accompagna questo articolo. Otterrai almeno uno scheletro, ma cercherò di incorporare il più possibile anche funzionalità già pronte. Lo puoi trovare su github.com/HermanPeeren/plug-gen.
Il mese prossimo
Il prossimo episodio tratterà dei plugin per i flussi di lavoro personalizzati. Ad esempio, per migliorare l'attuale flusso di lavoro di pubblicazione o per definire un flusso di lavoro completamente diverso.
Risorse
- L'articolo iniziale del Summer of Code 2021 sul progetto Task Scheduler di Tushar Malik.
- Scopri il nuovo Joomla! Task Scheduler, di Brendan Hedges, Joomla Community Magazine, marzo 2022.
- Attività pianificate, di Marc Dechèvre, Joomla Community Magazine, novembre 2023. Contiene link ad altre risorse.
- Tutorial: Come creare un plugin per lo scheduler delle attività di Joomla di Joomla Fry.
Informazioni generali sui plugin nel manuale per sviluppatori
- Tutorial su come creare un plugin (di contenuto).
- Come funzionano i plugin.
- Metodi ed eventi dei plugin.
Articoli di questa serie sui plugin personalizzati
- Panoramica generale.
- Plugin Task (questo articolo).
- Plugin Workflow (il mese prossimo).