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.
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 fonctiononce() dans Illuminate\Support\helpers.php est très simple.
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 classeOnceable 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.
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.
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é parIlluminate\Support\Once.
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
AppelerOnce::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.
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 directementonce(). 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
PreventsCircularRecursionest 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, variablesuse) facilite l’identification de la cause
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.