跳到內容

Artisan Console

簡介

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 環境,請執行 tinker Artisan 命令

1php artisan tinker

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

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

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

命令允許清單

Tinker 使用「允許」清單來判斷允許在其 shell 內執行的 Artisan 命令。預設情況下,您可以執行 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 目錄中;但是,只要您的命令可以由 Composer 載入,您就可以自由選擇自己的儲存位置。

產生命令

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

1php artisan make:command SendEmails

命令結構

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

讓我們看看範例命令。請注意,我們可以透過命令的 handle 方法請求我們需要的任何相依性。Laravel 服務容器 將自動注入在此方法簽章中類型提示的所有相依性

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

閉包會繫結至基礎命令實例,因此您可以完全存取您通常可以在完整命令類別上存取的所有輔助方法。

類型提示相依性

除了接收命令的引數和選項之外,命令閉包也可以類型提示您想要從 服務容器 中解析的其他相依性

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 將使用命令的名稱來產生用於在您應用程式的快取中取得原子鎖定的字串金鑰。但是,您可以透過在您的 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()->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}';

在此範例中,可以在呼叫 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}

您也可以透過使用包含問題和預留位置的元組來提供預留位置文字

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::where('name', 'like', "%{$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 方法。每次使用者輸入輸入字元時都會呼叫閉包。閉包應接受包含使用者目前輸入的字串參數,並傳回自動完成選項陣列

1$name = $this->anticipate('What is your address?', function (string $input) {
2 // Return auto-completion options...
3});

多項選擇題

如果您需要在提出問題時為使用者提供一組預先定義的選項,您可以使用 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});

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

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 使用應用程式 bootstrap/app.php 檔案中的 withCommands 方法掃描其他目錄以尋找 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;
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});

傳遞布林值

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

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

佇列 Artisan 命令

使用 Artisan facade 上的 queue 方法,您甚至可以將 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});

Stub 客製化

Artisan 主控台的 make 命令用於建立各種類別,例如控制器、任務、遷移和測試。這些類別是使用「stub」檔案產生的,這些檔案會根據您的輸入填入值。但是,您可能想要對 Artisan 產生的檔案進行小幅變更。若要完成此操作,您可以使用 stub:publish 命令將最常見的 stub 發佈到您的應用程式,以便您可以自訂它們

1php artisan stub:publish

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

事件

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