Skip to content

Artisan 控制台

簡介

Artisan 是 Laravel 隨附的命令列介面。Artisan 作為 artisan 腳本存在於您應用程式的根目錄中,並提供許多實用的命令,可在您建構應用程式時提供協助。若要查看所有可用 Artisan 命令的列表,您可以使用 list 命令:

shell
php artisan list

每個命令還包含一個「說明」畫面,其中顯示並描述了命令可用的引數和選項。若要查看說明畫面,請在命令名稱前加上 help

shell
php artisan help migrate

Laravel Sail

如果您正在使用 Laravel Sail 作為您的本機開發環境,請記得使用 sail 命令列來呼叫 Artisan 命令。Sail 將會在您應用程式的 Docker 容器中執行您的 Artisan 命令:

shell
./vendor/bin/sail artisan list

Tinker (REPL)

Laravel Tinker 是一個功能強大的 REPL,適用於 Laravel 框架,由 PsySH 套件提供支援。

安裝

所有 Laravel 應用程式預設都包含 Tinker。但是,如果您先前已從應用程式中移除 Tinker,則可以使用 Composer 安裝 Tinker:

shell
composer require laravel/tinker

📌 備註

在與您的 Laravel 應用程式互動時,正在尋找熱重載、多行程式碼編輯和自動補齊功能嗎?請查看 Tinkerwell

用法

Tinker 允許您在命令列上與您的整個 Laravel 應用程式互動,包括您的 Eloquent 模型、任務、事件等等。若要進入 Tinker 環境,請執行 tinker Artisan 命令:

shell
php artisan tinker

您可以使用 vendor:publish 命令發佈 Tinker 的設定檔:

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

⚠️ 警告

Dispatchable 類別上的 dispatch 輔助函式和 dispatch 方法依賴垃圾回收來將任務放入佇列中。因此,當使用 Tinker 時,您應該使用 Bus::dispatchQueue::push 來分派任務。

命令允許列表

Tinker 利用「允許」列表來決定哪些 Artisan 命令被允許在其 shell 中執行。預設情況下,您可以執行 clear-compileddownenvinspiremigratemigrate:installupoptimize 命令。如果您想允許更多命令,可以將它們添加到 tinker.php 設定檔中的 commands 陣列中:

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

不應建立別名的類別

通常,Tinker 在您與類別互動時會自動為其建立別名。但是,您可能希望永遠不要為某些類別建立別名。您可以透過在 tinker.php 設定檔的 dont_alias 陣列中列出這些類別來達成此目的:

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

撰寫命令

除了 Artisan 提供的命令之外,您還可以建立自己的自訂命令。命令通常儲存在 app/Console/Commands 目錄中;但是,只要您指示 Laravel 掃描其他目錄以尋找 Artisan 命令,您就可以自由選擇自己的儲存位置。

生成命令

要建立一個新命令,您可以使用 make:command Artisan 命令。這個命令將在 app/Console/Commands 目錄中建立一個新的命令類別。如果您的應用程式中不存在此目錄,請不要擔心 — 它將在您第一次執行 make:command Artisan 命令時建立:

shell
php artisan make:command SendEmails

命令結構

在生成命令後,您應該為類別的 signaturedescription 屬性定義適當的值。這些屬性將用於在 list 畫面中顯示您的命令。signature 屬性也允許您定義命令的輸入預期handle 方法將在執行您的命令時被呼叫。您可以將命令邏輯放在這個方法中。

讓我們來看一個範例命令。請注意,我們可以透過命令的 handle 方法請求所需的任何依賴項。Laravel 服務容器將自動注入所有在該方法簽章中進行型別提示的依賴項:

php
<?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')));
    }
}

📌 備註

為了提高程式碼重用性,建議讓您的主控台命令保持輕量,並將其委託給應用程式服務來完成任務。在上面的範例中,請注意我們注入了一個服務類別來執行傳送電子郵件的「繁重工作」。

結束碼

如果 handle 方法沒有回傳任何值,且命令成功執行,命令將以 0 的結束碼退出,表示成功。然而,handle 方法可以選擇性地回傳一個整數來手動指定命令的結束碼:

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

return 1;

如果您想在命令內的任何方法中「失敗」命令,您可以使用 fail 方法。fail 方法將立即終止命令的執行並回傳 1 的結束碼:

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

閉包命令

基於閉包的命令提供了一種替代方案,可以不將主控台命令定義為類別。就像路由閉包是控制器的一種替代方案一樣,您可以將命令閉包視為命令類別的一種替代方案。

儘管 routes/console.php 檔案沒有定義 HTTP 路由,但它定義了基於主控台的進入點(路由)到您的應用程式中。在此檔案中,您可以使用 Artisan::command 方法定義所有基於閉包的主控台命令。command 方法接受兩個參數:命令簽章以及一個接收命令引數和選項的閉包:

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

該閉包綁定到底層的命令實例,因此您可以完整存取通常在完整命令類別上可以存取的所有輔助方法。

型別提示依賴

除了接收命令的引數和選項之外,命令閉包還可以型別提示您希望從 服務容器中解析出的額外依賴項:

php
use App\Models\User;
use App\Support\DripEmailer;
use Illuminate\Support\Facades\Artisan;

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

閉包命令描述

當定義基於閉包的命令時,您可以使用 purpose 方法為命令添加描述。當您執行 php artisan listphp artisan help 命令時,此描述將被顯示:

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

可隔離命令

⚠️ 警告

若要使用此功能,您的應用程式必須使用 memcachedredisdynamodbdatabasefilearray 快取驅動程式作為應用程式的預設快取驅動程式。此外,所有伺服器都必須與相同的中央快取伺服器通訊。

有時您可能希望確保命令一次只能執行一個實例。為了實現這一點,您可以在命令類別上實現 Illuminate\Contracts\Console\Isolatable 介面:

php
<?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 透過嘗試使用應用程式的預設快取驅動程式取得原子鎖來實現這一點。如果命令的其他實例正在執行,則命令將不會執行;但是,命令仍將以成功的結束狀態碼退出:

shell
php artisan mail:send 1 --isolated

如果您希望指定命令在無法執行時應回傳的結束狀態碼,可以透過 isolated 選項提供所需的狀態碼:

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

鎖定 ID

預設情況下,Laravel 將使用命令的名稱來生成用於在應用程式快取中取得原子鎖的字串鍵。但是,您可以透過在 Artisan 命令類別上定義 isolatableId 方法來自訂此鍵,允許您將命令的引數或選項整合到鍵中:

php
/**
 * Get the isolatable ID for the command.
 */
public function isolatableId(): string
{
    return $this->argument('user');
}

鎖定過期時間

預設情況下,隔離鎖定會在命令完成後過期。或者,如果命令被中斷且無法完成,鎖定將在一小時後過期。但是,您可以透過在命令上定義 isolationLockExpiresAt 方法來調整鎖定過期時間:

php
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

php
/**
 * The name and signature of the console command.
 *
 * @var string
 */
protected $signature = 'mail:send {user}';

您也可以使參數成為選用,或為參數定義預設值:

php
// Optional argument...
'mail:send {user?}'

// Optional argument with default value...
'mail:send {user=foo}'

選項

選項與參數一樣,是使用者輸入的另一種形式。當透過命令列提供時,選項以兩個連字號 (--) 作為前綴。選項有兩種:接收值的選項和不接收值的選項。不接收值的選項充當布林「開關」。讓我們來看一個這種類型選項的範例:

php
/**
 * The name and signature of the console command.
 *
 * @var string
 */
protected $signature = 'mail:send {user} {--queue}';

在此範例中,可以在呼叫 Artisan 命令時指定 --queue 開關。如果傳遞了 --queue 開關,則選項的值將為 true。否則,該值將為 false

shell
php artisan mail:send 1 --queue

帶有值的選項

接下來,讓我們來看一個預期值的選項。如果使用者必須為選項指定一個值,則應在選項名稱後加上 = 符號:

php
/**
 * The name and signature of the console command.
 *
 * @var string
 */
protected $signature = 'mail:send {user} {--queue=}';

在此範例中,使用者可以像這樣傳遞選項的值。如果呼叫命令時未指定選項,則其值將為 null

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

您可以透過在選項名稱後指定預設值來為選項分配預設值。如果使用者未傳遞任何選項值,則將使用預設值:

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

選項快捷方式

在定義選項時分配快捷方式,您可以將其指定在選項名稱之前,並使用 | 字元作為分隔符將快捷方式與完整選項名稱分開:

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

在終端機上呼叫命令時,選項快捷方式應以單個連字號作為前綴,並且在為選項指定值時不應包含 = 字元:

shell
php artisan mail:send 1 -Qdefault

輸入陣列

如果您想定義參數或選項以預期多個輸入值,可以使用 * 字元。首先,讓我們來看一個指定此類參數的範例:

php
'mail:send {user*}'

執行此命令時,user 參數可以按順序傳遞給命令列。例如,以下命令會將 user 的值設定為一個包含 12 的陣列:

shell
php artisan mail:send 1 2

* 字元可以與選用參數定義結合使用,以允許零個或多個參數實例:

php
'mail:send {user?*}'

選項陣列

當定義一個預期多個輸入值的選項時,傳遞給命令的每個選項值都應該以選項名稱作為前綴:

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

此類命令可以透過傳遞多個 --id 參數來呼叫:

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

輸入說明

您可以透過使用冒號將參數名稱與說明分開,為輸入參數和選項分配說明。如果您需要更多空間來定義命令,可以將定義分散到多行:

php
/**
 * 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 介面,將命令配置為在缺少必填參數時自動提示使用者:

php
<?php

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 方法,傳回一個以參數名稱為鍵的問題陣列:

php
/**
 * 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?',
    ];
}

您也可以透過使用包含問題和佔位符的元組來提供佔位符文字:

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

如果您想完全控制提示,您可以提供一個閉包,該閉包應該提示使用者並傳回其答案:

php
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::whereLike('name', "%{$value}%")->pluck('name', 'id')->all()
            : []
    ),
];

📌 備註

完整的 Laravel Prompts 文件包含了有關可用提示及其用法的更多資訊。

如果您希望提示使用者選擇或輸入 options,您可以將提示包含在命令的 handle 方法中。但是,如果您只想在使用者也自動被提示缺少參數時才提示使用者,那麼您可以實作 afterPromptingForMissingArguments 方法:

php
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

擷取輸入

當您的命令執行時,您可能需要存取命令所接受的參數和選項的值。為此,您可以使用 argumentoption 方法。如果參數或選項不存在,將會回傳 null

php
/**
 * Execute the console command.
 */
public function handle(): void
{
    $userId = $this->argument('user');
}

如果您需要將所有參數以 array 形式擷取,請呼叫 arguments 方法:

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

選項也可以像參數一樣,使用 option 方法輕鬆擷取。若要將所有選項以陣列形式擷取,請呼叫 options 方法:

php
// Retrieve a specific option...
$queueName = $this->option('queue');

// Retrieve all options as an array...
$options = $this->options();

提示輸入

📌 備註

Laravel Prompts 是一個 PHP 套件,用於為您的命令列應用程式增加美觀且使用者友善的表單,具有類似瀏覽器的功能,包括提示文字和驗證。

除了顯示輸出之外,您也可以在命令執行期間要求使用者提供輸入。ask 方法會向使用者提示給定的問題,接受他們的輸入,然後將使用者的輸入回傳給您的命令:

php
/**
 * Execute the console command.
 */
public function handle(): void
{
    $name = $this->ask('What is your name?');

    // ...
}

ask 方法也接受一個可選的第二個參數,用於指定在未提供使用者輸入時應回傳的預設值:

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

secret 方法類似於 ask,但在使用者於控制台輸入時,其輸入將不會可見。此方法在要求敏感資訊 (例如密碼) 時非常有用:

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

要求確認

如果您需要向使用者請求簡單的「是或否」確認,您可以使用 confirm 方法。預設情況下,此方法將回傳 false。但是,如果使用者在提示中輸入 yyes,此方法將回傳 true

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

如有必要,您可以透過將 true 作為第二個參數傳遞給 confirm 方法,來指定確認提示應預設回傳 true

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

自動補全

anticipate 方法可用於為可能的選項提供自動補全。無論自動補全提示為何,使用者仍然可以提供任何答案:

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

或者,您可以將閉包作為第二個參數傳遞給 anticipate 方法。每當使用者輸入一個字元時,此閉包都會被呼叫。此閉包應接受一個字串參數,其中包含使用者目前為止的輸入,並回傳一個用於自動補全的選項陣列:

php
use App\Models\Address;

$name = $this->anticipate('What is your address?', function (string $input) {
    return Address::whereLike('name', "{$input}%")
        ->limit(5)
        ->pluck('name')
        ->all();
});

多選問題

如果您需要向使用者提供一組預先定義的選項來回答問題,您可以使用 choice 方法。您可以透過將預設值的陣列索引作為第三個參數傳遞給此方法,來設定在沒有選擇任何選項時應回傳的索引:

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

此外,choice 方法接受可選的第四和第五個參數,用於決定選擇有效回應的最大嘗試次數以及是否允許多重選擇:

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

寫入輸出

若要將輸出傳送至控制台,您可以使用 linenewLineinfocommentquestionwarnalerterror 方法。這些方法會根據其用途使用適當的 ANSI 顏色。例如,讓我們向使用者顯示一些一般資訊。通常,info 方法會在控制台中顯示為綠色文字:

php
/**
 * Execute the console command.
 */
public function handle(): void
{
    // ...

    $this->info('The command was successful!');
}

若要顯示錯誤訊息,請使用 error 方法。錯誤訊息文字通常顯示為紅色:

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

您可以使用 line 方法來顯示純色、無色彩的文字:

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

您可以使用 newLine 方法來顯示空行:

php
// Write a single blank line...
$this->newLine();

// Write three blank lines...
$this->newLine(3);

表格

table 方法可讓您輕鬆地正確格式化多行/多列資料。您只需提供欄位名稱和表格資料,Laravel 將自動為您計算表格的適當寬度和高度:

php
use App\Models\User;

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

進度條

對於長時間執行的任務,顯示進度條以告知使用者任務完成的程度會很有幫助。使用 withProgressBar 方法,Laravel 將顯示進度條並針對給定可迭代值的每次迭代推進其進度:

php
use App\Models\User;

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

有時,您可能需要對進度條的推進方式進行更手動的控制。首先,定義流程將迭代的總步驟數。然後,在處理每個項目後推進進度條:

php
$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 進度條元件文件

註冊命令

預設情況下,Laravel 會自動註冊 app/Console/Commands 目錄中的所有命令。然而,您可以使用應用程式的 bootstrap/app.php 檔案中的 withCommands 方法,指示 Laravel 掃描其他目錄以尋找 Artisan 命令:

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

如有必要,您也可以透過向 withCommands 方法提供命令的類別名稱來手動註冊命令:

php
use App\Domain\Orders\Commands\SendEmails;

->withCommands([
    SendEmails::class,
])

當 Artisan 啟動時,應用程式中的所有命令都將由 服務容器 解析並向 Artisan 註冊。

以程式化方式執行命令

有時您可能希望在 CLI 之外執行 Artisan 命令。例如,您可能希望從路由或控制器中執行 Artisan 命令。您可以使用 Artisan Facade 上的 call 方法來實現此目的。call 方法接受命令的簽章名稱或類別名稱作為其第一個參數,並接受一個命令參數陣列作為第二個參數。此方法將返回結束代碼:

php
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Route;

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

    // ...
});

或者,您可以將完整的 Artisan 命令以字串形式傳遞給 call 方法:

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

傳遞陣列值

如果您的命令定義了一個接受陣列的選項,您可以將一個陣列值傳遞給該選項:

php
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Route;

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

傳遞布林值

如果您需要為不接受字串值的選項指定值,例如 migrate:refresh 命令上的 --force 旗標,您應該將 truefalse 作為該選項的值傳遞:

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

佇列 Artisan 命令

使用 Artisan Facade 上的 queue 方法,您甚至可以將 Artisan 命令佇列,使其在後台由您的 佇列工作者 處理。在使用此方法之前,請確保您已配置好佇列並正在運行佇列監聽器:

php
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Route;

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

    // ...
});

使用 onConnectiononQueue 方法,您可以指定應分派 Artisan 命令的連線或佇列:

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

從其他命令呼叫命令

有時您可能希望從現有的 Artisan 命令中呼叫其他命令。您可以通過使用 call 方法來實現。此 call 方法接受命令名稱和一個命令參數/選項陣列:

php
/**
 * Execute the console command.
 */
public function handle(): void
{
    $this->call('mail:send', [
        'user' => 1, '--queue' => 'default'
    ]);

    // ...
}

如果您想呼叫另一個控制台命令並抑制其所有輸出,您可以使用 callSilently 方法。callSilently 方法具有與 call 方法相同的簽章:

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

訊號處理

如您所知,作業系統允許將訊號發送給執行中的程序。例如,SIGTERM 訊號是作業系統要求程序終止的方式。如果您希望在 Artisan 控制台命令中監聽訊號並在它們發生時執行程式碼,您可以使用 trap 方法:

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

    while ($this->shouldKeepRunning) {
        // ...
    }
}

若要一次監聽多個訊號,您可以向 trap 方法提供一個訊號陣列:

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

    dump($signal); // SIGTERM / SIGQUIT
});

Stub 客製化

Artisan 控制台的 make 命令用於創建各種類別,例如控制器、Job、遷移和測試。這些類別是使用 "stub" 檔案產生的,這些檔案會根據您的輸入填充值。但是,您可能希望對 Artisan 產生的檔案進行一些微小的更改。為了實現此目的,您可以使用 stub:publish 命令將最常見的 stub 檔案發佈到您的應用程式中,以便您可以自訂它們:

shell
php artisan stub:publish

發佈的 stub 檔案將位於您的應用程式根目錄中的 stubs 目錄。您對這些 stub 檔案所做的任何更改都將在您使用 Artisan 的 make 命令產生其對應類別時反映出來。

事件

Artisan 在執行命令時會分派三個事件:Illuminate\Console\Events\ArtisanStartingIlluminate\Console\Events\CommandStartingIlluminate\Console\Events\CommandFinishedArtisanStarting 事件在 Artisan 開始執行時立即分派。接著,CommandStarting 事件在命令執行前立即分派。最後,CommandFinished 事件在命令執行完成後分派。