v13.35.0 的实现,深入探讨 Laravel 包开发中的 commands() 注册示例。文件生成的实现请参阅 GeneratorCommand,参与优化任务请参阅集成到 optimize。
在 Provider 中显式注册
包内的命令在 Service Provider 的boot() 中注册类名。应用中针对 app/Console/Commands 的自动发现与包所提供类的注册是不同的入口。
runningInConsole() 判断的是是否处于控制台环境,而不是判断是否正在执行特定命令。如果还支持在 HTTP 请求中调用 Artisan::call() 的用法,就要考虑到因该条件而未被注册的可能性,并在包括无条件注册在内的选项中确定使用约定。从程序中执行的方法请参阅 Artisan。
commands() 不会当场执行命令
ServiceProvider::commands() 会将使用所接收类名的闭包注册到 Illuminate\Console\Application::starting()。此时并不会调用命令的 handle()。
在通常的 Console Kernel 中,之后创建 Artisan Application 时,会执行已注册的启动回调。此时 resolveCommands() 会将各个类传给 resolve(),推进命令的注册。
这里的关键在于:命令的实例创建并不一定与该命令的执行发生在同一时机。对于不使用后文所述专用延迟注册路径的普通类,resolve() 会调用容器的 make(),因此在注册时就会解析构造函数及其依赖。
因此,仅仅执行 php artisan list 或其他命令,包命令的构造函数也可能会运行。将 commands() 放在 boot() 中这件事本身,并不能保证实例创建会推迟到执行时。
将执行时的依赖注入到 handle()
作为示例,我们创建一个仅显示包配置启用/禁用状态的命令。与 Laravel 13 官方 Artisan 文档一样,使用Signature、Description 属性来定义名称和描述。
handle() 的参数接收。在命令的执行路径中,Command::execute() 使用容器的 call(),因此会注入声明了类型的依赖。
使用外部 API 客户端或同步服务时,如果只在执行时需要,也可以注入到同样的位置。不过,如果服务本身的创建或其他 Provider 的 boot() 存在副作用,这些并不会被自动阻止。
自行定义构造函数时,也要调用
parent::__construct()。因为 Laravel 的父类构造函数会根据属性、signature、description 等来构建命令。
Signature 与延迟注册是不同的机制
在所确认的Application::resolve() 中,有一个分支会读取 Symfony 的 AsCommand 属性的 name,并将名称与类的对应关系保存到 command map 中。该分支不会当场调用容器的 make()。Console Kernel 之后会调用 setContainerCommandLoader(),设置使用此 map 的加载器。
另一方面,上面示例中使用的 Laravel Signature 属性与这个 AsCommand 判断无关。仅仅添加 Signature,并不会让该类进入这条延迟注册路径。
请将这一差异作为阅读内部实现时的注意点,首先通过官方的命令定义和向 handle() 的依赖注入来降低创建成本和副作用。即使存在延迟加载器,列表、帮助等也可能需要命令信息,因此不要以”除了执行所选命令时之外绝不会被创建”为前提进行设计。
不要连命令的注册也延迟
延迟加载服务绑定与让 Artisan 知道命令的存在是两回事。如果 Provider 实现了DeferrableProvider,在需要目标服务之前,boot() 本身可能都不会被执行。
如果只把命令注册放在该 boot() 中,而在 Artisan 启动前 Provider 没有被加载,那么用于告知命令存在的启动回调也不会被注册。即使移到 handle() 中依赖注入的服务会加载延迟 Provider,也无法解决 Artisan 找到命令之前的问题。
作为确保命令公开的结构,可以将注册放在立即加载的 Provider 中,只把重量级服务的绑定拆分到另一个延迟 Provider。具体要求请参阅延迟 Service Provider。
更新时需要确认的事项
命令名称、参数、选项、输出、退出码是用户的 Shell 脚本或 CI 所使用的公共接口。不要以修改类内部实现的心态去变更它们,而要确认兼容性和迁移步骤。
对于上面的
StatusCommand,可以在加载了 Provider 的测试环境中按如下方式验证输出和退出码。测试基础请参阅使用 Orchestra Testbench 测试 Laravel 包,断言的详细信息请参阅控制台测试。