v13.36.0, ainsi que celle d’Octane, et relie les méthodes d’enregistrement de base du conteneur de services à la maintenance à long terme d’un package. Choisir scoped() ne suffit pas à réinitialiser toutes les références entre objets ni tous les états statiques.
Choisir la méthode d’enregistrement selon la durée de conservation du service
Ce tableau compare les résolutions classiques. Vérifiez séparément les chemins particuliers, comme la résolution avec paramètres explicites ou le contextual binding.
Même avec
bind(), si vous stockez le service récupéré dans une propriété d’un autre singleton, cette référence persiste. La méthode d’enregistrement est une règle de création et de réutilisation du conteneur : elle n’impose pas la recréation des objets conservés par le code appelant.
scoped combine un enregistrement partagé et le suivi des clés à supprimer
Container::scoped(), tel que nous l’avons examiné, effectue les deux opérations suivantes :
- Il enregistre la clé dans
scopedInstances. - Il appelle
singleton()pour l’enregistrer en tant que liaison partagée.
instances que pour un singleton. forgetScopedInstances() appelle unset() sur les instances stockées pour les clés enregistrées. La logique de création de la liaison est conservée, ce qui permet de créer un nouvel objet lors de la prochaine résolution classique.
Ce traitement ne réinitialise pas les propriétés de l’ancien objet et ne réécrit pas les références détenues par d’autres objets. Un ancien objet encore référencé reste utilisable.
C’est l’environnement d’exécution qui crée la frontière
Le conteneur lui-même ne surveille pas le début des requêtes HTTP ou des jobs.- Worker de file d’attente :
QueueServiceProvidertransmet au Worker un callback qui réinitialise la portée.Worker::daemon()l’appelle dans sa boucle avant de récupérer le job suivant, et ce callback appelle notammentforgetScopedInstances()et vide les instances résolues des Facades. - Octane : la configuration officielle par défaut enregistre
FlushTemporaryContainerInstancessurOperationTerminated. Cet écouteur appelleforgetScopedInstances(). - Commandes résidentes ou boucles personnalisées : répéter simplement
make()ne crée pas de frontière. Le cycle de vie doit être conçu côté infrastructure d’exécution.
Rendre scoped l’état partagé au sein d’un traitement
Prenons l’exemple d’un package de livraison qui souhaite partager l’identifiant de destination au sein d’une requête ou d’un job. Au lieu de conserver directement la Request HTTP, il ne stocke que la valeur nécessaire.handle() d’un job de l’application, définissez l’identifiant après avoir validé l’entrée et vérifié les autorisations. Les autres services qui résolvent ce type dans le même cycle de vie peuvent alors accéder au même context.
scoped() n’assure aucune persistance en base de données ou dans un cache externe. Les valeurs à transmettre au job suivant doivent être passées explicitement dans les données du job.
Avec Octane, les méthodes
register() et boot() des service providers s’exécutent au démarrage initial du worker. Ne concevez pas votre package pour stocker l’utilisateur courant ou la destination dans le context depuis boot(). Définissez les valeurs propres à chaque traitement une fois celui-ci commencé.Les références d’un singleton vers un service scoped ne sont pas mises à jour automatiquement
L’enregistrement suivant ne fonctionne pas comme prévu, même si seul le context est scoped.DeliveryService stocke le context dans une propriété dans son constructeur, il conserve le premier context créé. Même si le conteneur supprime le context stocké, la propriété du DeliveryService déjà créé ne change pas.
Choisissez la solution selon la responsabilité du service.
- Passer les valeurs nécessaires à chaque traitement en argument de méthode. Si aucun état ne doit être partagé, c’est la solution la plus simple.
- Rendre aussi scoped le service qui conserve la référence.
DeliveryServiceet le context sont alors tous deux recréés à chaque traitement. - Passer un resolver qui résout le service au moment de l’utilisation. Si le singleton doit être conservé, récupérez le service nécessaire depuis le conteneur courant à chaque fois.
DeliveryService doit recevoir une Closure dans son constructeur et être modifié pour exposer une API qui l’appelle à chaque opération. Appeler le resolver une seule fois dans le constructeur et en stocker le résultat ne résout pas le problème. Il s’agit d’un exemple de conception qui applique le container resolver de la documentation officielle d’Octane à un context propre au package.
Vérifier aussi les états que scoped ne supprime pas
scoped() ne supprime pas automatiquement les états suivants :
- les valeurs ajoutées aux propriétés statiques propres au package ;
- les références au context stockées dans un autre singleton ou une variable globale ;
- « l’utilisateur courant » ou « la destination courante » conservés à l’intérieur d’un singleton ;
- les références conservées par des Facades ou des couches de cache personnalisées.
Écrire des tests qui franchissent une frontière dans le même conteneur
Si chaque test HTTP crée simplement une nouvelle application, vous risquez de passer à côté des problèmes liés à la réutilisation du même conteneur par un processus résident. Commencez par vérifier, au niveau du conteneur seul, le partage au sein d’un même traitement et la recréation après la frontière. Les tests suivants utilisent PHPUnit. Le premier test utiliseforgetScopedInstances(), comme les tests officiels du Container, pour vérifier l’effet de la frontière sans démarrer un Worker complet.
Les tests unitaires du conteneur ne vérifient pas le chemin d’exécution réel de la sandbox d’Octane ou du worker de file d’attente. Des tests d’intégration exécutant des traitements successifs dans les environnements que vous déclarez prendre en charge sont également nécessaires. Lors d’un déploiement modifiant les méthodes d’enregistrement ou l’implémentation des services, rechargez les processus résidents pour appliquer le nouveau code.
Pages connexes
Hooks de résolution des services d'un package
Découvrez afterResolving, la récupération des instances partagées et les différences de configuration lors des nouvelles résolutions.
Laravel Octane
Découvrez la conservation de la Request et du conteneur, ainsi que les points d’attention liés à l’exploitation des processus résidents.
Sources primaires consultées
- Documentation officielle de Laravel : Binding scoped singletons
- Documentation officielle de Laravel : Dependency injection and Octane
- Container : scoped(), singleton(), forgetScopedInstances(), resolve()
- ContainerTest : partage et recréation des services scoped
- QueueServiceProvider : resetScope transmis au Worker
- Worker : réinitialisation de la portée dans daemon()
- Octane : FlushTemporaryContainerInstances
- Octane : écouteurs par défaut de OperationTerminated