Skip to main content

Qu’est-ce que once()

once() est un helper global qui exécute un callback puis met en cache son résultat en mémoire pour la durée de la requête. Si le même callback est rappelé depuis le même point d’appel, la valeur mise en cache est renvoyée.
Lorsqu’il est appelé depuis une méthode d’instance d’un objet, le cache est indépendant pour chaque instance.
Ce mécanisme d’un cache indépendant « par point d’appel et par instance » diffère d’un helper de mémoïsation simple comme memoize. Pour comprendre son fonctionnement, il faut examiner les deux classes Illuminate\Support\Once et Illuminate\Support\Onceable.

Appel dans helpers.php

Le corps de la fonction once() dans Illuminate\Support\helpers.php est très simple.
Le point clé est que once() lui-même ne stocke aucun cache : à partir des informations sur l’appelant obtenues via debug_backtrace(), il construit une instance de Onceable et délègue le traitement à la classe Once.

Onceable — Calcul du hash identifiant le point d’appel

La classe Onceable a pour rôle de calculer, à partir de la backtrace, un hash identifiant de façon unique « depuis quel point d’appel » l’appel provient.
Les informations qui servent à calculer le hash sont : chemin du fichier + nom de classe + nom de fonction + numéro de ligne + valeurs des variables capturées (use) par la closure. Autrement dit, même pour un once() appelé depuis la même ligne, si la valeur d’une variable capturée via use change, le cache est considéré comme différent.
Si once() est appelé depuis du code évalué via eval(), hashFromTrace() renvoie null et aucun cache n’est effectué. C’est une garde de sécurité pour les exécutions passant par eval, comme les vues Blade compilées.
object indique si « l’appelant est une méthode d’instance ». $trace[1]['object'] correspond à un niveau au-dessus dans debug_backtrace() (le code qui a appelé once()) : dans une méthode d’instance il contient $this, tandis que dans une méthode statique ou une fonction globale il vaut null.

Once — Le stockage de cache basé sur WeakMap

Le stockage effectif du cache est assuré par Illuminate\Support\Once.
La technique clé est WeakMap. $onceable->object (l’instance appelante) sert de clé, associée à un tableau associatif hash → résultat. Lorsqu’il n’y a pas d’object (fonction globale ou méthode statique), c’est le $this de Once lui-même qui sert de clé, ce qui constitue en pratique un espace de cache unique partagé pour tout le processus.
Grâce à WeakMap, dès qu’il n’existe plus d’autre référence à l’objet servant de clé, le cache qui lui est associé devient éligible au ramasse-miettes. La conception rend donc peu probable une fuite mémoire, même en appelant once() depuis un grand nombre d’objets.

Utilisation dans les tests — enable / disable / flush

Appeler Once::disable() désactive complètement le cache : once() exécute alors le callback à chaque appel. Utile pour les tests où « on veut à chaque fois une nouvelle valeur ».
Once::flush() détruit l’intégralité du cache : au prochain accès, un nouveau WeakMap sera créé. C’est utile pour tester des commandes Artisan ou pour éviter de conserver le cache d’une requête à l’autre dans un environnement où le processus est réutilisé, comme Octane.

PreventsCircularRecursion — Un exemple d’utilisation d’Onceable

Onceable::tryFromTrace() n’est pas réservé à once() : il est également réutilisé par le trait Illuminate\Database\Eloquent\Concerns\PreventsCircularRecursion d’Eloquent. Ce trait sert à éviter qu’un même point d’appel, sur un même objet, ne soit ré-entrant au sein d’une même pile d’appels.
La différence décisive avec Once est que le cache est nettoyé après l’appel dans le bloc finally. Là où Once « met en cache de façon persistante pendant la requête », PreventsCircularRecursion réutilise le même mécanisme Onceable pour « mettre en cache (en pratique verrouiller) uniquement pendant la pile d’appels en cours d’exécution ». Un cas d’usage typique dans les modèles Eloquent : empêcher qu’une méthode toArray() ou un accesseur se référence lui-même de façon récursive.

Utilisation en développement de packages

Si, dans votre package, vous souhaitez « n’exécuter qu’une seule fois par requête un appel donné sur une même instance », le plus simple est d’utiliser directement once(). Utiliser Onceable/Once directement est rare, mais comprendre l’implémentation interne est utile dans les cas suivants :
  • Lorsque, dans une commande Artisan ou un test, vous souhaitez contrôler le comportement du cache avec Once::disable() / Once::flush()
  • Lorsque vous voulez implémenter, dans votre propre trait, un cache « par point d’appel » ou une protection contre la ré-entrance (le même pattern que PreventsCircularRecursion est réutilisable)
  • Lorsqu’un résultat de once() ne correspond pas à vos attentes et que vous devez déboguer : connaître les éléments qui composent le hash (fichier, classe, fonction, ligne, variables use) facilite l’identification de la cause
Comme once() inclut dans le hash les variables capturées via use par la closure, passer dans une boucle une closure qui capture à chaque itération une valeur différente peut créer, involontairement, un cache différent à chaque tour — le résultat n’est alors, en pratique, pas mis en cache. Lorsque vous l’utilisez dans une boucle, vérifiez que la granularité de la clé de cache correspond bien à votre intention.

Pages associées

Helper tap() et trait Tappable

Le pattern d’implémentation du helper tap(), qui glisse un effet de bord tout en renvoyant une valeur.

Observers Eloquent et événements de modèle

Gestion centralisée via les événements et les observers des modèles Eloquent.
Dernière modification le 11 septembre 2026