跳至内容

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。但是,如果您之前已从应用程序中删除了 Tinker,则可以使用 Composer 安装 Tinker:

1composer require laravel/tinker

想要在与 Laravel 应用程序交互时使用热重载、多行代码编辑和自动完成功能吗?快来Tinkerwell看看吧!

用法

Tinker 允许你在命令行上与整个 Laravel 应用程序进行交互,包括 Eloquent 模型、作业、事件等等。要进入 Tinker 环境,请运行tinkerArtisan 命令:

1php artisan tinker

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

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

该类中的辅助dispatch函数和dispatch方法Dispatchable依赖于垃圾回收机制来将作业放入队列。因此,在使用 Tinker 时,应该使用Bus::dispatchQueue::push来调度作业。

命令允许列表

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

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

不应使用别名的类

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

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

编写命令

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

生成命令

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

1php artisan make:command SendEmails

指挥结构

生成命令后,您应该为类的signaturedescription属性定义适当的值。这些属性将在屏幕上显示命令时使用listsignature属性还允许您定义命令的输入期望handle执行命令时将调用 方法。您可以将命令逻辑放入此方法中。

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

1<?php
2 
3namespace App\Console\Commands;
4 
5use App\Models\User;
6use App\Support\DripEmailer;
7use Illuminate\Console\Command;
8 
9class SendEmails extends Command
10{
11 /**
12 * The name and signature of the console command.
13 *
14 * @var string
15 */
16 protected $signature = 'mail:send {user}';
17 
18 /**
19 * The console command description.
20 *
21 * @var string
22 */
23 protected $description = 'Send a marketing email to a user';
24 
25 /**
26 * Execute the console command.
27 */
28 public function handle(DripEmailer $drip): void
29 {
30 $drip->send(User::find($this->argument('user')));
31 }
32}

为了提高代码复用率,最好保持控制台命令的简洁,并让它们交给应用服务来完成任务。在上面的示例中,请注意,我们注入了一个服务类来执行发送电子邮件的“重任”。

退出代码

如果该方法未返回任何内容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});

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

类型Prompts依赖关系

除了接收命令的参数和选项之外,命令闭包还可以Prompts您希望从服务容器中解析的其他依赖项:

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

关闭命令描述

定义基于闭包的命令时,可以使用该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 会使用命令的名称来生成用于获取应用程序缓存中原子锁的字符串键。但是,你可以isolatableId在 Artisan 命令类中定义一个方法来自定义此键,从而将命令的参数或选项集成到键中:

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()->addMinutes(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}';

在此示例中,--queue调用 Artisan 命令时可以指定开关。如果--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}';

Prompts缺少输入

如果您的命令包含必需参数,则当用户未提供这些参数时,将收到错误消息。或者,您可以通过实现以下接口将命令配置为在缺少必需参数时自动Prompts用户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}

您还可以使用包含问题和占位符的元组来提供占位符文本:

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

如果您希望完全控制Prompts,您可以提供一个闭包来Prompts用户并返回他们的答案:

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::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all()
12 : []
13 ),
14];

全面的Laravel Prompts文档包含有关可用Prompts及其用法的更多信息。

如果您希望Prompts用户选择或输入选项,则可以在命令handle方法中包含Prompts。但是,如果您只希望在用户自动输入缺少的参数时Prompts用户,则可以实现该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}

命令输入/输出

检索输入

在命令执行过程中,您可能需要访问命令接受的参数和选项的值。为此,您可以使用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();

Prompts输入

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

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

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来响应Prompts,则该方法将返回true

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

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

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

自动完成

anticipate方法可用于为可能的选项提供自动完成功能。无论自动完成Prompts如何,用户仍然可以提供任何答案:

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);

写入输出

要将输出发送到控制台,可以使用lineinfocommentquestionwarnerror方法。每种方法都会根据其用途使用相应的 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);

表格

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});

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

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指示 Laravel 扫描其他目录中的 Artisan 命令withCommandsbootstrap/app.php

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 命令。您可以使用Facadecall上的方法Artisan来实现这一点。该call方法接受命令的签名名称或类名作为其第一个参数,并接受一个命令参数数组作为第二个参数。返回退出代码:

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

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

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

传递数组值

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

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

传递布尔值

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

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

Artisan 命令队列

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

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

使用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});

存根定制

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

1php artisan stub:publish

已发布的存根文件将位于stubs应用程序根目录下。您对这些存根文件所做的任何更改都将在使用 Artisanmake命令生成其对应的类时反映出来。

Events

Artisan 在运行命令时会调度三个事件:Illuminate\Console\Events\ArtisanStartingIlluminate\Console\Events\CommandStartingIlluminate\Console\Events\CommandFinishedArtisanStartingArtisan 开始运行时会立即调度该事件。其次,CommandStarting命令运行前会立即调度该事件。最后,CommandFinished命令执行完毕后会调度该事件。