Laravel 实用小技巧 —— Artisan 入门(上)

我们都知道,Laravel 提供了强大的命令行功能 —— Artisan,借助这个工具我们可以很方便地实现各种需要在控制台下处理的功能。接下来,我们就来介绍一下,如何在实际工作中高效地使用它。
考虑文章篇幅限制,我们将分成两篇文章为大家介绍。

起步

接下来,我们就从创建自定义的 Artisan 命令开始吧。

创建命令文件

自定义的命令一般放在 app/Console/Commands 目录下,如果选择其他目录,需要保证能够被 Composer 加载到。一般我们在 App\Console\Kernel 类中的 commands 方法中进行注册命令:

protected function commands(): void
{
    $this->load(__DIR__.'/Commands');
    $this->load(__DIR__.'{自定义命令目录}');
}

创建自定义命令非常简单,比如我们现在要实现一个发送邮件的命令,只需要在项目根目录下执行以下命令即可:

php artisan make:command SendMail

运行完以后,会在 app/Console/Commands 目录下生成一个 SendMail 的类文件。接下来我们就来看一下命令文件的「内部构造」。

定义命令结构

这里先来介绍一下该文件中我们需要关注的点,文件主要结构如下:

...
protected $signature = 'mail:send'; // 命令签名
protected $description = '发送邮件'; // 命令描述
// 命令入口
public function handle()
{
    return 0;
}
...
  • signature 属性: 命令签名,由命令名称、参数和选项组成
  • description 属性: 命令描述
  • handle 方法: 命令处理的逻辑,相当于命令入口

当我们在命令行运行 php artisan list 命令时,会看到如下展示:

pN9zKVJh9k.png

这里我们在定义命令名称的时候,并没有选择和类名称保持一致。其实通过上面的显示效果不难发现,我们命令名称使用了冒号分隔。冒号前的部分相当于「命名空间」,冒号后的部分相当于「具体命令」。比如当我们再添加一个删除邮件的命令时,就可以定义一个 mail:delete 命令,这时再运行 php artisan list 命令,会发现两个命令都放在 mail 这个「命名空间」下了:

uPyWYeLzek.png

那么命令名称里这个冒号分隔符是必须的吗?并不是。如果你定义了一个 test 命令,这时你再运行 php artisan list,发现系统并没有报错,依然能显示可用的命令,只不过这个命令跑到了 Available commands 这个「命名空间」下,如下图:

mWnVTD5JIJ.png

看似没什么问题,而且命令名称更短了,我们能直接这么用呢?

不急,我们先把这个命令删掉,然后再运行 php artisan list 看一下:

1adC2wDHzx.png

神马情况?test 命令不是已经删除了么,为什么还在呢?仔细看命令描述不难发现,这并不是我们定义的那个 test 命令,而是系统运行测试用例的原生命令。

现在应该知道为什么我们需要指定「命名空间」了吧 —— 没错,就是为了保持规范,避免与系统默认的命令冲突。因为当出现命令冲突的情况,系统并不会报错,而是优先使用用户自定义的命令。

那是不是意味着用了「命名空间」的写法就万事大吉了呢?也不是,因为系统或者安装的扩展包中预制的命令也使用了「命名空间」,比如我们运行 php artisan list cache,会发现 cache 这个命名空间下预制了以下命令:

31taJQ3dK5.png!large

我们在定义自己的命令时,就需要避开 cache 这个命名空间了。一个良好的习惯就是我们在定义我们的命令时,首先使用 php artisan list {namespace} 来检查下 namespace 是否可用,如果已被占用的话,就需要考虑换一个名字了。

讲完了定义命令的规范,我们再来看一看参数(argument)这个东西。定义参数通过以下方式定义:

protected $signature = '{命令名称} {参数名称}';

比如我们需要在调用命令时指定给谁发送邮件,就可以定义一个 user 的参数,参数格式如下:

protected $signature = 'mail:send {user}';

然后就可以通过以下方式调用命令了:

php artisan mail:send Tom

handle 方法中通过 $this->argument('{参数名称}') 就可以获取到传递的参数了。

Artisan 支持传递多个参数。比如我们需要在发送邮件时指定一下邮件的性质,就可以再增加一个 type 的参数:

protected $signature = 'mail:send {user} {type}';

这时我们就可以通过以下的方式来使用命令:

php artisan mail:send Tom notify

通过获取 type 参数我们可以知道这是一封通知类型的邮件。

因为我们在命令行中传递参数时并不能指定参数名称,所以必须严格按照参数定义的顺序传递。

如果我们想给多个人发送邮件该怎么办呢?也很简单,Artisan 的参数支持数组类型的参数,我们只需要在参数后加上 * 就可以了:

protected $signature = 'mail:send {users*}';

然后我们在调用命令的时候通过空格分隔的方式来传递参数就可以了:

php artisan mail:send Tom Sam

这时候,命令名称后传递的参数就被当成一个数组接收了。打印 $this->argument('users') 显示如下:

array:2 [
  0 => "Tom"
  1 => "Sam"
]

这里你会发现,我在上一个示例中把 type 参数去掉了,这是为什么呢?如果带着 type 参数会怎么样呢?我们按如下格式定义命令:

protected $signature = 'mail:send {users*} {type}';

调用命令方式如下:

php artisan mail:send Tom Sam notify

这时命令行会抛出一个错误:

Cannot add a required argument "type" after an array argument "users".

错误的意思就是:不能在数组参数后面添加必传参数

其实仔细看看我们调用的命令就可以看出端倪来,当我们在数组参数后面追加一个参数时,我们是无法在参数上区分出数组的边界的。所以 Artisan 在底层封装时就杜绝了这种情况的存在。

那如果我们还需要这个 type 参数该怎么办呢?如果一定通过参数形式实现的话,只能把 type 参数放在数组参数前,这样就不会报错了。但是切记,type 一定不能定义成数组参数 —— 因为 Artisan 只能添加一个数组参数,添加两个数组参数时一样会报上面的错误,道理是一样的。

到这里,你可能已经感受到参数用法的局限性了,这还不止,当你需要定义多个参数时,你还必须要记住每个参数对应的位置——是不是瞬间头大!

当你遇到这种困惑时,或许你就该考虑使用选项(option)了。

比如你可以在发邮件时指定是立即发送还是稍后发送,你就可以定义一个 send-now 的参数,格式如下:

protected $signature = 'mail:send {--send-now}';

选项和参数在格式上的不同之处在于选项前面多了 --,同时我们在调用命令的方式需要按照如下的方式调用:

php artisan mail:send --send-now

这时在 handle 中通过 $this->option('--send-now') 获取到的是 true

这是选项的第一种使用方式,即不指定选项值的方式,这种情况选项的值就是 true 或者 false :不传递选项的时候获取到的值是 false,指定选项名称的时候获取到的是 true。这种情况在选项值仅有 truefalse 时非常方便。

另一种方式是需要指定选项值的方式,比如我需要在发送邮件时指定具体的发送时间,我们可以定义一个 send-at 的选项:

protected $signature = 'mail:send {--send-at=}';

带值的选项后面多了一个 =,这就要求我们在调用命令的时候必须按照如下方式传递选项:

php artisan mail:send --send-at='2023-05-17 12:00'

参数如果想作为可选参数的话,需要在参数名称后面追加一个 ?,如 {user?} ,选项则不需要,如果选项指定传递值的话,命令行如果没有传递选项,并不会抛出异常,而是将选项值置为默认的 null

如果你嫌定义的选项太长,传递起来不方便的话,你还可以给他定义一个缩写:

protected $signature = 'mail:send {--S|send-at=}';

这样就可以使用缩写方式来传递选项了:

php artisan mail:send -S'2023-05-17 12:00'
或者 
php artisan mail:send -S '2023-05-17 12:00'

这里需要注意三点:

  1. 缩写必须是一位字母,大小写均可,一般使用大写。
  2. 使用缩写的方式传递选项时,选项名称缩写前是 -,与全称的 -- 注意区分,同时选项名称和选项值之间不需要 = 连接。可以加一个空格,也可以紧跟选项名称缩写之后。
  3. 选项值中如果有空格的情况下,需要用 ' 或者 " 将值包裹起来,否则会报错。

此外,选项也是支持数组传值的,但是和参数的数组传值方式有所区别,选项是通过传多次值的方式来实现数组传值的,比如我们指定在 2023-05-17 12:002023-05-17 15:00 两个时间来发送邮件,就可以把参数中的 {--S|send-at=} 定义为 {--S|send-at=*} ,调用方式如下:

php artisan mail:send -S '2023-05-17 12:00' -S '2023-05-17 15:00'

这样就可以给选项传递多个值了。

当你在设计命令的时候,如果需要定义两个以上的参数的时候,这时不妨考虑使用选项的方式来替代,后者在这种情况下使用起来更加灵活方便。

完善参数说明

当我们想了解某个命令的用法时,我们一般使用如下命令:

php artisan help mail:send

显示内容如下:

JORJNkqUL2.png!large

在选项说明里,我们可以看到除了我们自定义的参数和选项外,还有系统默认的一些选项,而且这些选项后面还带了说明信息,这样能帮助我们快速了解选项的具体作用。那我们自定义的参数和选项能不能添加这些说明信息呢?

很简单,只需要在参数和选项后跟一个分隔的 : ,然后跟上说明信息即可:

...
protected $signature = 'mail:send
                        {user : The receiver of mail}
                        {type : The type of mail}
                        {--I|send-now : Whether to send the mail immediately}
                        {--S|send-at=* : Specifies when to send}';
...

配置完后,我们在打印命令的帮助信息时,就能看到参数及选项的说明了:

tSwtOldCLV.png

好了,这篇文章就为大家介绍到这里。在下一篇文章中,我们会为大家介绍命令输入输出相关的内容,感谢大家的持续关注~

本作品采用《CC 协议》,转载必须注明作者和本文链接
你应该了解真相,真相会让你自由。
本帖由系统于 10个月前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 10

非常棒,好厉害。

11个月前 评论
Squ1rrel

支持烙铁

11个月前 评论

收藏,看一下

11个月前 评论

mark,分析的很透彻

10个月前 评论

不错,支持一下

10个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!