v13.35.0 et approfondit l’exemple d’enregistrement avec commands() présenté dans Développement de packages Laravel. Pour l’implémentation de la génération de fichiers, consultez GeneratorCommand, et pour la participation aux tâches d’optimisation, consultez Intégration à optimize.
Enregistrer explicitement depuis le provider
Les commandes d’un package sont enregistrées par leur nom de classe dans la méthodeboot() du service provider. La découverte automatique du répertoire app/Console/Commands de l’application et l’enregistrement des classes fournies par un package sont deux points d’entrée distincts.
runningInConsole() détermine si l’on se trouve dans un environnement console, et non si une commande particulière est en cours d’exécution. Si vous prévoyez aussi un usage via Artisan::call() depuis une requête HTTP, tenez compte du fait que cette condition peut empêcher l’enregistrement, et définissez le contrat d’utilisation en envisageant éventuellement un enregistrement inconditionnel. Pour l’exécution programmatique, consultez Artisan.
commands() n’exécute pas la commande sur-le-champ
ServiceProvider::commands() enregistre auprès de Illuminate\Console\Application::starting() une closure qui utilise les noms de classes reçus. À ce stade, la méthode handle() de la commande n’est pas appelée.
Avec le Console Kernel habituel, les callbacks de démarrage enregistrés sont exécutés plus tard, lorsque l’Artisan Application est créée. C’est alors que resolveCommands() transmet chaque classe à resolve() et procède à l’enregistrement des commandes.
Le point essentiel est que l’instanciation d’une commande ne coïncide pas nécessairement avec son exécution. Pour les classes ordinaires qui n’empruntent pas le chemin d’enregistrement différé dédié décrit plus bas, resolve() appelle make() du conteneur : le constructeur et ses dépendances sont donc résolus au moment de l’enregistrement.
Par conséquent, le simple fait d’exécuter php artisan list ou une autre commande peut déclencher le constructeur de la commande du package. Placer commands() dans boot() ne garantit pas en soi que l’instanciation sera reportée jusqu’à l’exécution.
Injecter les dépendances d’exécution dans handle()
À titre d’exemple, créons une commande qui affiche uniquement si la configuration du package est activée ou désactivée. Comme dans la documentation officielle d’Artisan pour Laravel 13, le nom et la description sont définis avec les attributsSignature et Description.
handle(). Comme Command::execute() utilise call() du conteneur dans le chemin d’exécution de la commande, les dépendances typées sont injectées.
Si vous utilisez un client d’API externe ou un service de synchronisation, vous pouvez également l’injecter au même endroit s’il n’est nécessaire qu’à l’exécution. Cependant, si la création du service lui-même ou le boot() d’un autre provider produit des effets de bord, ceux-ci ne sont pas évités automatiquement.
Si vous définissez votre propre constructeur, appelez également
parent::__construct(). En effet, le constructeur parent de Laravel configure la commande à partir des attributs, de la signature, de la description, etc.
Signature et l’enregistrement différé sont deux mécanismes distincts
La méthodeApplication::resolve() examinée contient une branche qui lit le name de l’attribut Symfony AsCommand et enregistre la correspondance entre le nom et la classe dans une command map. Dans cette branche, make() du conteneur n’est pas appelé immédiatement. Le Console Kernel appelle ensuite setContainerCommandLoader() pour configurer un loader qui utilise cette map.
En revanche, l’attribut Laravel Signature utilisé dans l’exemple ci-dessus est distinct de cette détection d’AsCommand. Ajouter Signature ne suffit pas à faire passer la classe par ce chemin d’enregistrement différé.
Considérez cette différence comme un point d’attention lors de la lecture de l’implémentation interne, et commencez par réduire le coût de création et les effets de bord grâce à la définition officielle des commandes et à l’injection de dépendances dans handle(). Même avec un loader différé, les informations de la commande peuvent être nécessaires pour la liste, l’aide, etc. : ne concevez donc pas votre code en supposant que la commande « ne sera jamais créée en dehors de son exécution ».
Ne pas différer l’enregistrement des commandes
Différer le chargement des liaisons de services et faire connaître une commande à Artisan sont deux choses différentes. Lorsqu’un provider implémenteDeferrableProvider, sa méthode boot() elle-même peut ne pas être exécutée tant que le service concerné n’est pas nécessaire.
Si l’enregistrement des commandes se trouve uniquement dans ce boot() et que le provider n’est pas chargé avant le démarrage d’Artisan, le callback de démarrage qui signale l’existence de la commande n’est pas enregistré non plus. Même si un service déplacé vers l’injection de dépendances de handle() charge le provider différé, cela ne résout pas le problème qui survient avant qu’Artisan ne trouve la commande.
Pour garantir l’exposition des commandes, vous pouvez placer leur enregistrement dans un provider chargé immédiatement et isoler uniquement les liaisons des services lourds dans un provider différé distinct. Pour les exigences précises, consultez Service providers différés.
Points à vérifier lors des mises à jour
Le nom de la commande, ses arguments, ses options, sa sortie et son code de sortie constituent une interface publique utilisée par les scripts shell et la CI des utilisateurs. Ne les modifiez pas avec la même légèreté que l’implémentation interne d’une classe ; vérifiez la compatibilité et la procédure de migration.
Pour le
StatusCommand ci-dessus, vous pouvez vérifier la sortie et le code de sortie comme suit dans un environnement de test qui charge le provider. Pour l’infrastructure de test, consultez Tester des packages Laravel avec Orchestra Testbench, et pour le détail des assertions, consultez Tests de console.
Sources primaires consultées
- Documentation officielle de Laravel : enregistrement des commandes d’un package
- Documentation officielle de Laravel : structure des commandes et injection de dépendances
- ServiceProvider : commands()
- Console Application : starting(), bootstrap(), resolve()
- Console Kernel : getArtisan() et enregistrement du loader
- Command : constructeur et execute()
- Tests officiels : résolution des commandes et enregistrement d’alias via AsCommand