Artisan 控制器
介绍
Artisan 是 Laravel 附带的命令行界面。Artisan 作为应用程序根目录下的 artisan
脚本存在,并提供了许多有用的命令,帮助你在构建应用程序时提高效率。要查看所有可用的 Artisan 命令,可以使用 list
命令:
php artisan list
每个命令都包含一个 “帮助” 屏幕,展示并描述命令的可用参数和选项。要查看帮助屏幕,可以在命令前加上 help
:
php artisan help migrate
Laravel Sail
如果你使用 【Laravel Sail】 作为本地开发环境,请记住使用 sail
命令行来调用 Artisan 命令。Sail 会在应用程序的 Docker 容器内执行你的 Artisan 命令:
./vendor/bin/sail artisan list
Tinker (REPL)
Laravel Tinker 是一个强大的 REPL(交互式命令行环境),为 Laravel 框架提供支持,依赖于 PsySH 包。
安装
所有 Laravel 应用程序默认包含 Tinker。如果你之前将其从应用中移除,可以通过 Composer 重新安装 Tinker:
composer require laravel/tinker
想要在与 Laravel 应用交互时获得热重载、多行代码编辑和自动补全?可以试试 Tinkerwell! |
使用
Tinker 允许你在命令行中与整个 Laravel 应用交互,包括 Eloquent 模型、作业、事件等。要进入 Tinker 环境,可以运行 tinker
Artisan 命令:
php artisan tinker
你可以使用 vendor:publish
命令发布 Tinker 的配置文件:
php artisan vendor:publish --provider="Laravel\Tinker\TinkerServiceProvider"
|
编写命令
除了 Artisan 提供的命令,你还可以构建自己的自定义命令。命令通常存储在 app/Console/Commands
目录中;不过,你可以选择自己的存储位置,只要这些命令可以通过 Composer 加载即可。
生成命令
要创建一个新的命令,可以使用 make:command
Artisan 命令。此命令将在 app/Console/Commands
目录下创建一个新的命令类。如果你的应用程序中没有这个目录,第一次运行 make:command
Artisan 命令时,Laravel 会自动创建该目录:
php artisan make:command SendEmails
命令结构
在生成命令之后,你应该为类的 signature
和 description
属性定义适当的值。这些属性将在显示命令列表时使用。signature
属性还允许你定义命令的输入要求。handle
方法将在执行命令时被调用,你可以将命令的逻辑放在这个方法中。
让我们来看一个命令的示例。请注意,我们可以通过命令的 handle
方法请求所需的任何依赖项。Laravel 服务容器会自动注入该方法签名中类型提示的所有依赖项:
<?php
namespace App\Console\Commands;
use App\Models\User;
use App\Support\DripEmailer;
use Illuminate\Console\Command;
class SendEmails extends Command
{
/**
* 控制台命令的名称和签名。
*
* @var string
*/
protected $signature = 'mail:send {user}';
/**
* 控制台命令描述。
*
* @var string
*/
protected $description = '向用户发送营销邮件';
/**
* 执行控制台命令。
*/
public function handle(DripEmailer $drip): void
{
$drip->send(User::find($this->argument('user')));
}
}
为了更好的代码复用,最好将你的控制台命令保持简洁,并让它们将任务委托给应用程序服务来完成。在上面的示例中,请注意,我们注入了一个服务类来执行发送电子邮件的 “繁重工作”。 |
闭包命令
基于闭包的命令提供了一种替代定义控制台命令为类的方式。就像路由闭包是控制器的替代一样,命令闭包也可以作为命令类的替代。
虽然 routes/console.php
文件并不定义 HTTP 路由,但它定义了进入应用程序的基于控制台的入口点(路由)。在这个文件中,你可以使用 Artisan::command
方法定义所有的基于闭包的控制台命令。command
方法接受两个参数:命令的签名和一个闭包,该闭包接收命令的参数和选项:
Artisan::command('mail:send {user}', function (string $user) {
$this->info("Sending email to: {$user}!");
});
该闭包与底层的命令实例绑定,因此你可以完全访问通常在完整命令类中能够访问的所有助手方法。
可隔离命令
要利用此功能,您的应用程序必须使用 |
有时您可能希望确保命令一次只运行一个实例。为此,您可以在命令类中实现 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
定义输入期望
在编写控制台命令时,通常需要通过参数或选项从用户收集输入。Laravel 使得定义您期望从用户获取的输入非常方便,可以使用命令上的 signature
属性。signature
属性允许您使用一种简洁、类似路由的语法,在一个地方定义命令的名称、参数和选项。
参数
所有用户提供的参数和选项都被括在大括号 {}
中。在下面的示例中,命令定义了一个必需的参数:user
:
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'mail:send {user}';
您还可以将参数设为可选,或者为参数定义默认值:
// 可选参数...
'mail:send {user?}'
// 带有默认值的可选参数...
'mail:send {user=foo}'
选项
选项与参数一样,是另一种用户输入的形式。选项在命令行中提供时以两个连字符(--
)为前缀。选项有两种类型:接收值的选项和不接收值的选项。那些不接收值的选项充当布尔 “开关”。让我们看一个这种类型的选项的示例:
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'mail:send {user} {--queue}';
在这个示例中,--queue
选项可以在调用 Artisan 命令时指定。如果传递了 --queue
选项,则该选项的值为 true
,否则为 false
:
php artisan mail:send 1 --queue
带值的选项
接下来,我们看一下一个需要值的选项。如果用户必须为某个选项指定一个值,则应在选项名后加上 =
符号:
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'mail:send {user} {--queue=}';
在这个示例中,用户可以像这样为选项传递一个值。如果在调用命令时未指定该选项,则其值为 null
:
php artisan mail:send 1 --queue=default
您还可以为选项指定默认值,方法是在选项名后指定默认值。如果用户未传递选项值,将使用默认值:
'mail:send {user} {--queue=default}'
输入数组
如果您希望定义期望多个输入值的参数或选项,可以使用 *
字符。首先,让我们看一下一个指定此类参数的示例:
'mail:send {user*}'
调用此方法时,用户参数可以按顺序传递到命令行。例如,以下命令将把 user
的值设置为包含 1
和 2
的数组:
php artisan mail:send 1 2
此 *
字符可以与可选参数定义结合使用,以允许零个或多个实例的参数:
'mail:send {user?*}'
输入描述
您可以通过使用冒号将参数名称与描述分开,为输入参数和选项分配描述。如果您需要更多空间来定义命令,可以将定义分布在多行上:
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'mail:send
{user : The ID of the user}
{--queue : Whether the job should be queued}';
提示缺失输入
如果您的命令包含必需的参数,当用户未提供时,系统将显示错误信息。或者,您可以通过实现 PromptsForMissingInput
接口来配置命令,当缺少必需参数时自动提示用户:
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Contracts\Console\PromptsForMissingInput;
class SendEmails extends Command implements PromptsForMissingInput
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'mail:send {user}';
// ...
}
如果 Laravel 需要从用户那里获取必需的参数,它将自动询问用户,智能地通过参数名或描述来提出问题。如果您希望自定义用于收集必需参数的问题,可以实现 promptForMissingArgumentsUsing
方法,返回一个以参数名为键的问题数组:
/**
* Prompt for missing input arguments using the returned questions.
*
* @return array<string, string>
*/
protected function promptForMissingArgumentsUsing(): array
{
return [
'user' => 'Which user ID should receive the mail?',
];
}
您还可以通过使用元组的方式提供占位符文本,元组中的第一个值是问题,第二个值是占位符:
return [
'user' => ['Which user ID should receive the mail?', 'E.g. 123'],
];
如果您希望完全控制提示,您可以提供一个闭包,该闭包应提示用户并返回他们的答案:
use App\Models\User;
use function Laravel\Prompts\search;
// ...
return [
'user' => fn () => search(
label: 'Search for a user:',
placeholder: 'E.g. Taylor Otwell',
options: fn ($value) => strlen($value) > 0
? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all()
: []
),
];
详细的 【Laravel Prompts】 文档包含了更多关于可用提示和其使用方式的信息。 |
如果您希望在提示用户选择或输入【选项】时包含提示,您可以在命令的 handle
方法中包含提示。然而,如果您只希望在用户已被自动提示缺少参数时再进行提示,则可以实现 afterPromptingForMissingArguments
方法:
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use function Laravel\Prompts\confirm;
// ...
/**
* Perform actions after the user was prompted for missing arguments.
*/
protected function afterPromptingForMissingArguments(InputInterface $input, OutputInterface $output): void
{
$input->setOption('queue', confirm(
label: 'Would you like to queue the mail?',
default: $this->option('queue')
));
}
命令 I/O
检索输入
在命令执行期间,您可能需要访问命令所接受的参数和选项的值。为此,您可以使用 argument
和 option
方法。如果参数或选项不存在,则会返回 null
:
/**
* 执行控制台命令。
*/
public function handle(): void
{
$userId = $this->argument('user');
}
如果需要以数组的形式检索所有参数,可以调用 arguments
方法:
$arguments = $this->arguments();
选项可以像参数一样轻松地通过 option
方法检索。要以数组形式检索所有选项,可以调用 options
方法:
// 检索特定的选项...
$queueName = $this->option('queue');
// 检索所有选项作为数组...
$options = $this->options();
提示输入
【Laravel Prompts】 是一个 PHP 包,用于在命令行应用程序中添加美观且用户友好的表单,提供类似浏览器的功能,包括占位符文本和验证。 |
除了显示输出,您还可以在执行命令时要求用户提供输入。ask
方法将提示用户输入给定的问题,接受用户的输入,然后将其返回到您的命令中:
/**
* 执行控制台命令。
*/
public function handle(): void
{
$name = $this->ask('What is your name?');
// ...
}
ask
方法还接受一个可选的第二个参数,用于指定如果没有提供用户输入时应返回的默认值:
$name = $this->ask('What is your name?', 'Taylor');
secret
方法与 ask
类似,但用户输入的内容在控制台中不会显示出来。这对于要求敏感信息(如密码)时非常有用:
$password = $this->secret('What is the password?');
询问确认
如果您需要询问用户是否简单地确认 “是或否”,可以使用 confirm
方法。默认情况下,返回值为 false
,但如果用户输入 y
或 yes
,则返回 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 address?', function (string $input) {
// 返回自动补全选项...
});
多项选择题
如果您需要给用户提供一个预定义的选项集来回答问题,可以使用 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
、question
、warn
和 error
方法。每个方法将根据其用途使用适当的 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');
您可以使用 newLine
方法显示空白行:
// 写一个空白行...
$this->newLine();
// 写三行空白...
$this->newLine(3);
表格
table
方法可以轻松地格式化多行/多列的数据。您只需要提供列名和表格数据,Laravel 会自动计算表格的适当宽度和高度:
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 Progress Bar 组件的文档】。 |
注册命令
默认情况下,Laravel 会自动注册 app/Console/Commands
目录中的所有命令。然而,您可以通过在应用程序的 bootstrap/app.php
文件中使用 withCommands
方法来指示 Laravel 扫描其它目录以查找 Artisan 命令:
->withCommands([
__DIR__.'/../app/Domain/Orders/Commands',
])
如果需要,您还可以通过将命令的类名提供给 withCommands
方法来手动注册命令:
use App\Domain\Orders\Commands\SendEmails;
->withCommands([
SendEmails::class,
])
当 Artisan 启动时,应用程序中的所有命令将通过服务容器解析并注册到 Artisan 中。
以编程方式执行命令
有时您可能希望在 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
标志),您应该将 true
或 false
作为选项的值传递:
$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'
]);
// ...
});
使用 onConnection
和 onQueue
方法,您可以指定 Artisan 命令应被调度到的连接或队列:
Artisan::queue('mail:send', [
'user' => 1, '--queue' => 'default'
])->onConnection('redis')->onQueue('commands');
从其它命令调用命令
有时您可能希望在现有的 Artisan 命令中调用其它命令。您可以使用 call
方法来实现。这个 call
方法接受命令名称和一组命令参数/选项:
/**
* 执行控制台命令。
*/
public function handle(): void
{
$this->call('mail:send', [
'user' => 1, '--queue' => 'default'
]);
// ...
}
如果您希望调用另一个控制台命令并抑制其所有输出,您可以使用 callSilently
方法。callSilently
方法的签名与 call
方法相同:
$this->callSilently('mail:send', [
'user' => 1, '--queue' => 'default'
]);
信号处理
如您所知,操作系统允许向正在运行的进程发送信号。例如,SIGTERM 信号是操作系统请求程序终止的方式。如果您希望在 Artisan 控制台命令中监听信号并在信号发生时执行代码,可以使用 trap
方法:
/**
* 执行控制台命令。
*/
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
});