Image mise en avant pour l'article

Comment créer des QueueWorkers dans Drupal ?

23 janvier 2023
Drupal
Qu'est-ce qu'un QueueWorker ? Pour quelle utilisation et comment en créer un ?


Les QueueWorkers sont une fonctionnalité arrivée avec Drupal 8 qui permet aux développeurs de créer des tâches asynchrones pour être exécutées en arrière-plan.

Elles sont utiles pour les tâches qui prennent un certain temps à être exécutées et qui ne doivent pas bloquer l'exécution du script principal. On peut utiliser les QueueWorkers pour des tâches telles que l'envoi de courriels, la mise à jour de données en arrière-plan, ou tout autre tâche qui ne doit pas être exécutée immédiatement. Ces tâches asynchrones sont mises en file d'attente et seront exécutées ultérieurement, de manière à ne pas bloquer l'exécution du script principal.

Comment créer des QueueWorker dans Drupal ? La photo montre des lignes d'un code Web

Qu’est-ce qu’un QueueWorker ?

Avec la version 8 de Drupal (sortie en 2015), un nouveau concept central a vu le jour : les plugins. À ne pas confondre avec les modules. Les plugins semblent être destinés à remplacer les hooks historiques de Drupal.

Les plugins vous permettent d'ajouter des fonctionnalités aux éléments existants de votre site Web en créant des classes selon certaines normes. Ce système doit donc être utilisé pour les éléments récurrents, et peut donc continuer à être utilisé pour de nombreuses fonctionnalités, en utilisant des méthodes communes connues de tous les développeurs Drupal 8+, les différents éléments du système peuvent évoluer. Nous allons nous intéresser à un certain type de plugins, les Queue Workers.

Un QueueWorker est donc un certain type de plugin dont le rôle est simplement de traiter une liste d'éléments un par un. Une analogie pourrait être un tapis roulant sur une caisse dans un supermarché, le caissier passe en revue chaque article sur le tapis pour les scanner.

Les QueueWorkers sont pratiques dans Drupal pour regrouper de grandes opérations, comme l'envoi d'e-mails à plusieurs personnes. En utilisant un QueueWorker, vous essayez d'éviter de surcharger les ressources du serveur, ce qui pourrait entraîner la mise hors ligne du site jusqu'à ce que les ressources du serveur soient libérées.

Afin de réaliser cela, le QueueWorker utilise le cron API de Drupal, en remplacement du hook_cron_queue_info(). Les QueueWorkers ne sont pas des cron, mais le cron peut les exécuter.

 

 

Comment créer un QueueWorker ? Les bases

Pour créer un QueueWorker, il faut :

  • Créer une classe php dans le dossier src/Plugin/QueueWorker de notre module.
  • Cette classe doit implémenter "Drupal\Core\Queue\QueueWorkerInterface" ( = étend QueueWorkerBase) et donc définir une méthode public function processItem($data). Qui est le cœur du QueueWorker.
  • Cette classe doit être annotée d'une définition @QueueWorker qui doit contenir :
    • un id,
    • un titre,
    • un paramètre cron (optionnel) qui va définir le temps par défaut passé par le QueueWorker avant de s'arrêter.

Une fois cela fait, le plugin sera trouvé et utilisable par Drupal (lorsqu'une mise en queue se fera sur l'ID du QueueWorker, il traitera les éléments).


/**
 * Send renew mail to advert owner.
 *
 * @QueueWorker(
 *   id = "module_global_library_expiracy_warning",
 *   title = @Translation("Envoi mensuel à une bibliothèque de la liste de ses
 *   abonnements expirant ce mois"), cron = {"time" = 10}
 * )
 */
class LibraryMonthlyExpiracyWarningQueueWorker extends QueueWorkerBase implements ContainerFactoryPluginInterface {

  /**
   * @var \Drupal\module_global\Manager\ExpiracyWarningManager
   */
  protected $expiracyWarningManager;

  /**
   * {@inheritdoc}
   */
  public function processItem($libraryNid) {
    $this->expiracyWarningManager->sendMonthlyLibraryExpiracyWarning($libraryNid);
  }

 

Le QueueWorker en action

Pour notre exemple, nous mettons en place une fonctionnalité qui aura en charge de vérifier toutes les 30 minutes si la page d'accueil de plusieurs sites Web (projets), que nous avons définis, n'est pas hors service. C'est dans ce contexte que nous allons utiliser notre QueueWorker custom, car selon le nombre de projets cela peut affecter les performances de notre propre site.

Nous utilisons le hook_cron. Le hook est appelé par le système lorsque la tâche cron est exécutée, et il permet aux modules de planifier des tâches ou d'effectuer des actions pendant l'exécution du cron.


/**
 * Implements hook_cron().
 */
function my_module_projects_monitoring_cron()
{
  $currentTime = Drupal::time()->getRequestTime();

  /** @var ProjectGateway $gateway */
  $gateway = Drupal::service('Drupal\my_module_projects_monitoring\Gateway\ProjectGateway');
  $last_run = Drupal::state()->get('my_module_projects_monitoring.last_run', 0);


  // Check home page of site and return the status code every 30 min
  $last_run_status_code = Drupal::state()->get('my_module_projects_monitoring.last_status_run', 0);
  if(($currentTime - $last_run_status_code) > 1800) {

    Drupal::logger('cron')->notice('Request homepage projects !');
    Drupal::state()->set('my_module_projects_monitoring.last_status_run', $currentTime);

   // Service Queue Worker
   $queue = Drupal::service('Drupal\my_module_projects_monitoring\Plugin\QueueWorker\StatusCodeReportQueueWorker'); 

    // Get all projects and call to queue worker
    $projects = $gateway->getAllProjects();
    foreach ($projects as $project) {
       $queue->createItem($project->id());
     }

  }
}
  1. La première étape consiste à récupérer l'heure courante en utilisant Drupal::time()->getRequestTime(), qui retourne un timestamp Unix représentant l'heure actuelle ;
  2. Ensuite, la fonction récupère une instance du service ProjectGateway en utilisant. Ce service permet de récupérer les sites et les rapports d'état associés ;
  3. La fonction récupère également le timestamp de la dernière vérification du code d'état de la page d'accueil, en utilisant Drupal::state()->get('my_module_projects_monitoring.last_status_run', 0). Le second argument (0) indique que si aucune valeur n'est trouvée dans l'état, la valeur 0 sera retournée à la place ;
  4. Si l'heure courante moins le timestamp de la dernière vérification du code d'état est supérieure à 30 minutes (1800 secondes), on met à jour le timestamp de la dernière vérification du code d'état en utilisant Drupal::state()->set('my_module_projects_monitoring.last_run', $currentTime). Cette étape permet de planifier la vérification du code d'état de la page d'accueil, qui ne sera exécutée qu'une fois toutes les 30 minutes ;
  5. On parcourt tous les projets et ajoute un élément au QueueWorker pour chaque projet en passant en argument l'id du projet via la fonction createItem, afin que le code d'état de la page d'accueil du site puisse être vérifié par la suite.

namespace Drupal\my_module_projects_monitoring\Plugin\QueueWorker;

use Drupal;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Queue\QueueWorkerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Plugin implementation of the status_code_report queueworker.
 *
 * @QueueWorker (
 *   id = "status_code_report",
 *   title = @Translation("projects status code data"),
 *   cron = {"time" = 50}
 * )
 */
class StatusCodeReportQueueWorker extends QueueWorkerBase implements ContainerFactoryPluginInterface
{


  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition
    );
  }

  /**
   * {@inheritdoc}
   */
  public function processItem($projectId) {
  // Je recommande l'injection de dépendance, mais pour l'exemple j'appel un service en statique
  /** @var ProjectService $projectService */
  $projectService = Drupal::service('Drupal\my_module\services\ProjectService');

  // Méthode de mon service qui fait un appel http avec Guzzle et qui envoie des alertes si la réponse n'est pas bonne (différent de 200)
  $projectService->checkStatusCodeOfSite((int)$projectId);

    Drupal::logger('cron')->notice('Run queue for status code project !');
  }

}

Voici notre QueueWorker.

La fonction processItem est une méthode de traitement des éléments d'une file d'attente dans Drupal. Elle est appelée pour chaque élément de la file d'attente lorsque la file d'attente est traitée.

La première étape consiste à appeler un service qui permet de gérer les projets et les rapports d'état associés. $projectId est le paramètre passé à la fonction, qui représente l'identifiant du projet pour lequel la vérification du code d'état de la page d'accueil doit être effectuée. Lorsque cette tâche sera exécutée, elle enregistrera un message dans les journaux de Drupal.

Une fois tout cela fait, dans notre liste de sites, nous pouvons savoir avec un code couleur par exemple si un site web est hors service ou non de manière automatique.

Pour en savoir plus sur les QueueWorkers, je vous conseille 2 autres articles :

Cet article vous a été utile ? Je vous conseille de lire également les articles suivants :

 

Crédit photo : Maximusnd

Image mise en avant pour l'article
Aimad Bachar
Développeur Web
Webinar
Drupal 10, qu'est-ce qui change concrètement ?
Voir le webinar !
Vous maitrisez la technologie Drupal et vous souhaitez participer à une aventure humaine ?
Adimeo recrute un Développeur Drupal Senior capable d’encadrer des projets et d’accompagner des développeurs juniors...
Voir notre offre d'emploi !