# Antipattern : appeler wp_remote_get en boucle dans un callback REST

> Fatal error, maximum execution time dépassé, sur une route consultée par seulement quelques dizaines de visiteurs. La cause tenait en une boucle d'appels HTTP synchrones oubliée dans un callback.

- Auteur : WordPress Développement
- Publié le : 2021-03-04
- Mis à jour le : 2021-03-04
- Catégorie : Headless &amp; API
- URL : https://www.wpmoderne.fr/headless/antipattern-wp-remote-get-boucle-callback-rest/

## L’essentiel

- Un appel HTTP synchrone dans une boucle bloque le thread PHP entier
- wp_remote_get ne fait aucune requête en parallèle par défaut
- Regrouper les appels ou les mettre en cache change tout

`Fatal error: Maximum execution time of 30 seconds exceeded` : ce message est apparu dans les journaux d'une PME du bâtiment dont le site headless croisait des données internes de chantiers avec un service météo externe pour afficher des alertes sur ses fiches d'intervention. La route REST en question n'était pourtant consultée que par une poignée d'utilisateurs internes chaque jour.

Ce genre d'erreur, bien identifiée une fois qu'on la rencontre, provient presque toujours du même antipattern : un appel HTTP sortant, synchrone, placé à l'intérieur d'une boucle qui itère sur plusieurs éléments avant de retourner la réponse REST.

## Ce qu'on observe dans le code fautif

La callback en cause récupérait une liste de chantiers, puis interrogeait un service météo externe pour chacun d'eux, un par un, avant de construire la réponse finale :

```
function chantiers_get_avec_meteo( $request ) {
    $chantiers = get_posts( array( 'post_type' => 'chantier', 'numberposts' => 40 ) );
    $result = array();

    foreach ( $chantiers as $chantier ) {
        $meteo = wp_remote_get(
            'https://service-meteo-exemple.fr/api?lieu=' . urlencode( get_post_meta( $chantier->ID, 'lieu', true ) )
        );
        $result[] = array(
            'id'    => $chantier->ID,
            'meteo' => wp_remote_retrieve_body( $meteo ),
        );
    }
    return rest_ensure_response( $result );
}
```

Avec quarante chantiers et un temps de réponse moyen de huit cents millisecondes par appel au service météo, la boucle complète dépassait allègrement la limite d'exécution PHP par défaut de trente secondes, provoquant l'erreur fatale observée.

## Pourquoi ça bloque tout le thread PHP

`wp_remote_get()` effectue une requête HTTP bloquante : le processus PHP attend la réponse complète avant de passer à l'itération suivante de la boucle. Aucun parallélisme n'existe par défaut, et chaque appel supplémentaire s'additionne strictement au temps total, sans possibilité de chevauchement.

> L'essentiel à retenir : Un appel HTTP synchrone dans une boucle bloque le thread PHP entier ; wp_remote_get ne fait aucune requête en parallèle par défaut ; Regrouper les appels ou les mettre en cache change tout

## Quoi faire à la place

### Regrouper les appels quand le service le permet

De nombreux services externes acceptent un appel unique avec une liste de paramètres plutôt qu'un appel par élément. Vérifier cette possibilité avant d'écrire la moindre boucle évite le problème à la racine.

### Mettre en cache la réponse externe

```
function chantier_get_meteo_cache( $lieu ) {
    $cle = 'meteo_' . md5( $lieu );
    $cache = get_transient( $cle );
    if ( false !== $cache ) {
        return $cache;
    }

    $reponse = wp_remote_get( 'https://service-meteo-exemple.fr/api?lieu=' . urlencode( $lieu ) );
    $corps   = wp_remote_retrieve_body( $reponse );
    set_transient( $cle, $corps, 15 * MINUTE_IN_SECONDS );
    return $corps;
}
```

Une donnée météo n'a pas besoin d'être rafraîchie à chaque appel de la route : un transitoire de quinze minutes suffit largement à l'usage réel, et réduit drastiquement le nombre d'appels sortants réellement exécutés.

### Déporter l'appel hors du cycle de requête

Une tâche cron planifiée, exécutée toutes les vingt minutes, peut mettre à jour la météo de tous les chantiers en arrière-plan, laissant la route REST se contenter de lire une donnée déjà disponible en base, sans aucun appel sortant au moment de la requête utilisateur.

- Grouper les appels externes quand le service le permet
- Mettre en cache toute réponse externe qui n'a pas besoin d'être instantanée
- Déplacer les appels coûteux vers une tâche planifiée plutôt que le cycle de requête

> Un appel HTTP sortant placé dans une boucle n'est jamais un détail d'implémentation ; c'est une dépendance directe entre la disponibilité de son propre site et celle d'un service qu'on ne maîtrise pas.

## En résumé

Ce que révèle cet incident, c'est la nécessité de traiter tout appel HTTP sortant dans un callback REST comme une opération potentiellement lente et fragile, jamais comme une simple ligne de code interchangeable. Cet article ne couvre pas le cache d'objets WordPress, qui apporterait une réponse complémentaire pour d'autres types de données, internes celles-là.
