跳轉至內容

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 事件在命令執行完成後分發。