在 Laravel 中编写高级的 Artisan 命令


上一篇教程中,学院君向大家介绍了什么是 Artisan 命令,系统内置的 Artisan 命令,以及如何编写一个简单的 Artisan 命令。我们完全可以将命令行看作与 Web 应用同等的控制台应用(实际上,Laravel 底层也是这么做的),它具备自己的路由、Kernel、输入、控制器(命令类)、输出。因此,在这篇教程中,我们将更进一步,一起来看下如何编写更加高级的 Artisan 命令,比如带输入参数、选项,以及能够与用户互动,输出图表/进度条的 Artisan 命令。

参数和选项

通过上一篇教程,我们已经知道 $signature 属性可以配置命令名称,除此之外,还可以将 Artisan 命令的参数和选项定义在里面,添加参数和选项到 Artisan 命令非常简单,我们以系统自带的 make:migrate 命令为例:

protected $signature = 'make:migration {name : The name of the migration}
    {--create= : The table to be created}
    {--table= : The table to migrate}
    {--path= : The location where the migration file should be created}
    {--realpath : Indicate any provided migration file paths are pre-resolved absolute paths}';

参数:必填、可选和默认参数

要定义一个必填参数,需要用花括号将其包裹起来:

make:migration {name}

要定义一个可选参数,可以在参数名称后面加一个问号:

make:migration {name?}

要为可选参数定义默认值,可以这么做:

make:migration {name=create_users_table}

选项:必须设值、默认值以及缩写

选项和参数很像,但是选项有前缀 --,而且可以在没有值的情况下使用,要添加一个最基本的选项,可以通过花括号将其包裹:

make:migration {name} {--table}

如果这个选项必须要设置选项值,可以加上一个 =

make:migration {name} {--table=}

然后如果你想要为其设置默认选项值,可以这么做:

make:migration {name} {--table=users}

此外,选项还支持缩写,比如我们可以通过 T 来代表 table

make:migration {name} {--T|table}

数组参数和数组选项

不管是参数还是选项,如果你想要接收数组作为参数,都要使用 * 通配符:

make:migration {name*} {--table=*}

数组参数和选项的调用方式如下(这里仅作演示,make:migration 本身不支持这么干):

make:migration create_users_table create_posts_table --table=users --table=posts

注:数组参数必须是参数列表中的最后一个参数。

输入描述

还记得我们上一篇教程提到的 artisan help 命令吧,我们使用该命令时会提示我们指定命令所有参数和选项的描述信息:

这是怎么做到的呢?看看 make:migration 命令的 $signature 属性怎么设置的就明白了:通过冒号分隔参数/选项与描述信息(冒号两端必须有空格,否则就成了参数名/选项名的一部分了),这样就可以了,可以将描述信息看作是针对参数/选项的注释信息,只不过这里的注释符是 :

获取参数和选项

接下来,我们需要在命令类中获取参数和选项信息,在此之前,根据上述知识点,我们改写下自定义的 welcome:message 命令的 $signature 属性:

protected $signature = 'welcome:message {name : 用户名} {--city : 来自的城市}';

在命令类中我们可以通过 $this->argument() 方法获取参数值,不带参数返回所有参数值,如果传入指定参数名,则返回对应的参数值。类似的,在命令类中可以通过 $this->option() 方法获取选项值,不带参数返回所有选项值,传入指定选项名返回对应的选项值。为此,我们改写 welcome:message 命令的 handle() 方法如下:

public function handle()
{
    $this->info('欢迎来自' . $this->option('city') . '的' . $this->argument('name') .'访问 Laravel 学院!');
}

这样,我们运行 php artisan welcome:message,带上参数和选项信息,就可以输出对应的欢迎信息了:

用户交互

除了在命令行运行命令时手动设置参数值和选项值获取输入信息之外,Artisan 还支持通过其它方式获取用户输入,比如用户在执行命令期间通过键盘输入参数信息。这在我们的命令行应用最终是交付给客户使用的情况下非常方便,因为不同客户的输入信息是不一样的,我们不能写死,如果让客户自己输入又长又多的参数和选项又很不友好。

Laravel Artisan 提供了很多方法支持用户输入不同类型的数据。

如果输入的是普通文本的话,通过 ask() 方法即可:

$name = $this->ask('你叫什么的名字');

如果输入的是敏感信息,比如密码之类的,可以通过 secret() 方法隐藏用户输入:

$password = $this->secret('输入密码才能执行此命令');

如果需要用户确认信息,可以通过 confirm() 方法,该方法返回布尔值:

if ($this->confirm('确定要执行此命令吗?')) {
    // 继续
}

有时候,我们为了方便用户快速输入,会提供自动完成提示功能,这可以通过 anticipate() 方法实现:

$city = $this->anticipate('你来自哪个城市', [ 
    "北京", 
    "杭州", 
    "深圳" 
]);

最后,还有个很常见的命令行交互方式是为用户提供选项让用户选择,这可以通过 choice() 方法实现:

$city = $this->choice('你来自哪个城市', [
    '北京', '杭州', '深圳'
], 0);

最后那个参数 0 是默认值(数组下标),如果用户没有选择,直接回车的话就会将 $city 设置为默认值 北京

好了,基于以上知识点,我们来改写下 welcome:message 的实现代码:

class WelcomeMessage extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'welcome:message';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = '打印欢迎信息';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return mixed
     */
    public function handle()
    {
        $name = $this->ask('你叫什么名字');
        $city = $this->choice('你来自哪个城市', [
            '北京', '杭州', '深圳'
        ], 0);
        $password = $this->secret('输入密码才能执行此命令');
        if ($password != '123') {
            $this->error('密码错误');
            exit(-1);
        }
        if ($this->confirm('确定要执行此命令吗?')) {
            $this->info('欢迎来自' . $city . '的' . $name . '访问 Laravel 学院');
        } else {
            exit(0);
        }
    }
}

现在,我们在命令行运行 welcome:message 命令时,就会以交互方式让我们输入信息了:

输出信息

在命令执行过程中,需要输出信息给用户,告知用户执行进度、结果或者错误信息,否则会相当不友好。

文本信息

首先来看文本信息的输出。我们在前面已经多次见到过了 $this->info() 方法,一般我们会在命令执行成功后通过该方法以绿色文本输出提示信息。

比较常见的还有 $this->error() 方法以红色高亮文本输出错误信息,比如上例中如果密码输错的话:

此外,还可以通过 $this->line() 方法输出行信息(没有颜色)、$this->comment() 方法输出注释信息(黄色)、$this->question() 方法输出问题(靛蓝色高亮)。

注:针对不同机器,以上颜色可能会有出入。以自己的机器为准。

图表

除了基本的文本信息之外,我们还可以通过 $this->table() 方法输出 ASCII 表格,看下面这个例子:

$headers = ['姓名', '城市'];
$data = [
    ['张三', '北京'],
    ['李四', '上海']
];
$this->table($headers, $data);

我们通过表格输出用户及所在城市,在定义表格数据的时候,需要提供表头 $headers 和表数据 $data,这段代码输出结果如下:

进度条

如果你之前运行过 npm install,就会看到安装过程中有进度条显示安装进度,在 Artisan 命令执行过程中,也可以显示类似的进度条,实现代码如下:

$totalUnits = 10;
$this->output->progressStart($totalUnits);

$i = 0;
while ($i++ < $totalUnits) {
    sleep(3);
    $this->output->progressAdvance();
}

$this->output->progressFinish();

分三个阶段,首先我们要设置总体进度值,然后通过这进度值初始化进度条,接下来将进度值以整数为单位等分成不同的块,依次遍历,逐步累加进度,知道遍历完,结束进度条更新。进度条在命令行显示如下:

如果按照百分制类计算百分比的话,可以将 $totalUnits 初始化为 100。

在应用代码中调用 Artisan 命令

除了在命令行运行 Artisan 命令之外,还可以在应用代码中通过 Artisan 门面调用它。你可以直接通过 Artisan:call() 调用指定命令,也可以通过 Artisan:queue() 将命令推送到队列中执行。

这两种方式都需要传递两个参数:第一个参数是命令名(比如 welcome:message),第二个参数是以数组形式传递的命令参数和选项。下面,我们简单演示在路由闭包中调用 Artisan 命令 welcome:message(带输入参数的):

Route::get('test_artisan', function () {
    $exitCode = Artisan::call('welcome:message', [
        'name' => '学院君',
        '--city' => '杭州'
    ]);
});

正如你所看到的,参数和选项通过参数名和选项名作为键,如果选项没有值的话,以 truefalse 替代。

你还可以在一个 Artisan 命令类中调用另一个 Artisan 命令,在命令类中调用 Artisan 命令,可以通过 Artisan:call(),也可以直接通过 $this->call() 方法,还可以通过 $this->callSilent() 方法(该方法会抑制所有输出),传递的参数格式完全一样。

最后,你还可以在任意类中注入实现了 Illuminate\Contracts\Console\Kernel 契约的实例,然后调用实例上的 call() 方法,本质上都是调用了一个方法,所以参数都是一样的。


点赞 取消点赞 收藏 取消收藏

<< 上一篇: 在 Laravel 中编写第一个 Artisan 命令

>> 下一篇: 通过 Tinker 实现 Laravel 命令行交互式 Shell