Skip to main content
Cuando quieras añadir a tu paquete comandos Artisan de diagnóstico, sincronización o instalación, considera por separado el registro del comando, la creación de la instancia y la ejecución del proceso. Si resuelves en el constructor servicios que solo se necesitan durante la ejecución, puedes acabar afectando incluso al arranque de comandos Artisan que no tienen nada que ver. Esta página revisa la implementación de Laravel Framework 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étodo boot() 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.
Este ejemplo supone que el provider se carga mediante el descubrimiento de paquetes o mediante un registro explícito en la aplicación. Colocar simplemente una clase de comando no hace que el provider del paquete se registre automáticamente. 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 atributos Signature y Description.
Este ejemplo no añade un constructor, sino que recibe el Repository de configuración como argumento de 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.
No te conectes a API externas, no actualices la base de datos ni sobrescribas archivos publicados en el constructor. Los usuarios del paquete deben poder usar list y help justo después de la instalación, incluso cuando la configuración de conexión aún no está completa.
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

El Application::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 implementa DeferrableProvider, 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.
Además, en los comandos que utilizan servicios pesados, verifica también que no se llame a los métodos de negocio del servicio al ejecutar el listado o la ayuda. Comprobar únicamente que el comando se puede registrar no detecta su impacto en comandos no relacionados.

Fuentes primarias consultadas

Última modificación el 12 de octubre de 2026