# Un WeakMap PHP 8 pour associer une donnée temporaire à un WP_Post sans fuite

> Sur un traitement en boucle qui doit mémoriser un état par objet sans empêcher le ramasse-miettes de le libérer, WeakMap remplace un tableau indexé par ID.

- Auteur : Clément Hadrot
- Publié le : 2024-12-28
- Mis à jour le : 2024-12-28
- Catégorie : Extensions
- URL : https://wpmoderne.dev.wordpress-developpement.fr/extensions/weakmap-php8-donnee-temporaire-wp-post/

## L’essentiel

- Un tableau indexé par ID d'objet retient les objets en mémoire indéfiniment
- WeakMap laisse le ramasse-miettes libérer l'objet dès qu'il n'est plus utilisé ailleurs
- Utile pour un cache de calcul temporaire, pas pour une persistance longue durée

128 mégaoctets consommés pour traiter trois mille articles : c'est le constat qui a mené à revoir un traitement d'export dans une extension éditoriale. Le code parcourait une liste de `WP_Post`, calculait un score de lisibilité coûteux pour chacun, et mémorisait ce score dans un tableau PHP indexé par identifiant d'article pour éviter de le recalculer si le même post repassait dans une boucle imbriquée plus loin dans le traitement.

Le problème ne venait pas du calcul lui-même, mais du tableau de mémorisation : en conservant l'identifiant de chaque article, ce tableau grossissait indéfiniment sur toute la durée du script, sans jamais être nettoyé, même une fois le traitement d'un article terminé. Sur un grand export, la consommation mémoire montait en flèche pour un gain de performance qui, au fond, n'avait besoin d'exister que le temps du traitement d'un seul article.

## Le tableau associatif classique, et sa limite

La version initiale ressemblait à ceci : un tableau PHP simple, indexé par l'identifiant du post, qui sert de cache de calcul le temps de la boucle.

```
$scores_lisibilite = array();

foreach ( $articles as $article ) {
    if ( ! isset( $scores_lisibilite[ $article->ID ] ) ) {
        $scores_lisibilite[ $article->ID ] = calculer_score_lisibilite( $article->post_content );
    }

    traiter_article( $article, $scores_lisibilite[ $article->ID ] );
}
```

Ce code fonctionne, mais le tableau `$scores_lisibilite` reste entièrement en mémoire jusqu'à la fin du script, même pour des articles déjà traités et qui ne seront plus jamais consultés. Sur un traitement de plusieurs dizaines de milliers d'articles, cette accumulation devient significative, surtout si l'objet `WP_Post` associé transporte lui-même un contenu volumineux.

## Ce qu'apporte la classe WeakMap

> L'essentiel à retenir : Un tableau indexé par ID d'objet retient les objets en mémoire indéfiniment ; WeakMap laisse le ramasse-miettes libérer l'objet dès qu'il n'est plus utilisé ailleurs ; Utile pour un cache de calcul temporaire, pas pour une persistance longue durée

PHP 8.0 introduit la classe `WeakMap`, qui permet d'associer une donnée à un objet sans empêcher le ramasse-miettes de libérer cet objet dès qu'il n'existe plus de référence forte ailleurs dans le script. Contrairement à un tableau indexé par identifiant, une `WeakMap` utilise l'objet lui-même comme clé, et l'entrée disparaît automatiquement quand cet objet est détruit.

```
$scores_lisibilite = new WeakMap();

foreach ( $articles as $article ) {
    if ( ! isset( $scores_lisibilite[ $article ] ) ) {
        $scores_lisibilite[ $article ] = calculer_score_lisibilite( $article->post_content );
    }

    traiter_article( $article, $scores_lisibilite[ $article ] );

    unset( $article ); // dès qu'aucune autre référence ne subsiste, l'entrée est libérée
}
```

La différence essentielle tient dans la nature de la clé : ce n'est plus un entier stable qui s'accumule indéfiniment, mais l'objet `WP_Post` lui-même. Une fois que ce post sort de portée, la `WeakMap` ne le retient pas artificiellement en vie, et l'entrée correspondante disparaît d'elle-même.

### Un piège à connaître : la source des objets WP_Post

Ce mécanisme ne fonctionne correctement que si les objets `WP_Post` traités sont bien des instances distinctes à chaque itération, et non des références vers un unique objet réutilisé. Avec `get_posts()` ou une boucle `WP_Query` classique, chaque itération fournit une instance propre, ce qui rend la `WeakMap` pertinente. Le comportement serait différent avec un objet global partagé et modifié en place.

## Quand préférer un tableau simple malgré tout

La `WeakMap` n'est pas une solution universelle. Si la donnée associée doit survivre au-delà de la requête ou être partagée entre plusieurs traitements successifs, un tableau classique, voire un transient ou une table dédiée, reste plus approprié. La `WeakMap` répond spécifiquement au cas d'un état de calcul strictement temporaire, borné à la durée de vie de l'objet auquel il se rattache.

- Utiliser `WeakMap` pour un cache de calcul local à une boucle de traitement, jamais pour une donnée qui doit persister.
- Vérifier que les objets utilisés comme clés sont bien des instances distinctes, pas un objet global réutilisé.
- Ne pas confondre avec `WeakReference`, une classe complémentaire qui référence un objet unique sans l'associer à une donnée.

## Mesurer avant de généraliser

Sur le traitement d'export mentionné plus haut, remplacer le tableau associatif par une `WeakMap` a permis de stabiliser la consommation mémoire au lieu qu'elle grimpe linéairement avec le nombre d'articles traités. Le gain dépend directement du volume traité et du poids de la donnée associée : sur une poignée d'articles, la différence est négligeable.

> Avant d'introduire une WeakMap dans un traitement critique, vérifiez avec `memory_get_peak_usage()` que le tableau associatif est bien la cause du problème, et non le contenu des articles eux-mêmes.

## En résumé

La classe `WeakMap` de PHP 8 répond à un besoin précis : associer une donnée temporaire à un objet sans empêcher son ramasse-miettes naturel. Ce n'est pas un mécanisme de mise en cache persistante, mais un outil de maîtrise mémoire pour des traitements en boucle où un tableau indexé par identifiant deviendrait, avec le temps, une fuite mémoire déguisée en optimisation.
