Artisan 控制台


简介

Artisan 是 Laravel 自带的命令行接口,它为我们在开发过程中提供了很多有用的命令。想要查看所有可用的 Artisan 命令,可使用 list 命令:

php artisan list

每个命令都可以用 help 指令显示命令描述及命令参数和选项。想要查看帮助界面,只需要在命令前加上 help 就可以了:

php artisan help migrate

Laravel Sail

如果您正在使用 Laravel Sail 作为本地开发环境,请记得使用 sail 命令行来调用 Artisan 命令。Sail 将在您的应用程序 Docker 容器中执行 Artisan 命令:

./vendor/bin/sail artisan list

Tinker (REPL)

所有的 Laravel 应用都提供了 Tinker —— 一个由 PsySH 扩展包驱动的 REPL(Read-Eval-Print Loop,即终端命令行“读取-求值-输出”循环工具)。

安装

所有 Laravel 应用默认都包含 Tinker,不过,你可以在需要的时候使用 Composer 手动安装:

composer require laravel/tinker

想要寻找一个与 Laravel 应用程序交互的图形化用户界面吗?可以看看 Tinkerwell

使用

Tinker 允许你通过命令行与整个 Laravel 应用进行交互,包括 Eloquent ORM、任务、事件等等。要进入 Tinker 环境,运行 tinker 命令即可:

php artisan tinker

你可以使用 vendor:publish 命令发布 Tinker 的配置文件:

php artisan vendor:publish --provider="Laravel\Tinker\TinkerServiceProvider"

注:dispatch 辅助函数和 Dispatchable 类上的 dispatch 方法依赖垃圾回收将任务放到队列。因此,使用 tinker 时,需要使用 Bus::dispatchQueue::push 来分发任务。

命令白名单

Tinker 使用一个白名单来判断哪个 Artisan 命令可以在 Shell 中运行。默认情况下,你可以运行 clear-compileddownenvinspiremigrateoptimizeup 命令。如果你想要在白名单中加入更多命令,可以将它们添加到 tinker.php 配置文件的 commands 数组:

'commands' => [
    // App\Console\Commands\ExampleCommand::class,
],

别名黑名单

通常,Tinker 会根据需要自动为类设置别名。不过,你可能希望永远不会为某些类添加别名,这可以通过在配置文件 tinker.phpdont_alias 数组中添加这些类来实现:

'dont_alias' => [
    App\Models\User::class,
],

编写命令

除了 Artisan 提供的系统命令之外,还可以编写自己的命令。自定义命令通常存放在 app/Console/Commands 目录下;当然,你也可以自己选择存放位置,只要该命令类可以被 Composer 自动加载即可。

生成命令

要创建一个新命令,你可以使用 Artisan 命令 make:command,该命令会在 app/Console/Commands 目录下创建一个新的命令类。如果该目录不存在,不用担心,它将会在你首次运行 Artisan 命令 make:command 时被创建。生成的命令将会包含默认的属性设置以及所有命令都共有的方法:

php artisan make:command SendEmails

命令结构

命令生成以后,需要填写该类的 signaturedescription 属性,这两个属性在调用 list 显示命令的时候会被用到。signature 属性还允许你定义命令的输入期望handle 方法在命令执行时被调用,你可以将所有命令逻辑都放在这个方法里面。

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

<?php
    
namespace App\Console\Commands;
    
use App\Models\User;
use App\Support\DripEmailer;
use Illuminate\Console\Command;
    
class SendEmails extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'mail:send {user}';
    
    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Send a marketing email to a user';
    
    /**
     * Execute the console command.
     */
    public function handle(DripEmailer $drip): void
    {
        $drip->send(User::find($this->argument('user')));
    }
}

为了更好地重复使用代码,最好将控制台命令保持轻量级,并让它们推迟到应用程序服务来完成其任务。在上面的例子中,请注意我们注入了一个服务类来完成发送电子邮件的“繁重工作”。

闭包命令

基于闭包的命令和闭包路由之于控制器一样,为以类的方式定义控制台命令提供了可选方案,在 app/Console/Kernel.php 文件的 commands 方法中,Laravel 加载了 routes/console.php 文件:

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

尽管这个文件没有定义 HTTP 路由,但是它定义了基于控制台的应用入口(和路由作用一样),在这个文件中,你可以使用 Artisan::command 方法定义所有基于闭包的路由。command 方法接收两个参数 —— 命令标识和接收命令参数和选项的闭包:

Artisan::command('mail:send {user}', function (string $user) {
    $this->info("Sending email to: {$user}!");
});

该闭包被绑定到底层命令实例,所以你可以像在完整的命令类中一样访问所有辅助方法。

类型提示依赖

除了接收命令参数和选项外,闭包命令还可以类型提示服务容器之外解析的额外依赖:

use App\Models\User;
use App\Support\DripEmailer;
    
Artisan::command('mail:send {user}', function (DripEmailer $drip, string $user) {
    $drip->send(User::find($user));
});

闭包命令描述

定义基于闭包的命令时,可以使用 purpose 方法来添加命令描述,这个描述将会在运行 php artisan listphp artisan help 命令时显示:

Artisan::command('mail:send {user}', function (string $user) {
    // ...
})->purpose('Send a marketing email to a user');

隔离命令

要使用此功能,您的应用程序必须将 memcachedredisdynamodbdatabasefilearray 缓存驱动作为应用程序的默认缓存驱动程序。此外,所有服务器必须与同一中央缓存服务器通信。

有时,您可能希望确保一次只能运行一个命令实例。为此,您可以在命令类上实现 Illuminate\Contracts\Console\Isolatable 接口:

<?php
 
namespace App\Console\Commands;
 
use Illuminate\Console\Command;
use Illuminate\Contracts\Console\Isolatable;
 
class SendEmails extends Command implements Isolatable
{
    // ...
}

当一个命令被标记为 Isolatable 时,Laravel 会自动为该命令添加一个 --isolated 选项。当使用该选项调用命令时,Laravel 将确保没有其他该命令的实例正在运行。Laravel 通过尝试使用您的应用程序的默认缓存驱动程序获取原子锁来实现此目的。如果其他命令实例正在运行,则该命令将不执行;但是,该命令仍将以成功的退出状态代码退出:

php artisan mail:send 1 --isolated

如果您想指定命令在无法执行时应返回的退出状态代码,可以通过 isolated 选项提供所需的状态代码:

php artisan mail:send 1 --isolated=12

锁过期时间

默认情况下,隔离锁在命令完成后会过期。或者,如果命令被中断并且无法完成,锁将在一小时后过期。但是,您可以通过在命令上定义一个 isolationLockExpiresAt 方法来调整锁的过期时间:

use DateTimeInterface;
use DateInterval;
 
/**
 * Determine when an isolation lock expires for the command.
 */
public function isolationLockExpiresAt(): DateTimeInterface|DateInterval
{
    return now()->addMinutes(5);
}

定义输入期望

编写控制台命令的时候,通常通过参数和选项收集用户输入,Laravel 使这项操作变得很方便:在命令中使用 signature 属性来定义我们期望的用户输入。signature 属性通过一个优雅的、路由风格的语法允许你定义命令的名称、参数以及选项。

参数

所有用户提供的参数和选项都包含在花括号里,下面这个例子定义的命令要求用户输入必选参数 user

/**
 * 控制台命令名称
 *
 * @var string
 */
protected $signature = 'email:send {user}';

你还可以让该参数可选并定义默认的可选参数值:

// 选项参数...
mail:send {user?}

// 带默认值的选项参数...
mail:send {user=foo}

选项

选项,和参数一样,是用户输入的另一种格式,不同之处在于选项前面有两个短划线(--),有两种类型的选项:接收值和不接收值的。不接收值的选项一般用作布尔开关。我们来看一个这种类型的选项:

/**
 * 控制台命令名称
 *
 * @var string
 */
protected $signature = 'mail:send {user} {--queue}';

在本例中,--queue 开关在调用 Artisan 命令的时候被指定。如果 --queue 被传递,对应开关值是 true,否则其值是 false

php artisan email:send 1 --queue

带值的选项

接下来,我们来看一个带值的选项,如果用户必须为选项指定值,需要通过=进行分配:

/**
 * 控制台命令名称
 *
 * @var string
 */
protected $signature = 'mail:send {user} {--queue=}';

在这个例子中,用户可以通过这样的方式传值:

php artisan email:send 1 --queue=default

还可以给选项分配默认值,如果用户没有传递值给选项,将会使用默认值:

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

选项缩写

如果想要为命令选项分配一个缩写,可以在选项前指定并使用分隔符|将缩写和完整选项名分开:

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

在终端上调用命令时,选项缩写应该以单个连字符为前缀,当为选项指定值时不应包含 = 字符:

php artisan mail:send 1 -Qdefault

输入数组

如果你想要定义参数和选项以便指定输入数组,可以使用字符*,首先,让我们看一个指定数组参数的例子:

mail:send {user*}

调用此方法时,用户参数可以按顺序传递到命令行中。例如,以下命令将将 user 的值设置为一个包含 1 和 2 作为其值的数组:

php artisan mail:send 1 2

字符可以与可选参数定义相结合,以允许零个或多个参数实例:

'mail:send {user?*}'

选项数组

当定义一个需要多个输入值的选项时,传递给命令的每个选项值应以选项名称为前缀:

'mail:send {--id=*}'

可以通过传递多个 --id 参数来调用此类命令:

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

输入描述

你可以通过冒号将参数和描述进行分隔的方式分配描述到输入参数和选项,如果你需要一些空间来定义命令,可以通过换行来定义命令:

/**
 * 控制台命令名称
 *
 * @var string
 */
protected $signature = 'mail:send
                        {user : The ID of the user}
                        {--queue= : Whether the job should be queued}';

命令 I/O

获取输入

在命令被执行的时候,很明显,你需要访问命令获取的参数和选项的值。使用 argumentoption 方法即可实现:

/**
 * 执行控制台命令
 */
public function handle(): void
{
    $userId = $this->argument('user');
}

如果需要以数组方式返回所有参数的值,调用 arguments 方法:

$arguments = $this->arguments();

选项值和参数值的获取一样简单,使用 option 方法,要以数组方式返回所有选项值,可以调用 options 方法:

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

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

如果参数或选项不存在,返回 null

输入提示

除了显示输出之外,你可能还要在命令执行期间要用户提供输入。ask 方法将会使用给定问题提示用户,接收输入,然后返回用户输入到命令:

/**
 * 执行控制台命令
 */
public function handle(): void
{
    $name = $this->ask('What is your name?');
}

secret 方法和 ask 方法类似,但用户输入在终端对他们而言是不可见的,这个方法在问用户一些敏感信息如密码时很有用:

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

让用户确认

如果你需要让用户确认信息,可以使用 confirm 方法,默认情况下,该方法返回 false,如果用户输入y,则该方法返回true

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

如果需要的话,您可以通过将 true 作为 confirm 方法的第二个参数传递来指定确认提示默认应返回 true

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

自动完成

anticipate 方法可用于为可能的选项提供自动完成功能,用户仍然可以选择答案,而不管这些选择:

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

作为替代方案,你还可以传递闭包作为 anticipate 方法的第二个参数,这个闭包会在每次用户输入一个字符时被调用,该闭包会接收一个包含用户输入的字符串参数,并返回自动完成所需的数组选项:

$name = $this->anticipate('What is your name?', function (string $input) {
    // Return auto-completion options...
});

给用户提供选择

如果你需要给用户预定义的选择,可以使用 choice 方法。用户选择答案的索引,但是返回给你的是答案的值。如果用户什么都没选的话你可以设置默认返回的值:

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

此外,choice 方法还可以接受第四、第五个可选参数,用于确定有效响应的最大尝试次数以及多个选择是否被许可:

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

编写输出

要将输出发送到控制台,使用 line, info, comment, questionerror 方法,每个方法都会使用相应的 ANSI 颜色以作标识。例如,要显示一条信息消息给用户, 使用 info 方法在终端显示为绿色:

/**
 * 执行控制台命令
 */
public function handle(): void
{
    $this->info('The command was successful!');
}

要显示一条错误消息,使用 error 方法。错误消息文本通常是红色:

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

如果你想要显示原生输出,可以使用line 方法,该方法输出的字符不带颜色:

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

The newLine 方法可用于输出空行:

// 输出单个空行...
$this->newLine();

// 输出三个空行...
$this->newLine(3);

表格布局

table 方法使输出多行/列格式的数据变得简单,只需要将头和行传递给该方法,宽度和高度将基于给定数据自动计算:

use App\Models\User;
 
$this->table(
    ['Name', 'Email'],
    User::all(['name', 'email'])->toArray()
);

进度条

针对长时间运行的任务,使用进度条可以帮助用户了解任务完成的进度。使用 withProgressBar 方法,Laravel 会显示一个进度条,并在遍历给定可迭代值的每个元素时推进其进度:

use App\Models\User;
 
$users = $this->withProgressBar(User::all(), function (User $user) {
    $this->performTask($user);
});

有时,你可能需要更多手动控制进度条的推进。首先,定义进程将遍历的总步数。然后,在处理每个项目后推进进度条:

$users = App\Models\User::all();
 
$bar = $this->output->createProgressBar(count($users));
 
$bar->start();
 
foreach ($users as $user) {
    $this->performTask($user);
 
    $bar->advance();
}
 
$bar->finish();

想要了解更多,查看Symfony进度条组件文档

注册命令

在应用的 App\Console\Kernel 类中,所有的控制台命令都会被注册,该类即为你的应用的“控制台内核”。在该类的 commands 方法中,你会看到对内核的 load 方法的调用。load 方法将扫描 app/Console/Commands 目录,并将其中的每个命令自动注册到 Artisan 中。你还可以自由调用 load 方法来扫描其他目录中的 Artisan 命令:

/**
 * Register the commands for the application.
 */
protected function commands(): void
{
    $this->load(__DIR__.'/Commands');
    $this->load(__DIR__.'/../Domain/Orders/Commands');
 
    // ...
}

如有必要,你可以通过将命令的类名添加到 $commands 属性来手动注册命令,该属性定义在你的 App\Console\Kernel 类中。如果这个属性在你的内核上未定义,你应该手动定义它。当 Artisan 启动时,此属性中列出的所有命令将由服务容器解析并注册到 Artisan 中:

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

程序化调用命令

有时候你可能希望在 CLI 之外执行 Artisan 命令,比如,你可能希望在路由或控制器中触发 Artisan 命令,这可以使用 Artisan 门面上的 call 方法来实现。call 方法接收被执行的命令名称作为第一个参数,命令参数数组作为第二个参数,退出码会被返回:

use Illuminate\Support\Facades\Artisan;
 
Route::post('/user/{user}/mail', function (string $user) {
    $exitCode = Artisan::call('mail:send', [
        'user' => $user, '--queue' => 'default'
    ]);
 
    // ...
});

此外,你也可以以字符串方式传递完整的 Artisan 命令到 call 方法:

Artisan::call('mail:send 1 --queue=default');

传递数组值

如果命令定义了接收数组的选项,可以传递数组值到该选项:

use Illuminate\Support\Facades\Artisan;
 
Route::post('/mail', function () {
    $exitCode = Artisan::call('mail:send', [
        '--id' => [5, 13]
    ]);
});

传递布尔值

如果你需要指定不接收字符串的选项值,例如 migrate:refresh 命令上的--force 标识,可以传递布尔值 truefalse

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

将 Artisan 命令加入队列

使用 Artisan 门面上的 queue 方法,您甚至可以将 Artisan 命令加入队列,以便由队列工作者在后台处理。在使用此方法之前,请确保已配置您的队列并运行队列监听器:

use Illuminate\Support\Facades\Artisan;
 
Route::post('/user/{user}/mail', function (string $user) {
    Artisan::queue('mail:send', [
        'user' => $user, '--queue' => 'default'
    ]);
 
    // ...
});

使用 onConnectiononQueue 方法,您可以指定应将 Artisan 命令分派到的连接或队列:

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

通过其他命令调用命令

有时候你希望从一个已存在的 Artisan 命令中调用其它命令。你可以通过使用 call 方法开实现这一目的。call 方法接收命令名称和数组形式的命令参数:

/**
 * 执行控制台命令
 */
public function handle(){
    $this->call('mail:send', [
        'user' => 1, '--queue' => 'default'
    ]);
}

如果你想要调用其它控制台命令并阻止其所有输出,可以使用callSilently 方法。callSilently 方法和 call 方法用法一致:

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

信号处理

如您所知,操作系统允许向正在运行的进程发送信号。例如,SIGTERM 信号是操作系统请求程序终止的方式。如果您希望在 Artisan 命令中监听信号并在发生时执行代码,可以使用 trap 方法:

/**
 * Execute the console command.
 */
public function handle(): void
{
    $this->trap(SIGTERM, fn () => $this->shouldKeepRunning = false);
 
    while ($this->shouldKeepRunning) {
        // ...
    }
}

要同时监听多个信号,可以向 trap 方法提供信号数组:

$this->trap([SIGTERM, SIGQUIT], function (int $signal) {
    $this->shouldKeepRunning = false;
 
    dump($signal); // SIGTERM / SIGQUIT
});

桩自定义

Artisan 控制台提供的 make 命令可用于创建多种类型的类,例如控制器、队列任务、数据库迁移和测试等。这些类都是通过「桩」文件以及命令行传入参数值填充占位符自动生成,大多数时候,不需要要做任何修改,生成的类就可以满足我们的需求,不过,如果你想要修改通过 Artisan 命令自动生成的文件内容,可以通过执行 stub:pulish 命令来发布最常用的桩文件来对它们进行自定义(桩文件默认都位于依赖包的 stubs 目录下):

php artisan stub:publish

发布的桩文件将位于应用程序根目录中的 stubs 目录中。使用 Artisan 的 make 命令生成相应类时,您对这些桩文件所做的任何更改都将反映在生成的类中。

事件

当运行命令时,Artisan 会触发三个事件:Illuminate\Console\Events\ArtisanStartingIlluminate\Console\Events\CommandStartingIlluminate\Console\Events\CommandFinishedArtisanStarting 事件在 Artisan 开始运行时立即触发。接下来,在命令运行之前立即触发 CommandStarting 事件。最后,一旦命令执行完毕,将会触发 CommandFinished 事件。


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

<< 上一篇: 日志

>> 下一篇: 广播