Le WordPress d'aujourd'hui, décodé pour les développeurs

Performance

json_validate() de PHP 8.3 pour ne jamais mettre en cache une réponse malformée

PHP 8.3 introduit json_validate(). L'utiliser avant d'écrire dans le cache d'objet évite qu'une réponse tronquée y reste stockée jusqu'à expiration.

Par Clément Hadrot • 14 janvier 2024 • 5 min de lecture • Aucun commentaire
json_validate() de PHP 8.3 pour ne jamais mettre en cache une réponse malformée

json_validate($reponse) : cette fonction, ajoutée dans PHP 8.3 sorti en novembre 2023, répond à un besoin précis qui manquait depuis longtemps. Avant elle, la seule façon de vérifier qu’une chaîne était un JSON valide consistait à la décoder avec json_decode() puis à vérifier que json_last_error() renvoyait bien JSON_ERROR_NONE, ce qui obligeait à construire toute la structure de données en mémoire même quand on n’avait besoin que d’une réponse booléenne.

Sur un site WordPress qui interroge une API externe et met le résultat en cache d’objet via wp_cache_set(), ce détail a une conséquence directe : si la réponse arrive tronquée (connexion coupée, timeout côté API, proxy qui coupe le flux), elle peut malgré tout être écrite dans le cache si aucune vérification n’est faite avant l’appel, et y rester jusqu’à l’expiration du TTL fixé, parfois plusieurs heures.

Le problème observé sans validation

Le scénario typique : une fonction récupère une réponse via wp_remote_get(), extrait le corps avec wp_remote_retrieve_body(), et le stocke tel quel en cache sans vérifier sa validité. Si l’API distante répond avec un corps vide ou une page d’erreur HTML au lieu du JSON attendu à cause d’une panne temporaire, cette réponse invalide est mise en cache exactement comme l’aurait été une réponse correcte. Tous les visiteurs suivants reçoivent alors la même erreur, reproduite fidèlement, jusqu’à expiration du cache.

Le correctif avec json_validate()

function recuperer_donnees_partenaire(string $endpoint): ?array {
    $cle = 'partenaire_' . md5($endpoint);
    $cache = wp_cache_get($cle, 'api_partenaire');

    if (false !== $cache) {
        return $cache;
    }

    $reponse = wp_remote_get($endpoint, ['timeout' => 5]);

    if (is_wp_error($reponse)) {
        return null;
    }

    $corps = wp_remote_retrieve_body($reponse);

    if (!json_validate($corps)) {
        return null;
    }

    $donnees = json_decode($corps, true);
    wp_cache_set($cle, $donnees, 'api_partenaire', 15 * MINUTE_IN_SECONDS);

    return $donnees;
}
L'essentiel à retenir : json_validate() vérifie sans décoder, donc sans allouer la structure complète ; Une réponse invalide ne doit jamais atteindre wp_cache_set ; Le correctif tient en une condition avant l'écriture

Pourquoi cette fonction est plus légère qu’un decode complet

La documentation officielle de PHP précise que json_validate() réutilise le même analyseur syntaxique que json_decode(), mais sans construire la structure de données correspondante en mémoire. Sur une réponse volumineuse, un catalogue de plusieurs centaines d’entrées par exemple, l’économie de mémoire et de temps processeur devient mesurable, alors qu’elle reste négligeable sur une petite réponse de quelques lignes.

Un piège à éviter malgré tout

Valider la structure JSON ne garantit pas que son contenu correspond au schéma attendu par l’application. Une réponse peut être un JSON parfaitement valide tout en étant un objet vide ou une liste sans les clés attendues. La validation de forme via json_validate() et la validation de contenu (vérifier la présence des clés utiles) restent deux étapes distinctes, la seconde devant toujours suivre la première.

Compatibilité et repli pour PHP antérieur

Comme json_validate() n’existe qu’à partir de PHP 8.3, un projet devant encore supporter PHP 8.1 ou 8.2 doit prévoir un repli manuel, généralement basé sur un appel à json_decode() suivi d’une vérification de json_last_error(), en acceptant le léger surcoût mémoire que cela implique sur ces versions plus anciennes.

Où placer cette vérification dans une architecture plus large

Sur un projet qui interroge plusieurs API partenaires distinctes, il est tentant de dupliquer cette vérification dans chaque fonction d’appel. Une approche plus propre consiste à centraliser la logique de récupération et de mise en cache dans une seule classe utilitaire, appelée par chaque intégration, de sorte que la vérification via json_validate() ne soit écrite et testée qu’à un seul endroit du code.

final class ClientApiEnCache {
    public function get(string $url, string $groupe, int $ttl): ?array {
        $cle = 'appel_' . md5($url);
        $cache = wp_cache_get($cle, $groupe);

        if (false !== $cache) {
            return $cache;
        }

        $reponse = wp_remote_get($url, ['timeout' => 5]);
        if (is_wp_error($reponse)) {
            return null;
        }

        $corps = wp_remote_retrieve_body($reponse);
        if (!json_validate($corps)) {
            return null;
        }

        $donnees = json_decode($corps, true);
        wp_cache_set($cle, $donnees, $groupe, $ttl);
        return $donnees;
    }
}

Ce découpage facilite aussi les tests automatisés : il suffit de simuler une réponse tronquée pour vérifier que la classe refuse bien de la mettre en cache, sans avoir à reproduire une vraie panne réseau côté API partenaire.

En résumé

Ajouter une vérification de validité JSON avant toute écriture en cache d’objet est une précaution peu coûteuse qui évite un scénario particulièrement pénible : une panne temporaire d’une API tierce qui se transforme, via le cache, en panne prolongée et invisible côté serveur d’origine. Avec PHP 8.3, cette vérification devient à la fois plus simple à écrire et plus légère à exécuter.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi