Artisan 命令行

简介

Artisan 是 Laravel 自带的命令行接口。它提供了许多有用的命令,可以在构建应用时为您提供帮助。要查看所有可用的 Artisan 命令列表,可以使用 list 命令:

php artisan list

每个命令还包括一个「帮助」页面,显示和描述命令的可用参数和选项。要查看帮助页面,可以在命令名前面加上 help

php artisan help migrate

Laravel REPL

所有 Laravel 应用都包含了 Tinker,一个由 PsySH 扩展包提供的 REPL。Tinker 允许您使用命令行和整个 Laravel 应用进行交互,包括 Eloquent ORM,任务,事件等。要进入 Tinker 环境,请运行 Artisan 命令 tinker

php artisan tinker

编写命令

除了 Artisan 提供的命令外,您还可以编写自己的自定义命令。命令通常存储在 app/Console/Commands 目录中;不过,您可以自由选择存储位置只要命令可以被 Composer 加载:

生成命令

要创建一个新的命令,使用 Artisan 命令 make:command。此命令会在 app/Console/Commands 目录中新建一个命令类。如果该目录在应用中不存在也别担心,因为它会在第一次运行 Artisan 命令 make:command 时自动创建。生成的命令会包含所有命令中都默认存在的属性和方法:

php artisan make:command SendEmails

命令结构

生成命令后,应该填写类的 signaturedescription 属性,它们会在 list 界面显示命令时使用。handle 方法会在命令执行时调用。您可以将命令逻辑放在此方法中。

为了更好地复用代码,一个好的开发实践是保持终端命令代码轻量,将相关任务放到应用服务中完成。在如下示例中,注意我们注入了一个服务类来进行发送邮件的「繁重的工作」。

我们来看一个示例命令。注意我们可以在命令的构造函数或 handle 方法中注入任何我们需要的依赖。Laravel 的 服务容器 会自动注入构造函数或 handle 方法中所有使用类型提示的依赖:

namespace App\Console\Commands;

use App\User;
use App\DripEmailer;
use Illuminate\Console\Command;

class SendEmails extends Command
{
    /**
     * 终端命令的名称和参数
     *
     * @var string
     */
    protected $signature = 'email:send {user}';

    /**
     * 终端命令的描述
     *
     * @var string
     */
    protected $description = 'Send drip e-mails to a user';

    /**
     * Drip 邮件服务
     *
     * @var DripEmailer
     */
    protected $drip;

    /**
     * 创建新的命令实例
     *
     * @param  DripEmailer  $drip
     * @return void
     */
    public function __construct(DripEmailer $drip)
    {
        parent::__construct();

        $this->drip = $drip;
    }

    /**
     * 执行终端命令
     *
     * @return mixed
     */
    public function handle()
    {
        $this->drip->send(User::find($this->argument('user')));
    }
}

闭包命令

基于闭包的命令提供了一个用类替代定义终端命令的方法。和路由闭包是控制器的替代方法一样,可以将命令闭包视为命令类的替代方法。在 app/Console/Kernel.php 文件的 commands 方法中,Laravel 加载了 routes/console.php 文件:

/**
 * 为应用注册闭包命令
 *
 * @return void
 */
protected function commands()
{
    require base_path('routes/console.php');
}

即使此文件未定义 HTTP 理由,但它依然定义了基于终端的应用的入口点(路由)。在此文件中,可以使用 Artisan::command 方法定义所有闭包路由。command 方法接收两个参数:命令参数 和一个接收命令参数和选项的闭包:

Artisan::command('build {project}', function ($project) {
    $this->info("Building {$project}!");
});

闭包会绑定到底层的命令实例,因此可以完全访问通常能在完整的命令类上访问的所有辅助方法。

类型提示依赖

除了接收命令参数和选项外,命令闭包也可以使用类型提示从 服务容器 中解析其它依赖:

use App\User;
use App\DripEmailer;

Artisan::command('email:send {user}', function (DripEmailer $drip, $user) {
    $drip->send(User::find($user));
});

闭包命令描述

当定义闭包命令时,可以使用 describe 方法为命令添加描述。此描述会在运行 php artisan listphp artisan help 命令时显示:

Artisan::command('build {project}', function ($project) {
    $this->info("Building {$project}!");
})->describe('Build the project');

定义输入内容

编写终端命令时,通常通过参数或选项收集用户输入。Laravel 可以在命令中使用 signature 属性方便地定义所期望的用户输入。signature 属性允许使用单个、可读性高、类似路由的语法定义命令的名称、参数和选项。

参数

所有用户提供的参数和选项都用花括号包起来。在以下示例中,此命令定义了一个必需的参数 user

/**
 * 终端命令的名称和参数
 *
 * @var string
 */
protected $signature = 'email:send {user}';

也可以定义可选参数并为其指定默认值:

// 可选参数
email:send {user?}

// 带默认值的可选参数
email:send {user=foo}

选项

选项,与参数一样,是用户输入的另一种形式。当在命令行中指定选项时,以两个连字符(--)作为前缀。有两种类型的选项:接收一个值的选项和不接收值的选项。不接收值的选项可以用作布尔的「开关」。我们来看一个这种类型的选项:

/**
 * 终端命令的名称和参数
 *
 * @var string
 */
protected $signature = 'email:send {user} {--queue}';

在此示例中,当调用 Artisan 命令时可以指定 --queue 开关。如果传递了 --queue 开关,选项的值会是 true。否则,值会是 false

php artisan email:send 1 --queue

带值的选项

接下来,我们看看带值的选项。如果用户一定要为选项指定值,可以在选项名称后面加上 = 符号:

/**
 * 终端命令的名称和参数
 *
 * @var string
 */
protected $signature = 'email:send {user} {--queue=}';

在此示例中,用户可以像这样传递一个值给该选项:

php artisan email:send 1 --queue=default

可以用过在选项名称后指定默认值来为选项指定默认值。如果用户未传递任何选项值,则将使用默认值:

email:send {user} {--queue=default}

选项简写

要在定义选项时指定简写,可以在选项名称之前指定它并使用 | 分隔简写和完整的选项名称:

email:send {user} {--Q|queue}

输入数组

如果要定义接收数组输入的参数或者选项,可以使用 * 符号。首先,我们看一个指定数组参数的示例:

email:send {user*}

当调用此方法时,会将 user 参数按顺序传递给命令行。例如,如下命令会将 user 值设置为 ['foo', 'bar']

php artisan email:send foo bar

当定义一个接收数组输入的选项时,每个传给命令的选项值都应该以选项名为前缀:

email:send {user} {--id=*}

php artisan email:send --id=1 --id=2

输入描述

可以通过使用冒号将参数与描述分开来为输入参数和选项指定描述。如果需要一些额外的空间来定义命令,可以随意将定义分布在多行中:

/**
 * 终端命令的名称和参数
 *
 * @var string
 */
protected $signature = 'email:send
                        {user : The ID of the user}
                        {--queue= : Whether the job should be queued}';

I/O 命令

获取输入

当命令执行时,显然要获取命令接收的参数和选项值。为此,可以使用 argumentoption 方法:

/**
 * 执行终端命令
 *
 * @return mixed
 */
public function handle()
{
    $userId = $this->argument('user');

    //
}

如果需要将所有参数作为数组获取,调用 arguments 方法:

$arguments = $this->arguments();

可以使用 option 和轻松获取参数一样获取选项。要将所有选项作为一个数组获取,可以调用 options 方法:

// 获取一个指定选项
$queueName = $this->option('queue');

// 获取所有选项
$options = $this->options();

如果指定的参数或选项不存在,会返回 null

提示输入

除了显示输出,也可能会在执行命令时要求用户提供输入。ask 方法会用给定问题提示用户,接收用户输入,然后将用户输入返回给命令:

/**
 * 执行终端命令
 *
 * @return mixed
 */
public function handle()
{
    $name = $this->ask('What is your name?');
}

secret 方法和 ask 类似,但是用户在终端输入时内容将不可见。此命令在要求用户输入敏感信息(例如密码)时很有用:

$password = $this->secret('What is the password?');

询问确认

如果需要询问用户进行简单的确认,可以使用 confirm 方法。默认情况下,此方法会返回 false。但是,如果用户输入 yyes 作为询问对应的响应,此方法会返回 true

if ($this->confirm('Do you wish to continue?')) {
    //
}

自动补全

anticipate 方法可用于为可能的选项提供自动补全。不管自动完成提示的内容是什么,用户仍然可以选择任何回答:

$name = $this->anticipate('What is your name?', ['Taylor', 'Dayle']);

多个选项

如果要为用户预设一组选择,可以使用 choice 方法。可以设置没有选项选择时返回的数组的默认索引值:

$name = $this->choice('What is your name?', ['Taylor', 'Dayle'], $defaultIndex);

编写输出

要发送输出给终端,可以使用 lineinfocommentquestionerror 方法。每个方法都使用适当的 ANSI 颜色来表明其用途。例如,我们显示一些普通信息给用户。通常情况下,info 方法会在终端中显示绿色文本:

/**
 * 执行终端命令
 *
 * @return mixed
 */
public function handle()
{
    $this->info('Display this on the screen');
}

要显示错误信息,可以使用 error 方法。错误信息文本通常用红色显示:

$this->error('Something went wrong!');

如果要显示普通的、没有着色的终端输出,可以使用 line 方法:

$this->line('Display this on the screen');

表格布局

table 方法可以方便地正确格式化多行/列的数据。只需要将标题和行传递给此方法。宽度和高度会根据给定数据动态计算:

$headers = ['Name', 'Email'];

$users = App\User::all(['name', 'email'])->toArray();

$this->table($headers, $users);

进度条

对于长时间运行的任务,显示进度条会很有用。使用输出对象,我们可以开始、前进和停止进度条。首先,定义进程要迭代的总步数。然后,在处理完每项后前进进度条:

$users = App\User::all();

$bar = $this->output->createProgressBar(count($users));

foreach ($users as $user) {
    $this->performTask($user);

    $bar->advance();
}

$bar->finish();

更多高级选项,请查看 Symfony 进度条组件文档

注册命令

由于在终端内核的 commands 方法中调用了 load 方法,因此 Artisan 会自动注册所有 app/Console/Commands 目录中的命令。实际上,可以自由调用 load 方法扫码其它目录中的 Artisan 命令:

/**
 * 为应用注册命令
 *
 * @return void
 */
protected function commands()
{
    $this->load(__DIR__.'/Commands');
    $this->load(__DIR__.'/MoreCommands');

    // ...
}

也可以在 app/Console/Kernel.php 文件的 $commands 属性中通过添加类名来手动注册命令。当 Artisan 启动时,所有列在此属性中的命令都会通过 服务容器 解析并在 Artisan 注册:

protected $commands = [
    Commands\SendEmails::class
];

程序中执行命令

有时可能希望在 CLI 之外执行 Artisan 命令。例如,希望在路由或控制器中触发 Artisan 命令。可以使用 Artisan Facade 的 call 方法实现此目的。call 方法接收命令名称或类作为第一个参数,以及命令参数数组作为第二个参数。将返回退出码:

Route::get('/foo', function () {
    $exitCode = Artisan::call('email:send', [
        'user' => 1, '--queue' => 'default'
    ]);

    //
});

使用 Artisan Facade 的 queue 方法时,甚至可以将 Artisan 命令交给队列,以便它们可以在后台通过 队列进程 处理。在使用此方法前,确保配置好了队列并运行了队列监听程序:

Route::get('/foo', function () {
    Artisan::queue('email:send', [
        'user' => 1, '--queue' => 'default'
    ]);

    //
});

还可以指定 Artisan 命令应该被分发的连接或队列:

Artisan::queue('email:send', [
    'user' => 1, '--queue' => 'default'
])->onConnection('redis')->onQueue('commands');

传递数组值

如果命令定义了一个接收数组的选项,可以传递一个数组值给此选项:

Route::get('/foo', function () {
    $exitCode = Artisan::call('email:send', [
        'user' => 1, '--id' => [5, 13]
    ]);
});

传递布尔值

如果需要为不接收字符串值的选项指定值,例如 migrate:refresh 命令的 --force 标志,应该传递 truefalse

$exitCode = Artisan::call('migrate:refresh', [
    '--force' => true,
]);

在其它命令中调用命令

有时可能希望在一个现有的 Artisan 命令中调用其它命令,可以使用 call 方法完成该操作。call 方法接收命令的名称和一个命令参数数组:

/**
 * 执行终端命令
 *
 * @return mixed
 */
public function handle()
{
    $this->call('email:send', [
        'user' => 1, '--queue' => 'default'
    ]);

    //
}

如果要调用另一个终端命令并禁止其所有输出,可以使用 callSilent 方法。callSilent 方法和 call 方法有相同的参数:

$this->callSilent('email:send', [
    'user' => 1, '--queue' => 'default'
]);