v13.35.0 y profundiza en el ejemplo de registro con commands() de Fundamentos del desarrollo de paquetes. Para implementar la generación de archivos, consulta GeneratorCommand; para participar en las tareas de optimización, consulta Integración con optimize.
Registrar los comandos explícitamente desde el provider
Los comandos de un paquete se registran por nombre de clase en el métodoboot() del service provider. El descubrimiento automático de app/Console/Commands de la aplicación y el registro de las clases que proporciona un paquete son puntos de entrada distintos.
runningInConsole() comprueba si la aplicación se ejecuta en un entorno de consola, no si se está ejecutando un comando concreto. Si también ofreces la posibilidad de llamar a Artisan::call() desde una petición HTTP, ten en cuenta que esta condición puede impedir el registro y define el contrato de uso, incluida la opción de registrar sin condiciones. Para ejecutar comandos desde el código, consulta Artisan.
commands() no ejecuta el comando en ese momento
ServiceProvider::commands() registra en Illuminate\Console\Application::starting() un closure que utiliza los nombres de clase recibidos. En este punto no se llama al handle() del comando.
Con el Console Kernel habitual, los callbacks de arranque registrados se ejecutan más tarde, cuando se crea la Artisan Application. Allí, resolveCommands() pasa cada clase a resolve() y continúa con el registro de los comandos.
Lo importante aquí es que la instancia de un comando no se crea necesariamente en el mismo momento en que se ejecuta. En las clases normales que no usan la ruta específica de registro diferido que se describe más adelante, resolve() llama al make() del contenedor, por lo que el constructor y sus dependencias se resuelven en el momento del registro.
Por lo tanto, el constructor de un comando del paquete puede ejecutarse aunque solo ejecutes php artisan list u otro comando. Colocar commands() en boot() no garantiza por sí mismo que la creación de la instancia se retrase hasta la ejecución.
Inyectar las dependencias de ejecución en handle()
Como ejemplo, crearemos un comando que solo muestra si la configuración del paquete está activada o desactivada. Al igual que en la documentación oficial de Artisan de Laravel 13, el nombre y la descripción se definen con los atributosSignature y Description.
handle(). Como Command::execute() utiliza el call() del contenedor en la ruta de ejecución del comando, se inyectan las dependencias con tipo declarado.
Si usas clientes de API externas o servicios de sincronización, también puedes inyectarlos en el mismo lugar cuando solo se necesiten durante la ejecución. Sin embargo, si la creación del propio servicio o el boot() de otro provider tiene efectos secundarios, estos no se evitan automáticamente.
Si defines tu propio constructor, llama también a
parent::__construct(). El constructor padre de Laravel configura el comando a partir de los atributos, la signature, la description, etc.
Signature y el registro diferido son mecanismos distintos
ElApplication::resolve() que revisamos tiene una rama que lee el name del atributo AsCommand de Symfony y guarda la correspondencia entre el nombre y la clase en un command map. En esta rama no se llama al make() del contenedor en ese momento. Más tarde, el Console Kernel llama a setContainerCommandLoader() y configura un loader que utiliza este map.
Por otro lado, el atributo Signature de Laravel que usamos en el ejemplo anterior es independiente de esta comprobación de AsCommand. Añadir solo Signature no hace que la clase entre en esta ruta de registro diferido.
Trata esta diferencia como un punto de atención al leer la implementación interna y, en primer lugar, reduce el coste de creación y los efectos secundarios con la definición oficial de comandos y la inyección de dependencias en handle(). Aunque exista un loader diferido, la información del comando puede ser necesaria para el listado, la ayuda, etc., así que no diseñes partiendo de que «el comando nunca se crea salvo cuando se selecciona y se ejecuta».
No diferir el registro de los comandos
Cargar de forma diferida los bindings de servicios y dar a conocer un comando a Artisan son cosas distintas. Si un provider implementaDeferrableProvider, es posible que su boot() ni siquiera se ejecute hasta que se necesite el servicio correspondiente.
Si colocas el registro de comandos únicamente en ese boot() y el provider no se carga antes de que arranque Artisan, tampoco se registra el callback de arranque que da a conocer el comando. Aunque un servicio que hayas trasladado a la inyección de dependencias de handle() cargue el provider diferido, eso no resuelve el problema que ocurre antes de que Artisan encuentre el comando.
Para garantizar que los comandos estén disponibles, puedes colocar el registro en un provider de carga inmediata y separar únicamente los bindings de servicios pesados en otro provider diferido. Para conocer los requisitos concretos, consulta Service providers diferidos.
Qué comprobar al actualizar
Los nombres de comandos, los argumentos, las opciones, la salida y los códigos de salida son interfaces públicas que utilizan los scripts de shell y la CI de los usuarios. No los cambies con la misma ligereza que los detalles internos de una clase; comprueba la compatibilidad y los pasos de migración.
Con el
StatusCommand anterior, puedes verificar la salida y el código de salida de la siguiente manera en un entorno de pruebas donde se haya cargado el provider. Para la infraestructura de pruebas, consulta Pruebas de paquetes; para los detalles de las aserciones, consulta Console Tests.
Fuentes primarias consultadas
- Documentación oficial de Laravel: registro de comandos de paquetes
- Documentación oficial de Laravel: estructura de comandos e inyección de dependencias
- ServiceProvider: commands()
- Console Application: starting(), bootstrap() y resolve()
- Console Kernel: getArtisan() y registro del loader
- Command: constructor y execute()
- Pruebas oficiales: resolución de comandos y registro de alias con AsCommand