
用asCommand把 Laravel Actions 变成 Artisan 命令命令元数据、注册与测试全解析【免费下载链接】coolifyAn open-source, self-hostable PaaS alternative to Vercel, Heroku Netlify that lets you easily deploy static sites, databases, full-stack applications and 280 one-click services on your own servers.项目地址: https://gitcode.com/GitHub_Trending/co/coolify导读在 Coolify 这样基于 Laravel 构建的自托管 PaaS 项目中大量业务逻辑被收敛为AsAction动作类见 app/Actions 下数十个动作同一份领域逻辑常常需要同时暴露给 HTTP 路由、队列与命令行。本篇文章聚焦lorisleiva/laravel-actions的命令入口Command Entrypoint讲解如何通过asCommand(...)让一个动作既能被普通代码调用又能注册为php artisan可执行命令覆盖命令签名、描述、帮助文本与隐藏开关的完整元数据用法、控制台内核注册方式及聚焦型命令测试写法并强调“控制台 I/O 与领域逻辑分离”这一核心边界。Scope 与核心思路command.md使用场景非常明确当你希望把某个 Action 同时暴露为一个 Artisan 命令例如运维人员希望以php artisan users:update-role 1 admin的方式手工触发一次角色变更而不是通过 HTTP 或队列时就使用该参考。整个模式可以浓缩为一张“三层结构”的心智模型领域逻辑始终沉淀在handle(...)中与输入来源无关asCommand(Command $command)是控制台适配器负责从Command对象读取参数/选项、调用handle(...)、向终端输出结果命令元数据签名、描述、帮助、是否隐藏通过若干属性或同名get*方法声明。从本项目编码惯例看app/Actions 中的动作大量采用use AsAction;并将核心能力封装进handle(...)例如 StopApplication.php 的handle()一次性覆盖“停止容器、是否清理 Docker、是否重置重启计数”等完整领域决策这正体现了“任何入口控制器/队列/命令都只是 handle 的薄适配层”的设计原则。命令入口工作方式asCommand与handle的联动asCommand(Command $command)当动作作为命令被执行时框架会调用asCommand(Command $command)如果该方法缺失则自动回退到handle(...)——这是CommandDecorator提供给所有动作的默认能力。推荐的实现方式是在asCommand内解析命令行参数后委托给handle并在命令上下文里做 I/O 输出。文档中的标准示例use Illuminate\Console\Command; class UpdateUserRole { use AsAction; public string $commandSignature users:update-role {user_id} {role}; public function handle(User $user, string $newRole): void { $user-update([role $newRole]); } public function asCommand(Command $command): void { $this-handle( User::findOrFail($command-argument(user_id)), $command-argument(role) ); $command-info(Done!); } }注意上述示例展示了一条非常重要的适配规则handle(User $user, string $newRole)接收的是已解析的领域模型与强类型标量而asCommand负责把$command-argument(...)拿到的字符串转换成这些输入用findOrFail查模型、按需做类型转换。这样handle依然可以被 HTTP 控制器、队列 Job 或单元测试直接以领域语义调用。自定义handle的别名执行动作类若希望命令执行的默认业务入口与handle不同也可以覆盖asCommand内部逻辑选择调用其他方法但本项目的动作惯例见.cursor/skills/laravel-actions/SKILL.md的 Project Conventions始终推荐领域逻辑放handle传输层/框架关注点放as*适配方法避免重复实现业务规则。命令元数据签名、描述、帮助与可见性命令的行为描述通过一组“属性”或其对应的“同名方法”二选一完成。选择规则静态的、编译期就确定的元数据直接用属性需要动态计算时用方法。签名SignaturegetCommandSignature()方法用于定义命令签名当类没有设置$commandSignature属性时它是必需的public function getCommandSignature(): string { return users:update-role {user_id} {role}; }属性的等价写法推荐见 SKILL 中“Define$commandSignatureand$commandDescriptionproperties”的指引public string $commandSignature users:update-role {user_id} {role};签名语法完全沿用 Laravel 控制台的参数/选项语法{user_id}是必填参数、{role}是必填参数、{--queue}之类的写法用于定义选项、{arg?}为可选参数还可以追加default指定默认值。签名字符串中应把参数/选项的定义意图完整写出便于artisan list与--help展示。描述Descriptionpublic function getCommandDescription(): string { return Updates the role of a given user.; }属性等价写法public string $commandDescription Updates the role of a given user.;帮助文本Help--help触发时展示的补充说明适合描述行为细节、副作用或示例public function getCommandHelp(): string { return My help message.; }属性等价写法public string $commandHelp My help message.;是否隐藏Hidden控制命令是否从php artisan list中隐藏默认false。适合内部维护型命令public function isCommandHidden(): bool { return true; }属性等价写法public bool $commandHidden true;无论选属性还是方法混用同一组如同时定义$commandSignature属性与getCommandSignature()方法可能引起歧义实践中应保持同组元数据只选其一。注册命令从 Console Kernel 到项目真实注册方式要让php artisan users:update-role真正可用需要把动作类注册进控制台内核。经典写法是在 app/Console/Kernel.php 的$commands数组里声明// app/Console/Kernel.php protected $commands [ UpdateUserRole::class, ];而在当前 Coolify 仓库中控制台命令通过$this-load(__DIR__./Commands)按目录自动发现注册见 Kernel.php因此如果你的动作类位于app/Console/Commands之外例如app/Actions域名子命名空间就必须走显式注册或把命令包装为内核可发现的命令类。仓库中既有的调度还展示了命令的更多消费方式内核的schedule()里大量使用$schedule-command(cleanup:stucked-resources)、$schedule-command(horizon:snapshot)等把命令纳入 Laravel Scheduler见 Kernel.php。Coolify 的真实工程模式也提示我们当一个 Action 需要被调度系统定时触发时仓库选择的是在命令类内部::dispatch()动作 Job例如CheckTraefikVersionCommand中调用CheckTraefikVersionJob::dispatch()、CleanupStuckedResources.php 循环派发CleanupHelperContainersJob、DeleteResourceJob等。这条调用链体现了三种入口形态的取舍若动作本身定义了asJob如 StopApplication.php 这类具备$jobQueue属性的动作则命令只做“触发”若动作只想保留对象/命令两种入口则直接在asCommand中同步调用handle。两者都以handle为唯一领域事实源。聚焦型命令测试命令入口的测试聚焦于“通过 artisan 调用 → 断言输出与退出码”验证的是适配层接线而非领域规则领域正确性仍应通过直接调用handle(...)的业务测试覆盖。文档给出的最小化 Pest/PHPUnit 风格示例$this-artisan(users:update-role 1 admin) -expectsOutput(Done!) -assertSuccessful();解读$this-artisan(users:update-role 1 admin)以参数化方式执行命令等价于php artisan users:update-role 1 admin-expectsOutput(Done!)断言命令输出了Done!即asCommand中$command-info(Done!)的产物-assertSuccessful()断言进程退出码为 0未抛出异常。更完整的命令测试还可以追加-expectsQuestion(...)断言交互式提问、-assertExitCode(...)断言特定错误码等。对应 SKILL 中“Recommended test matrix for Actions”的建议命令行用例只做接线验证而handle的业务分支例如User不存在时应报错应在独立测试中直接调用handle覆盖。Checklist写完后自查清单按照文档的收尾清单一个合格的命令型 Action 应满足已导入use Illuminate\Console\Command;签名中的参数/选项均已文档化建议同时在 PHPDoc 或描述/帮助文本中说明含义存在命令测试验证命令可被调用且输出符合预期handle(...)内不掺入CommandI/O、HTTP 响应等传输层关注点。Common Pitfalls常见坑文档点名的两大高频反模式在结合仓库代码后值得展开把命令 I/O 混进handle(...)例如在handle里直接调用$command-info(...)或依赖Command类型。一旦如此同一动作就无法再被 HTTP 控制器或队列 Job 干净复用违背 Action 模式“多入口共享一套领域逻辑”的初衷。仓库内所有动作如 RestartDatabase.php、StartProxy.php 等的handle均只接收领域参数不触碰任何终端对象可以作为对照样板。签名缺失或含混既没有$commandSignature属性也没有getCommandSignature()或签名语法写错参数/选项、必填/可选标记混乱将导致命令无法注册或参数解析错位。建议维护一份与调用参数对齐的清晰签名并在帮助文本中给出示例。此外可以补充第三点实践提示保持适配器只做“翻译”——字符串到模型的转换User::findOrFail($id)、默认值填充都应留在asCommand而不是污染handle的参数契约。参考与延伸阅读本文技术骨架来源于仓库内 skill 文档 command.md动作模式的整体工作流与项目约定见 SKILL.md其中记录了对象入口、控制器入口、Job、Listener、测试 Fake 等各入口的分工与推荐用法项目依赖声明了lorisleiva/laravel-actions: ^2.10.2见 composer.json实际开发中可用composer show lorisleiva/laravel-actions确认已安装版本想深入命令入口的完整 API 面可查看同目录下关于对象入口的 object.mdrun/runIf/runUnless的语义与测试相关文档 testing-fakes.md真实工程的命令注册与调度可对照 app/Console/Kernel.php 及 routes/console.php。适用前提提示以上 API 均以lorisleiva/laravel-actions2.xAsAction特征体系为基准当前仓库锁定的版本满足该前提在非 2.x 或未安装该包的 Laravel 项目中属性/方法与asCommand回退语义可能不同请以实际 vendor 源码为准。【免费下载链接】coolifyAn open-source, self-hostable PaaS alternative to Vercel, Heroku Netlify that lets you easily deploy static sites, databases, full-stack applications and 280 one-click services on your own servers.项目地址: https://gitcode.com/GitHub_Trending/co/coolify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考