# Chaîner des tâches avec Action Scheduler : groupes et reprises après échec

> Générer un rapport annuel suppose d'agréger des données, produire un PDF puis l'envoyer par courriel, trois étapes qui doivent s'enchaîner même si l'une d'elles échoue une première fois.

- Auteur : Clément Hadrot
- Publié le : 2022-03-22
- Mis à jour le : 2022-03-22
- Catégorie : Extensions
- URL : https://wpmoderne.dev.wordpress-developpement.fr/extensions/chainer-taches-action-scheduler-groupes-reprises/

## L’essentiel

- Un groupe nomme une famille de tâches, utile pour le suivi et l'annulation
- Chaque étape déclenche la suivante via une action dédiée à la fin de son propre callback
- Un identifiant de lot transmis en argument relie les étapes entre elles

Une extension de gestion associative doit produire, une fois par an, un rapport d'activité par adhérent : agrégation de plusieurs mois de données de participation, génération d'un PDF mis en forme, puis envoi par courriel avec le fichier en pièce jointe. Pour quelques dizaines d'adhérents, tout tiendrait dans une seule tâche cron. Pour les plus grosses fédérations qui utilisent l'extension, avec plusieurs milliers d'adhérents, chaque étape prise séparément peut déjà dépasser plusieurs minutes, et l'ensemble ne peut raisonnablement pas s'exécuter dans une seule requête HTTP ni dans un seul appel WP-Cron classique.

Action Scheduler, la bibliothèque devenue standard de facto dans l'écosystème WordPress notamment via WooCommerce, permet de dépasser le simple remplacement du cron natif pour construire une véritable chaîne de traitements avec dépendances entre étapes, chacune reprise indépendamment en cas d'échec.

## Le principe de la chaîne

Plutôt que de tout exécuter dans un seul hook, chaque étape planifie elle-même l'étape suivante à sa fin, en lui transmettant un identifiant de lot commun qui permet de relier les données entre les différentes tâches. Cette approche découple totalement la durée et la fiabilité de chaque étape : si l'envoi du courriel échoue pour un adhérent, cela ne remet pas en cause l'agrégation déjà réalisée pour les autres.

## Mise en place

> L'essentiel à retenir : Un groupe nomme une famille de tâches, utile pour le suivi et l'annulation ; Chaque étape déclenche la suivante via une action dédiée à la fin de son propre callback ; Un identifiant de lot transmis en argument relie les étapes entre elles

```
function lancer_generation_rapports_annuels() {
    $lot_id = wp_generate_uuid4();

    as_schedule_single_action(
        time(),
        'rapport_etape_agregation',
        array( 'lot_id' => $lot_id, 'offset' => 0 ),
        'rapports-annuels'
    );
}

add_action( 'rapport_etape_agregation', function( $lot_id, $offset ) {
    $adherents = get_adherents_par_lot( $offset, 50 );

    foreach ( $adherents as $adherent ) {
        $donnees = agreger_activite_adherent( $adherent->ID );
        set_transient( "rapport_donnees_{$lot_id}_{$adherent->ID}", $donnees, DAY_IN_SECONDS );

        as_schedule_single_action(
            time(),
            'rapport_etape_pdf',
            array( 'lot_id' => $lot_id, 'adherent_id' => $adherent->ID ),
            'rapports-annuels'
        );
    }

    if ( count( $adherents ) === 50 ) {
        as_schedule_single_action(
            time() + 30,
            'rapport_etape_agregation',
            array( 'lot_id' => $lot_id, 'offset' => $offset + 50 ),
            'rapports-annuels'
        );
    }
}, 10, 2 );
```

Chaque appel traite un lot de cinquante adhérents, planifie la génération du PDF pour chacun d'eux, puis, s'il reste potentiellement d'autres adhérents à traiter, planifie sa propre poursuite trente secondes plus tard avec un offset incrémenté. Ce découpage en petits lots évite qu'une seule tâche ne s'exécute trop longtemps et ne dépasse le temps maximal alloué par le serveur.

```
add_action( 'rapport_etape_pdf', function( $lot_id, $adherent_id ) {
    $donnees = get_transient( "rapport_donnees_{$lot_id}_{$adherent_id}" );

    if ( false === $donnees ) {
        throw new Exception( "Données introuvables pour l'adhérent {$adherent_id}, lot {$lot_id}" );
    }

    $chemin_pdf = generer_pdf_rapport( $adherent_id, $donnees );

    as_schedule_single_action(
        time(),
        'rapport_etape_envoi',
        array( 'adherent_id' => $adherent_id, 'chemin_pdf' => $chemin_pdf ),
        'rapports-annuels'
    );
}, 10, 2 );
```

Lever une exception explicite quand les données attendues manquent, plutôt que d'échouer silencieusement, est essentiel : Action Scheduler capture cette exception et marque la tâche en échec dans son propre historique, consultable depuis l'écran `Outils > Action planifiées`, avec la possibilité de relancer manuellement l'action en échec sans avoir à retoucher le code.

## Pourquoi utiliser un groupe nommé

Le quatrième argument de `as_schedule_single_action()`, ici `rapports-annuels`, nomme un groupe qui regroupe logiquement toutes les tâches de cette chaîne. Ce groupe sert à deux choses concrètes : filtrer l'écran d'administration des actions planifiées pour ne voir que les tâches de cette famille, et permettre une annulation groupée avec `as_unschedule_all_actions()` si un administrateur doit interrompre l'ensemble du processus, par exemple parce qu'une erreur de données a été détectée après le lancement.

### Bonnes pratiques observées sur ce projet

- Toujours transmettre un identifiant de lot, jamais reconstruire un contexte à partir de suppositions sur l'ordre d'exécution des tâches.
- Nettoyer les transients temporaires utilisés pour transmettre des données entre étapes, avec une durée de vie courte mais suffisante pour couvrir un éventuel délai de reprise après échec.
- Surveiller la table `wp_actionscheduler_actions` en cas de doute sur le volume de tâches en attente, plutôt que de se fier uniquement à l'écran d'administration qui pagine les résultats.

> Une chaîne de tâches qui suppose que chaque étape réussira du premier coup n'est pas une architecture asynchrone, c'est un espoir optimiste.

## En résumé

Action Scheduler permet de construire des chaînes de traitement à plusieurs étapes bien au-delà du simple remplacement de WP-Cron, à condition de découper le travail en petits lots, de transmettre un identifiant de corrélation entre les étapes, et de laisser les exceptions remonter pour profiter du mécanisme natif de reprise après échec offert par la bibliothèque.
