drupal

Drupal 7 : Utilisation du Batch API et du Cron

Nous vous montrons l'utilisation du Batch API et du Cron

Version de Drupal : 7.12

Version de Background Process : 7.x-1.12

J’ai récemment travaillé sur un module d’extraction de données en CSV pour le Comparateur d’assurance d’Assor Russie. Face à une telle quantité de données à traiter, il est intéressant d’automatiser le traitement par le biais d’un cron et l’utilisation d’un batch, fonctionnalité proposée par Drupal avec son Batch API.

Etape 1 : création de l’url par le hook_menu

Premièrement, il est nécessaire d’implémenter le hook_menu pour accéder au batch par une url.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * Implémentation du hook_menu pour accéder au batch
 * @return array
 */
function monmodule_menu()
{
    return array(
        'monbatch' => array( //la clé sert d'url
            'title' => 'Titre',
            'description' => 'Description',
            'page callback' => '_monmodule_initbatch',
            'access callback' => true,
            'type' => MENU_NORMAL_ITEM
        )
    );
}

Pour plus d’informations pour l’implémentation du hook_menu, voici le lien vers la documentation officielle.

Etape 2 : fonction d’initialisation du batch

Maintenant que nous avons l’url d’appel au batch, il faut maintenant coder la fonction d’initialisation du batch. Dans cet exemple, j’ai appelé cette fonction _monmodule_initbatch.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
 * Fonction callback d'initialisation du formulaire pour un
 * appel direct (sans l'utilisation d'un formulaire)
 */
function _monmodule_initbatch()
{
    //Récupération des identifiants des noeuds à traiter
    $nidsStacks = _monmodule_get_nids();
 
    $operations = array();
    foreach($nidsStacks as $stack)
    {
        //1 opération = 1 traitement
        //1 opération = 1 array avec :
        //le nom de la fonction à appeler et les arguments
        //de cette fonction
        $operations[] = array('_monmodule_extract_nodes',
            array($stack));
    }
 
    //Le batch nécessite au moins la liste des opérations
    //La clé 'finished' est utilisée pour exécuter du code
    //à la fin du traitement de toutes les opérations
    $batch = array(
        'operations' => $operations,
        'finished' => '_monmodule_batch_finished'
    );
 
    batch_set($batch);
 
    //Attention, si vous ne précisez pas l'url,
    //le batch va rediriger vers l'url du batch,
    //ce qui engendrera donc une boucle infinie
    //L'appel de cette fonction n'est pas nécessaire
    //dans le cas où vous utilisez un formulaire
    batch_process('node/1');
}

La fonction _monmodule_get_nodes récupère les différentes données à traiter. Dans mon cas d’Assor Russie, j’ai utilisé l’EntityFieldQuery pour récupérer les identifiants des nœuds à extraire afin d’alléger le traitement d’initialisation.

Comme vous avez pu le constater sur les commentaires de la fonction batch_process, il existe deux manières d’exécuter le batch : l’appel de cette fonction ou par submit d’un formulaire. Dans notre cas, nous n’emploierons pas de formulaire car il sera destiné à être appelé par un cron.

Etape 3 : définir le traitement

Le batch étant établi, il reste encore à définir les différents traitements effectués par celui-ci. Bien évidemment, ce code sera spécifique à votre besoin, c’est pourquoi on ne parlera, ici, que de quelques détails.

1
2
3
4
5
6
7
8
9
10
11
/**
 * Fonction de callback d'extraction des nodes
 * @param array $nids nids des nodes à charger
 * @param array $context contient des données contextuelles
 * informatives ou nécessaires à l'exécution du traitement
 */
function _monmodule_extract_nodes($nids, &$context)
{
    //Chargement des nodes à charger...
    $nodes = node_load_multiple($nids);
}

Ce qui nous intéresse dans cette fonction, ce sont ses paramètres. Comme nous l’avons vu dans la fonction d’initialisation du batch, on fournit à la méthode des données en paramètres. Le callback est appelé par la fonction call_user_func_array, avec les paramètres en array et l’ajout du contexte du batch.

Ainsi, si on fournit 3 paramètres lors de la définition des opérations, la fonction de callback recevra ces variables en paramètres (dans le même ordre) ET le contexte du batch, soit 4 paramètres. Dans notre cas, la fonction reçoit l’array de nid à traiter et le contexte.

Cette variable de contexte est composée de la sorte :

  • sandbox : Cette variable sert de substitut à la session pour le traitement du batch. A utiliser dans le cadre où vous avez besoin de faire communiquer les différentes opérations du batch
  • results : Cette variable sert à stocker les différents résultats de chaque opération. Je déconseille néanmoins son utilisation avec l’emploi d’un cron, mais nous verrons cela plus tard
  • message : Cette variable permet d’afficher un message de progression
  • finished : Float compris entre 0 à 1 correspondant au niveau de complétude de l’opération

Si vous utilisez un batch appelé par navigateur, alors les trois premières variables vous seront utiles. Dans le cadre du cron, seul la première sera vraiment utile.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * Fonction de callback appelée après que le batch
 * ait traité toutes les opérations
 * @param boolean $success indique si toutes les opérations
 * se sont bien déroulées (pas d'erreurs PHP)
 * @param array $results contient les données saisies
 * dans $context['results']
 * @param array $operations
 */
function _monmodule_batch_finished($success, $results, $operations)
{
    if($success)
    {
        drupal_set_message(t('Exécution du batch réussi'));
    }
}

C’est dans cette fonction que l’on voit l’utilité de la variable results du contexte, mais elle posera aussi un problème dans le cadre du cron.

Etape 4 : Adapter le batch pour le cron

A présent, le batch devrait être fonctionnel et accessible à partir de l’url via un navigateur web. Maintenant, on veut que ce batch fonctionne via un cron. Après recherche, il semble que le batch ne peut s’exécuter qu’avec un navigateur : chaque opération est effectuée par un rafraîchissement de la page en PHP ou un appel en AJAX.

Pour cela, nous allons utiliser le module background_process. En plus de proposer une interface de gestion des batchs, ce module permet de lancer un batch sans l’utilisation d’un navigateur. Le module background batch activé, il reste encore à remplacer l’appel de fonction de batch_process par background_batch_process_batch.

1
2
//    batch_process('node/1');
    background_batch_process_batch('node/1');

Attention, si vous êtes sur Windows et que vous tentez d’exécuter votre batch, celui-ci ne fonctionnera pas à cause des urls utilisées par background_process. Pour résoudre ce problème, un patch est proposé dans ce lien. Pour faire court, ce patch remplace tous les bgp:start/ par bgp-start/.

Après avoir vidé les caches (pour les liens du menu de background_process), il reste encore un problème : le callback finished n’est pas appelé, d’où mon conseil de ne pas utiliser la variable results du contexte.

Il ne vous reste alors plus qu’à configurer un cron et le tour est joué.

Ressources

http://drupal.org/project/background_process

http://api.drupal.org/api/examples/batch_example%21batch_example.module/group/batch_example/6

 

Articles liés