跳转至内容

Artisan 控制台

简介

Artisan 是 Laravel 内置的命令行界面。Artisan 位于您应用程序的根目录下,作为一个 artisan 脚本存在,并提供许多有用的命令来辅助您进行应用程序开发。要查看所有可用 Artisan 命令的列表,可以使用 list 命令

1php artisan list

每个命令还包含一个“帮助”屏幕,用于显示并描述该命令的可用参数和选项。要查看帮助屏幕,请在命令名称前加上 help

1php artisan help migrate

Laravel Sail

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

1./vendor/bin/sail artisan list

Tinker (REPL)

Laravel Tinker 是一个强大的 Laravel 框架 REPL(交互式解释器),由 PsySH 包提供支持。

安装

所有 Laravel 应用程序默认都包含 Tinker。如果您之前从应用程序中移除了它,可以使用 Composer 重新安装 Tinker

1composer require laravel/tinker

在与 Laravel 应用程序交互时,是否在寻找热重载、多行代码编辑和自动补全功能?请查看 Tinkerwell

使用方法

Tinker 允许您在命令行与整个 Laravel 应用程序进行交互,包括 Eloquent 模型、任务(jobs)、事件等。要进入 Tinker 环境,请运行 tinker Artisan 命令

1php artisan tinker

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

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

dispatch 辅助函数和 Dispatchable 类上的 dispatch 方法依赖垃圾回收机制将任务放置到队列中。因此,在使用 Tinker 时,您应该使用 Bus::dispatchQueue::push 来分发任务。

命令允许列表

Tinker 使用一个“允许”列表来确定哪些 Artisan 命令可以在其 shell 中运行。默认情况下,您可以运行 clear-compileddownenvinspiremigratemigrate:installupoptimize 命令。如果您想允许更多命令,可以将它们添加到 tinker.php 配置文件中的 commands 数组中

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

不应别名化的类

通常,当您在 Tinker 中与类交互时,Tinker 会自动为其创建别名。但是,您可能希望某些类永远不要被别名化。您可以通过在 tinker.php 配置文件的 dont_alias 数组中列出这些类来实现这一点

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

编写命令

除了 Artisan 提供的命令外,您还可以构建自己的自定义命令。命令通常存储在 app/Console/Commands 目录中;但是,只要您告知 Laravel 扫描其他目录以查找 Artisan 命令,您可以自由选择自己的存储位置。

生成命令

要创建新命令,可以使用 make:command Artisan 命令。此命令将在 app/Console/Commands 目录中创建一个新的命令类。如果应用程序中不存在此目录也不必担心——它会在您首次运行 make:command Artisan 命令时自动创建

1php artisan make:command SendEmails

命令结构

生成命令后,您应该使用 SignatureDescription 属性来定义命令的签名和描述。Signature 属性还允许您定义 命令的输入预期。当命令执行时,将调用 handle 方法。您可以在此方法中放置命令逻辑。

让我们看一个示例命令。请注意,我们可以通过命令的 handle 方法请求所需的任何依赖项。Laravel 服务容器 将自动注入此方法签名中声明的所有类型提示依赖项

1<?php
2 
3namespace App\Console\Commands;
4 
5use App\Models\User;
6use App\Support\DripEmailer;
7use Illuminate\Console\Attributes\Description;
8use Illuminate\Console\Attributes\Signature;
9use Illuminate\Console\Command;
10 
11#[Signature('mail:send {user}')]
12#[Description('Send a marketing email to a user')]
13class SendEmails extends Command
14{
15 /**
16 * Execute the console command.
17 */
18 public function handle(DripEmailer $drip): void
19 {
20 $drip->send(User::find($this->argument('user')));
21 }
22}

为了实现更好的代码重用,保持控制台命令轻量化并将其任务委托给应用程序服务是一种良好的实践。在上面的示例中,请注意我们注入了一个服务类来处理发送电子邮件的“繁重工作”。

退出代码

如果 handle 方法没有返回值且命令执行成功,命令将以 0 退出代码结束,表示成功。但是,handle 方法也可以选择返回一个整数,以手动指定命令的退出代码

1$this->error('Something went wrong.');
2 
3return 1;

如果您希望在命令内的任何方法中使命令“失败”,可以使用 fail 方法。fail 方法将立即终止命令的执行并返回 1 的退出代码

1$this->fail('Something went wrong.');

闭包命令

基于闭包的命令提供了一种将控制台命令定义为类的替代方案。就像路由闭包是控制器的替代方案一样,可以将命令闭包视为命令类的替代方案。

尽管 routes/console.php 文件不定义 HTTP 路由,但它定义了进入您应用程序的基于控制台的入口点(路由)。在此文件中,您可以使用 Artisan::command 方法定义所有基于闭包的控制台命令。command 方法接受两个参数:命令签名 和一个接收命令参数和选项的闭包

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

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

依赖项类型提示

除了接收命令的参数和选项外,命令闭包还可以对您希望从 服务容器 解析的其他依赖项进行类型提示

1use App\Models\User;
2use App\Support\DripEmailer;
3use Illuminate\Support\Facades\Artisan;
4 
5Artisan::command('mail:send {user}', function (DripEmailer $drip, string $user) {
6 $drip->send(User::find($user));
7});

闭包命令描述

定义基于闭包的命令时,可以使用 purpose 方法为命令添加描述。当您运行 php artisan listphp artisan help 命令时,将显示此描述

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

可隔离命令

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

有时您可能希望确保同一时间只有一个命令实例在运行。要实现这一点,您可以在命令类上实现 Illuminate\Contracts\Console\Isolatable 接口

1<?php
2 
3namespace App\Console\Commands;
4 
5use Illuminate\Console\Command;
6use Illuminate\Contracts\Console\Isolatable;
7 
8class SendEmails extends Command implements Isolatable
9{
10 // ...
11}

当您将命令标记为 Isolatable 时,Laravel 会自动使 --isolated 选项可用于该命令,而无需在命令的选项中显式定义它。当使用该选项调用命令时,Laravel 将确保没有其他该命令的实例正在运行。Laravel 通过尝试使用应用程序的默认缓存驱动程序获取原子锁来实现这一点。如果其他命令实例正在运行,该命令将不会执行;但是,命令仍将以成功的退出状态码退出

1php artisan mail:send 1 --isolated

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

1php artisan mail:send 1 --isolated=12

锁 ID

默认情况下,Laravel 将使用命令名称来生成用于在应用程序缓存中获取原子锁的字符串键。但是,您可以通过在 Artisan 命令类上定义 isolatableId 方法来自定义此键,从而允许您将命令的参数或选项集成到键中

1/**
2 * Get the isolatable ID for the command.
3 */
4public function isolatableId(): string
5{
6 return $this->argument('user');
7}

锁过期时间

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

1use DateTimeInterface;
2use DateInterval;
3 
4/**
5 * Determine when an isolation lock expires for the command.
6 */
7public function isolationLockExpiresAt(): DateTimeInterface|DateInterval
8{
9 return now()->plus(minutes: 5);
10}

定义输入预期

在编写控制台命令时,通过参数或选项收集用户输入是很常见的。Laravel 使用命令上的 signature 属性,让定义预期的用户输入变得非常方便。signature 属性允许您以一种简洁、富有表现力、类似于路由的语法定义命令的名称、参数和选项。

参数

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

1/**
2 * The name and signature of the console command.
3 *
4 * @var string
5 */
6protected $signature = 'mail:send {user}';

您还可以设置可选参数或为参数定义默认值

1// Optional argument...
2'mail:send {user?}'
3 
4// Optional argument with default value...
5'mail:send {user=foo}'

选项

选项和参数一样,是用户输入的另一种形式。通过命令行提供选项时,选项前缀有两个连字符 (--)。选项有两种类型:接收值的和不接收值的。不接收值的选项充当布尔“开关”。让我们看看这种选项的一个例子

1/**
2 * The name and signature of the console command.
3 *
4 * @var string
5 */
6protected $signature = 'mail:send {user} {--queue}';

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

1php artisan mail:send 1 --queue

带值的选项

接下来,让我们看看一个需要值的选项。如果用户必须为选项指定值,则应在选项名称后加上 =

1/**
2 * The name and signature of the console command.
3 *
4 * @var string
5 */
6protected $signature = 'mail:send {user} {--queue=}';

在此示例中,用户可以这样为该选项传递值。如果调用命令时未指定该选项,其值将为 null

1php artisan mail:send 1 --queue=default

您可以通过在选项名称后指定默认值来为选项分配默认值。如果用户没有传递选项值,则将使用默认值

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

选项快捷方式

要在定义选项时分配快捷方式,可以在选项名称前指定它,并使用 | 字符作为分隔符将快捷方式与完整选项名称分开

1'mail:send {user} {--Q|queue=}'

在终端调用命令时,选项快捷方式应以单个连字符为前缀,并且在为选项指定值时,不应包含 = 字符

1php artisan mail:send 1 -Qdefault

输入数组

如果您想定义参数或选项以期望多个输入值,可以使用 * 字符。首先,让我们看一个指定此类参数的示例

1'mail:send {user*}'

运行此命令时,user 参数可以依次传递给命令行。例如,以下命令将 user 的值设置为一个数组,其值为 12

1php artisan mail:send 1 2

这个 * 字符可以与可选参数定义结合使用,以允许零个或多个参数实例

1'mail:send {user?*}'

选项数组

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

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

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

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

输入描述

您可以通过使用冒号将参数名称与描述分开,为输入参数和选项分配描述。如果您需要更多空间来定义命令,请随意将定义分散在多行上

1/**
2 * The name and signature of the console command.
3 *
4 * @var string
5 */
6protected $signature = 'mail:send
7 {user : The ID of the user}
8 {--queue : Whether the job should be queued}';

缺失输入提示

如果您的命令包含必需参数,当用户未提供这些参数时,用户将收到错误消息。或者,您可以通过实现 PromptsForMissingInput 接口,配置您的命令在缺少必需参数时自动提示用户

1<?php
2 
3namespace App\Console\Commands;
4 
5use Illuminate\Console\Command;
6use Illuminate\Contracts\Console\PromptsForMissingInput;
7 
8class SendEmails extends Command implements PromptsForMissingInput
9{
10 /**
11 * The name and signature of the console command.
12 *
13 * @var string
14 */
15 protected $signature = 'mail:send {user}';
16 
17 // ...
18}

如果 Laravel 需要从用户那里收集必需参数,它将通过使用参数名称或描述来智能地组织问题,从而自动询问用户。如果您希望自定义用于收集必需参数的问题,可以实现 promptForMissingArgumentsUsing 方法,并返回一个以参数名称为键的问题数组

1/**
2 * Prompt for missing input arguments using the returned questions.
3 *
4 * @return array<string, string>
5 */
6protected function promptForMissingArgumentsUsing(): array
7{
8 return [
9 'user' => 'Which user ID should receive the mail?',
10 ];
11}

您还可以通过使用包含问题和占位符的元组(tuple)来提供占位符文本

1return [
2 'user' => ['Which user ID should receive the mail?', 'E.g. 123'],
3];

如果您想完全控制提示,可以提供一个闭包,该闭包应提示用户并返回他们的回答

1use App\Models\User;
2use function Laravel\Prompts\search;
3 
4// ...
5 
6return [
7 'user' => fn () => search(
8 label: 'Search for a user:',
9 placeholder: 'E.g. Taylor Otwell',
10 options: fn ($value) => strlen($value) > 0
11 ? User::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
12 : []
13 ),
14];

详尽的 Laravel Prompts 文档包含了有关可用提示及其用法的更多信息。

如果您希望提示用户选择或输入 选项,可以在命令的 handle 方法中包含提示。但是,如果您只想在用户已被自动提示缺少参数时才提示用户,那么您可以实现 afterPromptingForMissingArguments 方法

1use Symfony\Component\Console\Input\InputInterface;
2use Symfony\Component\Console\Output\OutputInterface;
3use function Laravel\Prompts\confirm;
4 
5// ...
6 
7/**
8 * Perform actions after the user was prompted for missing arguments.
9 */
10protected function afterPromptingForMissingArguments(InputInterface $input, OutputInterface $output): void
11{
12 $input->setOption('queue', confirm(
13 label: 'Would you like to queue the mail?',
14 default: $this->option('queue')
15 ));
16}

命令 I/O

获取输入

在命令执行时,您可能需要访问命令所接受的参数和选项的值。为此,您可以使用 argumentoption 方法。如果参数或选项不存在,则将返回 null

1/**
2 * Execute the console command.
3 */
4public function handle(): void
5{
6 $userId = $this->argument('user');
7}

如果您需要将所有参数作为 array 获取,请调用 arguments 方法

1$arguments = $this->arguments();

使用 option 方法获取选项就像获取参数一样简单。要将所有选项作为数组获取,请调用 options 方法

1// Retrieve a specific option...
2$queueName = $this->option('queue');
3 
4// Retrieve all options as an array...
5$options = $this->options();

输入提示

Laravel Prompts 是一个 PHP 包,用于为您的命令行应用程序添加美观且用户友好的表单,并具有类似浏览器的功能,包括占位符文本和验证。

除了显示输出外,您还可以在命令执行期间要求用户提供输入。ask 方法将向用户提问,接受他们的输入,然后将用户的输入返回给您的命令

1/**
2 * Execute the console command.
3 */
4public function handle(): void
5{
6 $name = $this->ask('What is your name?');
7 
8 // ...
9}

ask 方法还接受一个可选的第二个参数,该参数指定如果未提供用户输入应返回的默认值

1$name = $this->ask('What is your name?', 'Taylor');

secret 方法与 ask 类似,但用户的输入在控制台中输入时对他们不可见。当询问密码等敏感信息时,此方法非常有用

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

询问确认

如果您需要询问用户进行简单的“是或否”确认,可以使用 confirm 方法。默认情况下,此方法将返回 false。但是,如果用户在响应提示时输入 yyes,则该方法将返回 true

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

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

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

自动补全

anticipate 方法可用于为可能的选择提供自动补全。无论自动补全提示如何,用户仍然可以提供任何答案

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

或者,您可以将闭包作为第二个参数传递给 anticipate 方法。每当用户键入输入字符时,都会调用该闭包。该闭包应接受一个包含用户目前输入的字符串参数,并返回一个用于自动补全的选项数组

1use App\Models\Address;
2 
3$name = $this->anticipate('What is your address?', function (string $input) {
4 return Address::whereLike('name', "{$input}%")
5 ->limit(5)
6 ->pluck('name')
7 ->all();
8});

多选题

如果您需要在提问时为用户提供一组预定义的选项,可以使用 choice 方法。如果未选择任何选项,您可以通过将数组索引作为方法的第三个参数传递,来设置返回的默认值的数组索引

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

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

1$name = $this->choice(
2 'What is your name?',
3 ['Taylor', 'Dayle'],
4 $defaultIndex,
5 $maxAttempts = null,
6 $allowMultipleSelections = false
7);

写入输出

要将输出发送到控制台,可以使用 linenewLineinfocommentquestionwarnalerterror 方法。这些方法中的每一个都将使用适当的 ANSI 颜色来达到其目的。例如,让我们向用户显示一些一般信息。通常,info 方法会在控制台中以绿色文本显示

1/**
2 * Execute the console command.
3 */
4public function handle(): void
5{
6 // ...
7 
8 $this->info('The command was successful!');
9}

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

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

您可以使用 line 方法显示纯文本,不带任何颜色

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

您可以使用 newLine 方法显示一个空行

1// Write a single blank line...
2$this->newLine();
3 
4// Write three blank lines...
5$this->newLine(3);

数据表 (Tables)

table 方法可以轻松地正确格式化数据的多行/列。您所需要做的就是提供列名和表格数据,Laravel 将自动为您计算表格的适当宽度和高度

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

进度条

对于长时间运行的任务,显示一个进度条来告知用户任务完成了多少是有帮助的。使用 withProgressBar 方法,Laravel 将显示一个进度条,并针对给定可迭代值的每次迭代推进其进度

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

有时,您可能需要对进度条的推进方式进行更手动的控制。首先,定义进程将迭代的总步数。然后,在处理每个项目后推进进度条

1$users = App\Models\User::all();
2 
3$bar = $this->output->createProgressBar(count($users));
4 
5$bar->start();
6 
7foreach ($users as $user) {
8 $this->performTask($user);
9 
10 $bar->advance();
11}
12 
13$bar->finish();

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

注册命令

默认情况下,Laravel 会自动注册 app/Console/Commands 目录中的所有命令。但是,您可以使用应用程序 bootstrap/app.php 文件中的 withCommands 方法,指示 Laravel 扫描其他目录以查找 Artisan 命令

1->withCommands([
2 __DIR__.'/../app/Domain/Orders/Commands',
3])

如有必要,您还可以通过将命令的类名提供给 withCommands 方法来手动注册命令

1use App\Domain\Orders\Commands\SendEmails;
2 
3->withCommands([
4 SendEmails::class,
5])

当 Artisan 启动时,应用程序中的所有命令都将由 服务容器 解析并向 Artisan 注册。

程序化执行命令

有时您可能希望在 CLI 之外执行 Artisan 命令。例如,您可能希望从路由或控制器执行 Artisan 命令。您可以使用 Artisan 外观(facade)上的 call 方法来实现这一点。call 方法接受命令的签名名称或类名作为其第一个参数,并接受命令参数数组作为第二个参数。它将返回退出代码

1use Illuminate\Support\Facades\Artisan;
2use Illuminate\Support\Facades\Route;
3 
4Route::post('/user/{user}/mail', function (string $user) {
5 $exitCode = Artisan::call('mail:send', [
6 'user' => $user, '--queue' => 'default'
7 ]);
8 
9 // ...
10});

或者,您可以将整个 Artisan 命令作为字符串传递给 call 方法

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

传递数组值

如果您的命令定义了一个接受数组的选项,您可以将值数组传递给该选项

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

传递布尔值

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

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

队列化 Artisan 命令

使用 Artisan 外观上的 queue 方法,您甚至可以对 Artisan 命令进行排队,以便由您的 队列工作进程 在后台处理它们。在使用此方法之前,请确保已配置队列并正在运行队列监听器

1use Illuminate\Support\Facades\Artisan;
2use Illuminate\Support\Facades\Route;
3 
4Route::post('/user/{user}/mail', function (string $user) {
5 Artisan::queue('mail:send', [
6 'user' => $user, '--queue' => 'default'
7 ]);
8 
9 // ...
10});

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

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

从其他命令调用命令

有时您可能希望从现有的 Artisan 命令中调用其他命令。您可以使用 call 方法来实现。此 call 方法接受命令名称和命令参数/选项数组

1/**
2 * Execute the console command.
3 */
4public function handle(): void
5{
6 $this->call('mail:send', [
7 'user' => 1, '--queue' => 'default'
8 ]);
9 
10 // ...
11}

如果您想调用另一个控制台命令并抑制其所有输出,可以使用 callSilently 方法。callSilently 方法具有与 call 方法相同的签名

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

信号处理

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

1/**
2 * Execute the console command.
3 */
4public function handle(): void
5{
6 $this->trap(SIGTERM, fn () => $this->shouldKeepRunning = false);
7 
8 while ($this->shouldKeepRunning) {
9 // ...
10 }
11}

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

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

存根(Stub)自定义

Artisan 控制台的 make 命令用于创建各种类,例如控制器、任务、迁移和测试。这些类是使用根据您的输入填充值的“存根”(stub)文件生成的。但是,您可能希望对 Artisan 生成的文件进行细微更改。要实现这一点,可以使用 stub:publish 命令将最常见的存根发布到您的应用程序中,以便您可以对其进行自定义

1php artisan stub:publish

发布的存根将位于应用程序根目录下的 stubs 目录中。当您使用 Artisan 的 make 命令生成相应的类时,您对这些存根所做的任何更改都将得到体现。

活动

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