如何编写Laravel Artisan脚本来自定义命令?
- 内容介绍
- 文章标签
- 相关推荐
本文共计972个文字,预计阅读时间需要4分钟。
使用以下命令直接生成命令类:
常见错误:手写类但忘了在 Kernel.php 中注册,或用了旧版 Laravel 却没开自动发现,结果 php artisan list 根本看不到命令。
- 命令名建议用短横线分隔,比如
sync:user-data,别用下划线或驼峰 - 类名必须以
Command结尾(如SyncUserDataCommand),否则自动发现会跳过 - Laravel 8+ 默认启用命令自动发现,只要类继承
Illuminate\Console\Command且放在app/Console/Commands/下即可
如何在命令里安全读取参数和选项
signature 属性是核心——它不只是“长得好看”,而是决定了参数解析逻辑、帮助文本生成、甚至影响 php artisan tinker 里的补全行为。
容易踩的坑:把必需参数写成可选(比如写成 {user?} 却没在 handle() 里做空值判断),或者混淆 --force(布尔选项)和 --timeout=30(带值选项)的写法。
- 必需参数:用
{user},调用时必须提供,如php artisan sync:user-data john - 可选参数:写成
{user?},不传时$this->argument('user')返回null - 带默认值的选项:写成
{--timeout=30},$this->option('timeout')拿到的是字符串"30",记得类型转换 - 布尔选项(开关型):写成
{--force},$this->option('force')返回true或false
为什么 handle() 里不能直接 throw Exception
Artisan 命令默认捕获所有未处理异常,并转为带堆栈的红字输出;但如果你主动 throw new Exception,它会终止执行并显示完整 trace,这在 CI 环境或定时任务中容易导致误判失败原因。
更合适的做法是用 $this->error() 输出提示 + return 1,让退出码可控,也方便 shell 脚本判断成败。
- 正常退出:返回
0(不用写,默认就是) - 业务失败:用
$this->error('用户不存在')+return 1 - 避免在
handle()里用die、exit或未捕获的throw,它们绕过 Artisan 的生命周期钩子(比如terminate()) - 数据库事务、文件句柄、Redis 连接等资源清理,应放在
terminate()方法里,而不是靠异常触发析构
什么时候该用 Command::class 而不是 php exec
在另一个命令或控制器里调用 Artisan 命令,优先用 Artisan::call('sync:user-data', ['user' => 'john']),而不是 exec('php artisan sync:user-data john')。
后者看似简单,实则绕过了 Laravel 的服务容器、事件系统、日志上下文,还可能因 PHP CLI 配置不同(比如内存限制、扩展缺失)而静默失败。
-
Artisan::call()返回整数退出码,可配合Artisan::output()拿到输出内容 - 若需异步执行,不要用
exec("nohup php artisan ... &"),改用队列驱动的命令(加implements ShouldQueue) - 在测试中模拟命令调用,用
artisan('sync:user-data', [...])测试方法,比 mock exec 更可靠
命令里涉及模型操作、队列、缓存这些 Laravel 特性时,脱离应用上下文就大概率出问题——exec 是黑盒,Artisan::call 是白盒。
本文共计972个文字,预计阅读时间需要4分钟。
使用以下命令直接生成命令类:
常见错误:手写类但忘了在 Kernel.php 中注册,或用了旧版 Laravel 却没开自动发现,结果 php artisan list 根本看不到命令。
- 命令名建议用短横线分隔,比如
sync:user-data,别用下划线或驼峰 - 类名必须以
Command结尾(如SyncUserDataCommand),否则自动发现会跳过 - Laravel 8+ 默认启用命令自动发现,只要类继承
Illuminate\Console\Command且放在app/Console/Commands/下即可
如何在命令里安全读取参数和选项
signature 属性是核心——它不只是“长得好看”,而是决定了参数解析逻辑、帮助文本生成、甚至影响 php artisan tinker 里的补全行为。
容易踩的坑:把必需参数写成可选(比如写成 {user?} 却没在 handle() 里做空值判断),或者混淆 --force(布尔选项)和 --timeout=30(带值选项)的写法。
- 必需参数:用
{user},调用时必须提供,如php artisan sync:user-data john - 可选参数:写成
{user?},不传时$this->argument('user')返回null - 带默认值的选项:写成
{--timeout=30},$this->option('timeout')拿到的是字符串"30",记得类型转换 - 布尔选项(开关型):写成
{--force},$this->option('force')返回true或false
为什么 handle() 里不能直接 throw Exception
Artisan 命令默认捕获所有未处理异常,并转为带堆栈的红字输出;但如果你主动 throw new Exception,它会终止执行并显示完整 trace,这在 CI 环境或定时任务中容易导致误判失败原因。
更合适的做法是用 $this->error() 输出提示 + return 1,让退出码可控,也方便 shell 脚本判断成败。
- 正常退出:返回
0(不用写,默认就是) - 业务失败:用
$this->error('用户不存在')+return 1 - 避免在
handle()里用die、exit或未捕获的throw,它们绕过 Artisan 的生命周期钩子(比如terminate()) - 数据库事务、文件句柄、Redis 连接等资源清理,应放在
terminate()方法里,而不是靠异常触发析构
什么时候该用 Command::class 而不是 php exec
在另一个命令或控制器里调用 Artisan 命令,优先用 Artisan::call('sync:user-data', ['user' => 'john']),而不是 exec('php artisan sync:user-data john')。
后者看似简单,实则绕过了 Laravel 的服务容器、事件系统、日志上下文,还可能因 PHP CLI 配置不同(比如内存限制、扩展缺失)而静默失败。
-
Artisan::call()返回整数退出码,可配合Artisan::output()拿到输出内容 - 若需异步执行,不要用
exec("nohup php artisan ... &"),改用队列驱动的命令(加implements ShouldQueue) - 在测试中模拟命令调用,用
artisan('sync:user-data', [...])测试方法,比 mock exec 更可靠
命令里涉及模型操作、队列、缓存这些 Laravel 特性时,脱离应用上下文就大概率出问题——exec 是黑盒,Artisan::call 是白盒。

