Artisan 主控台
簡介
Artisan 是 Laravel 內所包含的指令列界面。Artisan 是放在專案根目錄的 artisan
工序指令,提供多種實用指令來幫你撰寫你的專案。若要檢視所有可用的 Artisan 指令,可以使用 list
指令:
1php artisan list
1php artisan list
每個指令也包含了一個「help」畫面,用於顯示指令的說明以及可用的引數與選項。若要檢視輔助說明畫面,請在指令名稱的前面加上 help
:
1php artisan help migrate
1php artisan help migrate
Laravel Sail
若使用 Laravel Sail 作為本機開發環境,請記得使用 sail
指令列來叫用 Artisan 指令。Sail 會在專案的 Docker 容器內執行 Artisan 指令。
1./vendor/bin/sail artisan list
1./vendor/bin/sail artisan list
Tinker (REPL)
Laravel Tinker 是用於 Laravel 框架的強大 REPL,由 PsySH 套件提供。
安裝
所有的 Laravel 專案預設都包含了 Tinker。但若先前曾自專案內移除 Tinker,則可使用 Composer 來安裝:
1composer require laravel/tinker
1composer require laravel/tinker
Looking for hot reloading, multiline code editing, and autocompletion when interacting with your Laravel application? Check out Tinkerwell!
使用
Tinker 可讓你在指令列內與完整的 Laravel 專案進行互動,包含 Eloquent Model、任務、事件…等。要進入 Tinker 環境,請執行 tinker
Artisan 指令:
1php artisan tinker
1php artisan tinker
可以通過 vendor:publish
指令來安裝 Tinker 的設定檔:
1php artisan vendor:publish --provider="Laravel\Tinker\TinkerServiceProvider"
1php artisan vendor:publish --provider="Laravel\Tinker\TinkerServiceProvider"
dispatch
輔助函式與 Dispatchable
類別上的 dispatch
方法需要仰賴垃圾回收機制來將任務放進佇列中。因此,在使用 Tinker 時,應使用 Bus::dispatch
或 Queue::push
來分派任務。
指令允許列表
Tinker 使用一個「allow」清單來判斷哪些 Artisan 指令可在其 Shell 內執行。預設情況下,可以執行 clear-compiled
, down
, env
, inspire
, migrate
, optimize
以及 up
指令。若想允許更多指令,可以將要允許的指令加在 tinker.php
設定檔中的 commands
陣列內:
1'commands' => [2 // App\Console\Commands\ExampleCommand::class,3],
1'commands' => [2 // App\Console\Commands\ExampleCommand::class,3],
不應以別名使用的類別
一般來說,Tinker 會在使用過程中自動為類別加上別名。但有些類別可能不希望被設定別名。可以通過在 tinker.php
設定檔中的 dont_alias
陣列中列出這些不想被自動別名的類別來達成:
1'dont_alias' => [2 App\Models\User::class,3],
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
1php artisan make:command SendEmails
指令結構
產生指令後,應為類別的 signature
與 description
屬性定義適當的值。當在 list
畫面內顯示該指令時,就會用到這些屬性。signature
屬性可以用來定義 指令預期的輸入。handle
方法會在執行該指令時呼叫。可以將指令的邏輯放在該方法內。
來看看一個範例指令。請注意,我們可以通過指令的 handle
方法來要求任意的相依性。Laravel 的 Service Container 會自動插入所有在方法簽章內有型別提示的相依性。
1<?php23namespace App\Console\Commands;45use App\Models\User;6use App\Support\DripEmailer;7use Illuminate\Console\Command;89class SendEmails extends Command10{11 /**12 * The name and signature of the console command.13 *14 * @var string15 */16 protected $signature = 'mail:send {user}';1718 /**19 * The console command description.20 *21 * @var string22 */23 protected $description = 'Send a marketing email to a user';2425 /**26 * Execute the console command.27 */28 public function handle(DripEmailer $drip): void29 {30 $drip->send(User::find($this->argument('user')));31 }32}
1<?php23namespace App\Console\Commands;45use App\Models\User;6use App\Support\DripEmailer;7use Illuminate\Console\Command;89class SendEmails extends Command10{11 /**12 * The name and signature of the console command.13 *14 * @var string15 */16 protected $signature = 'mail:send {user}';1718 /**19 * The console command description.20 *21 * @var string22 */23 protected $description = 'Send a marketing email to a user';2425 /**26 * Execute the console command.27 */28 public function handle(DripEmailer $drip): void29 {30 $drip->send(User::find($this->argument('user')));31 }32}
為了提升程式碼重複使用率,最好保持主控台指令精簡,並將指令的任務委託給應用程式服務來完成。在上方的例子中,可以注意到我們插入了一個服務類別來處理寄送 E-Mail 的這個「重責大任」。
閉包指令
基於閉包的指令提供了以類別定義主控台指令外的另一個選擇。就如同使用閉包來定義路由可用來代替控制器一樣,可以將指令閉包想象成是指令類別的代替。在 app/Console/Kernel.php
檔中的 commands
方法內,Laravel 載入了 routes/console.php
檔:
1/**2 * Register the closure based commands for the application.3 */4protected function commands(): void5{6 require base_path('routes/console.php');7}
1/**2 * Register the closure based commands for the application.3 */4protected function commands(): void5{6 require base_path('routes/console.php');7}
這個檔案並沒有定義 HTTP 路由,而是定義從主控台「路由」進入專案的進入點。在該檔案內,可以通過 Artisan::command
方法來定義基於閉包的主控台指令。command
方法接受 2 個引數:指令簽章,以及一個用來接收指令引數與選項的閉包:
1Artisan::command('mail:send {user}', function (string $user) {2 $this->info("Sending email to: {$user}!");3});
1Artisan::command('mail:send {user}', function (string $user) {2 $this->info("Sending email to: {$user}!");3});
這裡的閉包有綁定到該指令的基礎類別執行個體,因此可以像在完整的指令類別內一樣存取所有的輔助函式。
對相依關係進行型別提示
除了接收指令的引數與選項外,指令閉包也可以通過型別提示來向 Service Container 解析額外的相依關係。
1use App\Models\User;2use App\Support\DripEmailer;34Artisan::command('mail:send {user}', function (DripEmailer $drip, string $user) {5 $drip->send(User::find($user));6});
1use App\Models\User;2use App\Support\DripEmailer;34Artisan::command('mail:send {user}', function (DripEmailer $drip, string $user) {5 $drip->send(User::find($user));6});
閉包指令描述
在定義基於閉包的指令時,可以使用 purpose
方法來為該指令加上描述。這段描述會在執行 php artisan list
或 php artisan help
指令時顯示:
1Artisan::command('mail:send {user}', function (string $user) {2 // ...3})->purpose('Send a marketing email to a user');
1Artisan::command('mail:send {user}', function (string $user) {2 // ...3})->purpose('Send a marketing email to a user');
可隔離的指令
若要使用此功能,則應用程式必須要使用 memcached
, redis
, dynamodb
, database
, file
或 array
作為應用程式的預設快取 Driver。另外,所有的伺服器也都必須要連線至相同的中央快取伺服器。
有時候,我們可能需要確保某個指令在同一時間只有一個實體在執行。為此,可以在指令類別上實作 Illuminate\Contracts\Console\Isolatable
Interface:
1<?php23namespace App\Console\Commands;45use Illuminate\Console\Command;6use Illuminate\Contracts\Console\Isolatable;78class SendEmails extends Command implements Isolatable9{10 // ...11}
1<?php23namespace App\Console\Commands;45use Illuminate\Console\Command;6use Illuminate\Contracts\Console\Isolatable;78class SendEmails extends Command implements Isolatable9{10 // ...11}
將指令標記為 `Isolatable` 後,Laravel 會自動為該指令加上一個 --isolated
選項。使用 --isolated
選項呼叫該指令時,Laravel 會確保沒有其他該指令的實體正在執行。Laravel 通過在預設快取 Driver 上取得 Atomic Lock 來確保只有一個實體在執行。若該指令有其他實體在執行,就不會執行該指令。不過,該指令依然會以成功的終止狀態碼結束:
1php artisan mail:send 1 --isolated
1php artisan mail:send 1 --isolated
若想指定該指令無法執行時回傳的終止狀態碼,可使用 isolated
選項來設定:
1php artisan mail:send 1 --isolated=12
1php artisan mail:send 1 --isolated=12
Lock ID
預設情況下,Laravel 會使用該指令的名稱來產生一組字串索引鍵,以用在專案快取中取得 Atomic Lock。不過,只要在 Artisan Command 類別上定義 isolatebleId
方法,就可以自定這個索引鍵,讓你能將該指令的引數或選項整合進索引鍵中:
1/**2 * Get the isolatable ID for the command.3 */4public function isolatableId(): string5{6 return $this->argument('user');7}
1/**2 * Get the isolatable ID for the command.3 */4public function isolatableId(): string5{6 return $this->argument('user');7}
Lock 的逾期時間
預設情況下,指令完成執行後,獨立指令的 Lock 就會逾時。而如果指令在執行時遭到中斷而無法完成,該 Lock 會在一小時後逾時。不過,你可以在指令中定義一個 isolationLockExpiresAt
方法來調整逾時時間:
1use DateTimeInterface;2use DateInterval;34/**5 * Determine when an isolation lock expires for the command.6 */7public function isolationLockExpiresAt(): DateTimeInterface|DateInterval8{9 return now()->addMinutes(5);10}
1use DateTimeInterface;2use DateInterval;34/**5 * Determine when an isolation lock expires for the command.6 */7public function isolationLockExpiresAt(): DateTimeInterface|DateInterval8{9 return now()->addMinutes(5);10}
定義預期的輸入
在撰寫主控台指令時,常常會通過引數或選項來向使用者取得輸入。Laravel 通過指令的 signature
屬性來定義預期從使用者那取得的輸入,讓這個過程變得非常簡單。通過 signature
屬性,就能通過類似路由的格式來一次定義名稱、引數,以及選項。非常簡潔有力。
引數
所有由使用者提供的引數與選項都以大括號來包裝。在下列範例中的指令定義了一個必要的引數:user
:
1/**2 * The name and signature of the console command.3 *4 * @var string5 */6protected $signature = 'mail:send {user}';
1/**2 * The name and signature of the console command.3 *4 * @var string5 */6protected $signature = 'mail:send {user}';
也可以將引數設為可選,或是定義引數的預設值:
1// Optional argument...2'mail:send {user?}'34// Optional argument with default value...5'mail:send {user=foo}'
1// Optional argument...2'mail:send {user?}'34// Optional argument with default value...5'mail:send {user=foo}'
選項
選項就像引數一樣,是另一種形式的使用者輸入。選項在從指令列提供時,會加上兩個減號 (--
) 作為前綴。有兩種類型的選項:一種可接收值,一種沒有接收值。沒有接收值的選項是一種布林「開關」功能。來看看一個使用這種類型選項的例子:
1/**2 * The name and signature of the console command.3 *4 * @var string5 */6protected $signature = 'mail:send {user} {--queue}';
1/**2 * The name and signature of the console command.3 *4 * @var string5 */6protected $signature = 'mail:send {user} {--queue}';
在這個例子中,呼叫該 Artisan 指令時可以指定 --queue
開關。若有傳入 --queue
開關,則該選項的值會是 true
。否則,該值為 false
:
1php artisan mail:send 1 --queue
1php artisan mail:send 1 --queue
帶值的選項
接下來,來看看有值的選項。若使用者必須為選項指定一個值,則應在選項名稱後方加上 =
符號:
1/**2 * The name and signature of the console command.3 *4 * @var string5 */6protected $signature = 'mail:send {user} {--queue=}';
1/**2 * The name and signature of the console command.3 *4 * @var string5 */6protected $signature = 'mail:send {user} {--queue=}';
在這個例子中,使用者可以傳入像這樣給選項帶入一個值。若在叫用該指令時未指定該選項,則其值為 null
:
1php artisan mail:send 1 --queue=default
1php artisan mail:send 1 --queue=default
可以通過在選項名稱後方加上預設值來為選項指派一個預設值。若使用者未傳入選項值,將會使用預設值:
1'mail:send {user} {--queue=default}'
1'mail:send {user} {--queue=default}'
選項捷徑
若要在定義選項時指定捷徑,可以在選項名稱前加上其捷徑名稱,並使用 |
字元來區分捷徑名稱與完整的選項名稱:
1'mail:send {user} {--Q|queue}'
1'mail:send {user} {--Q|queue}'
在終端機中呼叫指令時,選項捷徑前面應該只有一個減號,且在指定選項值時不應加上 =
字元:
1php artisan mail:send 1 -Qdefault
1php artisan mail:send 1 -Qdefault
輸入陣列
若想要定義預期有多個輸入值的引數或選項,則可以使用 *
字元。首先,來看看這樣設定引數的例子:
1'mail:send {user*}'
1'mail:send {user*}'
呼叫這個方法的時候,user
引數在指令列中可以按照順序傳入。舉例來說,下列指令會將 user
的值設為一個內容為 1
與 2
的陣列:
1php artisan mail:send 1 2
1php artisan mail:send 1 2
*
字元可以與可選引數組合使用來定義,這樣一來可允許有 0 個或多個引數的實體:
1'mail:send {user?*}'
1'mail:send {user?*}'
選項陣列
定義預期有多個輸入值的選項時,每個傳入指令的選項值都應以選項名稱作為前綴:
1'mail:send {--id=*}'
1'mail:send {--id=*}'
可以通過傳入多個 -id
引數來叫用這樣的指令:
1php artisan mail:send --id=1 --id=2
1php artisan mail:send --id=1 --id=2
輸入描述
可以通過以冒號 (:
) 區分引數名與描述來為輸入引數或選項指定描述。若需要更多空間來定義指令的話,可以將定義拆分為多行:
1/**2 * The name and signature of the console command.3 *4 * @var string5 */6protected $signature = 'mail:send7 {user : The ID of the user}8 {--queue : Whether the job should be queued}';
1/**2 * The name and signature of the console command.3 *4 * @var string5 */6protected $signature = 'mail:send7 {user : The ID of the user}8 {--queue : Whether the job should be queued}';
Prompting for Missing Input
當指令有包含必填的引數時,若使用者未提供這些引數,則會產生錯誤訊息。除了產生錯誤訊息外,只要實作 PromptsForMissingInput
Interface,就可以讓 Command 在使用者未提供必填引數時自動提示使用者輸入。
1<?php23namespace App\Console\Commands;45use Illuminate\Console\Command;6use Illuminate\Contracts\Console\PromptsForMissingInput;78class SendEmails extends Command implements PromptsForMissingInput9{10 /**11 * The name and signature of the console command.12 *13 * @var string14 */15 protected $signature = 'mail:send {user}';1617 // ...18}
1<?php23namespace App\Console\Commands;45use Illuminate\Console\Command;6use Illuminate\Contracts\Console\PromptsForMissingInput;78class SendEmails extends Command implements PromptsForMissingInput9{10 /**11 * The name and signature of the console command.12 *13 * @var string14 */15 protected $signature = 'mail:send {user}';1617 // ...18}
當 Laravel 需要從使用者取得必填引數時,Laravel 會自動智慧地使用引數名稱與說明來產生提問,並向使用者要求輸入。若想自定必填引數的提問,可實作 promptForMissingArgumentsUsing
方法,並回傳一組以引數名稱為索引鍵的問題陣列:
1/**2 * Prompt for missing input arguments using the returned questions.3 *4 * @return array5 */6protected function promptForMissingArgumentsUsing()7{8 return [9 'user' => 'Which user ID should receive the mail?',10 ];11}
1/**2 * Prompt for missing input arguments using the returned questions.3 *4 * @return array5 */6protected function promptForMissingArgumentsUsing()7{8 return [9 'user' => 'Which user ID should receive the mail?',10 ];11}
也可以使用包含提問與預留位置 (Placeholder) 的 Tuple 來提供預留位置文字:
1return [2 'user' => ['Which user ID should receive the mail?', 'E.g. 123'],3];
1return [2 'user' => ['Which user ID should receive the mail?', 'E.g. 123'],3];
若想完全控制提示,可以提供一個 Closure 以用於向使用者提問並回傳使用者提供的答案:
1use App\Models\User;2use function Laravel\Prompts\search;34// ...56return [7 'user' => fn () => search(8 label: 'Search for a user:',9 placeholder: 'E.g. Taylor Otwell',10 options: fn ($value) => strlen($value) > 011 ? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all()12 : []13 ),14];
1use App\Models\User;2use function Laravel\Prompts\search;34// ...56return [7 'user' => fn () => search(8 label: 'Search for a user:',9 placeholder: 'E.g. Taylor Otwell',10 options: fn ($value) => strlen($value) > 011 ? User::where('name', 'like', "%{$value}%")->pluck('name', 'id')->all()12 : []13 ),14];
完整的 Laravel Prompts 說明文件中包含了有關可用的提示及其使用方法的更多資訊。
若想提示使用者選擇或輸入選項,則可在 Command 的 handle
方法中包含提示。不過,若只想在使用者同時被提示未提供的引數時提示使用者,則可實作 afterPromptingForMissingArguments
方法:
1use Symfony\Component\Console\Input\InputInterface;2use Symfony\Component\Console\Output\OutputInterface;3use function Laravel\Prompts\confirm;45// ...67/**8 * Perform actions after the user was prompted for missing arguments.9 *10 * @param \Symfony\Component\Console\Input\InputInterface $input11 * @param \Symfony\Component\Console\Output\OutputInterface $output12 * @return void13 */14protected function afterPromptingForMissingArguments(InputInterface $input, OutputInterface $output)15{16 $input->setOption('queue', confirm(17 label: 'Would you like to queue the mail?',18 default: $this->option('queue')19 ));20}
1use Symfony\Component\Console\Input\InputInterface;2use Symfony\Component\Console\Output\OutputInterface;3use function Laravel\Prompts\confirm;45// ...67/**8 * Perform actions after the user was prompted for missing arguments.9 *10 * @param \Symfony\Component\Console\Input\InputInterface $input11 * @param \Symfony\Component\Console\Output\OutputInterface $output12 * @return void13 */14protected function afterPromptingForMissingArguments(InputInterface $input, OutputInterface $output)15{16 $input->setOption('queue', confirm(17 label: 'Would you like to queue the mail?',18 default: $this->option('queue')19 ));20}
指令 I/O
截取輸入
指令執行時,我們通常需要存取這些指令所接收的引數與選項值。要截取這些值,可以使用 argument
與 option
方法。若引數或選項不存在,則會回傳 null
:
1/**2 * Execute the console command.3 */4public function handle(): void5{6 $userId = $this->argument('user');7}
1/**2 * Execute the console command.3 */4public function handle(): void5{6 $userId = $this->argument('user');7}
若要將所有引數截取為陣列,則可呼叫 arguments
方法:
1$arguments = $this->arguments();
1$arguments = $this->arguments();
我們也可像截取引數一樣使用 option
方法來輕鬆地截取選項。若要將所有選項截取為陣列,請呼叫 options
方法:
1// Retrieve a specific option...2$queueName = $this->option('queue');34// Retrieve all options as an array...5$options = $this->options();
1// Retrieve a specific option...2$queueName = $this->option('queue');34// Retrieve all options as an array...5$options = $this->options();
Prompting for Input
Laravel Prompts 是一個 PHP 套件,可用來在 CLI 應用程式中新增好看且對使用者友善的表單,並具有類似瀏覽器中的功能,如預留位置文字與表單驗證。
除了顯示輸出外,也可以在執行指令的過程中詢問使用者來提供輸入。ask
方法會提示使用者給定的問題,並接受使用者輸入,然後將使用者的輸入回傳至指令:
1/**2 * Execute the console command.3 */4public function handle(): void5{6 $name = $this->ask('What is your name?');78 // ...9}
1/**2 * Execute the console command.3 */4public function handle(): void5{6 $name = $this->ask('What is your name?');78 // ...9}
The ask
method also accepts an optional second argument which specifies the default value that should be returned if no user input is provided:
1$name = $this->ask('What is your name?', 'Taylor');
1$name = $this->ask('What is your name?', 'Taylor');
secret
方法與 ask
類似,但使用者在指令列輸入的過程中將看不到他們自己的輸入值。這個方法適用於像使用者詢問如密碼等機密資訊的時候:
1$password = $this->secret('What is the password?');
1$password = $this->secret('What is the password?');
Asking for Confirmation
若需要使用者回答簡單的「yes / no」的確認問題,可以使用 confirm
方法。預設情況下,這個方法會回傳 false
,但若使用者在提示時輸入 y
或 yes
,則該方法會回傳 true
。
1if ($this->confirm('Do you wish to continue?')) {2 // ...3}
1if ($this->confirm('Do you wish to continue?')) {2 // ...3}
若有必要,也可以通過將 true
傳入為 confirm
方法的第二個引數來指定讓確認提示預設回傳 true
:
1if ($this->confirm('Do you wish to continue?', true)) {2 // ...3}
1if ($this->confirm('Do you wish to continue?', true)) {2 // ...3}
自動補全
anticipate
方法可以用來為可能的選項提供自動補全。不論自動補全提示了什麼,使用者一樣可以提供任意回答:
1$name = $this->anticipate('What is your name?', ['Taylor', 'Dayle']);
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});
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 $defaultIndex5);
1$name = $this->choice(2 'What is your name?',3 ['Taylor', 'Dayle'],4 $defaultIndex5);
另外,choice
方法也接受第 4 個與第 5 個引數,這兩個引數分別是用來判斷選擇有效回答的最大嘗試次數,以及是否允許多重選擇:
1$name = $this->choice(2 'What is your name?',3 ['Taylor', 'Dayle'],4 $defaultIndex,5 $maxAttempts = null,6 $allowMultipleSelections = false7);
1$name = $this->choice(2 'What is your name?',3 ['Taylor', 'Dayle'],4 $defaultIndex,5 $maxAttempts = null,6 $allowMultipleSelections = false7);
撰寫輸出
若要將輸出傳送至主控台,可以使用 line
, info
, comment
, question
, warn
與 error
方法。這幾個方法會依不同目的來使用適當的 ANSI 色彩。舉例來說,我們來顯示一些一般的資訊給使用者看。通常來說,info
方法會在主控台上顯示出綠色的文字:
1/**2 * Execute the console command.3 */4public function handle(): void5{6 // ...78 $this->info('The command was successful!');9}
1/**2 * Execute the console command.3 */4public function handle(): void5{6 // ...78 $this->info('The command was successful!');9}
若要顯示錯誤訊息,可以使用 error
方法。錯誤訊息文字通常會以紅色顯示:
1$this->error('Something went wrong!');
1$this->error('Something went wrong!');
也可以使用 line
方法來顯示未標示色彩的純文字:
1$this->line('Display this on the screen');
1$this->line('Display this on the screen');
可以使用 newLine
方法來顯示空行:
1// Write a single blank line...2$this->newLine();34// Write three blank lines...5$this->newLine(3);
1// Write a single blank line...2$this->newLine();34// Write three blank lines...5$this->newLine(3);
表格
The table
method makes it easy to correctly format multiple rows / columns of data. All you need to do is provide the column names and the data for the table and Laravel will
automatically calculate the appropriate width and height of the table for you:
1use App\Models\User;23$this->table(4 ['Name', 'Email'],5 User::all(['name', 'email'])->toArray()6);
1use App\Models\User;23$this->table(4 ['Name', 'Email'],5 User::all(['name', 'email'])->toArray()6);
進度列
當有需要長時間執行的任務時,最好顯示一個能告訴使用者目前任務完成度的進度列。使用 withProgressBar
方法,Laravel 就會顯示出一個進度列,並在每次迭代過指定的迭代值時增加進度列的進度:
1use App\Models\User;23$users = $this->withProgressBar(User::all(), function (User $user) {4 $this->performTask($user);5});
1use App\Models\User;23$users = $this->withProgressBar(User::all(), function (User $user) {4 $this->performTask($user);5});
有時候,我們可能需要手動控制進度列何時需要增加。首先,我們先定義整個過程所需要迭代的次數。接著,在每個項目處理完後增加進度:
1$users = App\Models\User::all();23$bar = $this->output->createProgressBar(count($users));45$bar->start();67foreach ($users as $user) {8 $this->performTask($user);910 $bar->advance();11}1213$bar->finish();
1$users = App\Models\User::all();23$bar = $this->output->createProgressBar(count($users));45$bar->start();67foreach ($users as $user) {8 $this->performTask($user);910 $bar->advance();11}1213$bar->finish();
有關更進階的選項,請參考 Symfony Progress Bar 元件說明文件。
註冊指令
所有主控台指令都在 App\Console\Kernel
類別內自動註冊。該類別為專案的「主控台核心」。在該類別的 commands
方法內,可以看到一個核心 load
方法的呼叫。load
方法會掃描 app/Console/Commands
目錄並自動向 Artisan 註冊其中的各個指令。你也可以在 load
方法中加上額外的呼叫來掃描其他目錄中的 Artisan 指令:
1/**2 * Register the commands for the application.3 */4protected function commands(): void5{6 $this->load(__DIR__.'/Commands');7 $this->load(__DIR__.'/../Domain/Orders/Commands');89 // ...10}
1/**2 * Register the commands for the application.3 */4protected function commands(): void5{6 $this->load(__DIR__.'/Commands');7 $this->load(__DIR__.'/../Domain/Orders/Commands');89 // ...10}
若有需要的話,也可以通過將指令的類別名稱加至 App\Console\Kernel
類別的 $commands
屬性來手動註冊指令。若該屬性不存在,你可以手動建立。當 Artisan 啟動時,列在該屬性上的所有指令都會由 Service Container 進行解析,並向 Artisan 註冊:
1protected $commands = [2 Commands\SendEmails::class3];
1protected $commands = [2 Commands\SendEmails::class3];
通過程式碼執行指令
有時候可能需要在 CLI 以外的地方執行 Artisan 指令。舉例來說,你可能會想在路由或控制器內執行 Artisan 指令。可以使用 Artisan
Facade 的 call
方法來完成這一目標。可以傳入指令的簽章名稱或類別名稱給 call
方法的第一個引數,而指令的參數則可以陣列傳為第二個引數。指令的結束代碼(Exit Code)會被回傳:
1use Illuminate\Support\Facades\Artisan;23Route::post('/user/{user}/mail', function (string $user) {4 $exitCode = Artisan::call('mail:send', [5 'user' => $user, '--queue' => 'default'6 ]);78 // ...9});
1use Illuminate\Support\Facades\Artisan;23Route::post('/user/{user}/mail', function (string $user) {4 $exitCode = Artisan::call('mail:send', [5 'user' => $user, '--queue' => 'default'6 ]);78 // ...9});
或者,也可以將整個 Artisan 指令作為字串傳給 call
方法:
1Artisan::call('mail:send 1 --queue=default');
1Artisan::call('mail:send 1 --queue=default');
傳入陣列值
若指令有定義接受陣列的選項,則可將陣列傳給該選項:
1use Illuminate\Support\Facades\Artisan;23Route::post('/mail', function () {4 $exitCode = Artisan::call('mail:send', [5 '--id' => [5, 13]6 ]);7});
1use Illuminate\Support\Facades\Artisan;23Route::post('/mail', function () {4 $exitCode = Artisan::call('mail:send', [5 '--id' => [5, 13]6 ]);7});
傳入布林值
若有需要為不接受字串值的選項指定值,如 migrate:refresh
指令的 --force
旗標,則可以為該選項傳入 true
或 false
:
1$exitCode = Artisan::call('migrate:refresh', [2 '--force' => true,3]);
1$exitCode = Artisan::call('migrate:refresh', [2 '--force' => true,3]);
將 Artisan 指令放入佇列
只需要使用 Artisan
Facade 的 queue
方法,就可以將 Artisan 指令放入佇列執行,這樣這個指令就會在 佇列背景工作角色 內背景執行。在使用該方法前,請先確認是否已設定好佇列,且有執行佇列監聽程式:
1use Illuminate\Support\Facades\Artisan;23Route::post('/user/{user}/mail', function (string $user) {4 Artisan::queue('mail:send', [5 'user' => $user, '--queue' => 'default'6 ]);78 // ...9});
1use Illuminate\Support\Facades\Artisan;23Route::post('/user/{user}/mail', function (string $user) {4 Artisan::queue('mail:send', [5 'user' => $user, '--queue' => 'default'6 ]);78 // ...9});
可以使用 onConnection
與 onQueue
方法來指定 Artisan 指令應分派到哪個連線或佇列上:
1Artisan::queue('mail:send', [2 'user' => 1, '--queue' => 'default'3])->onConnection('redis')->onQueue('commands');
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(): void5{6 $this->call('mail:send', [7 'user' => 1, '--queue' => 'default'8 ]);910 // ...11}
1/**2 * Execute the console command.3 */4public function handle(): void5{6 $this->call('mail:send', [7 'user' => 1, '--queue' => 'default'8 ]);910 // ...11}
若有需要呼叫另一個主控台指令並忽略其所有輸出,則可使用 callSilently
方法。callSilently
方法的簽章與 call
方法相同:
1$this->callSilently('mail:send', [2 'user' => 1, '--queue' => 'default'3]);
1$this->callSilently('mail:send', [2 'user' => 1, '--queue' => 'default'3]);
處理訊號
讀者可能已經知道,在作業系統中,我們可以傳送訊號 (Signal) 給正在執行的處理程序。舉例來說,作業系統會使用 SIGTERM
訊號來要求某個程式停止執行。若想在 Artisan 主控台指令上監聽這些訊號,可使用 trap
方法:
1/**2 * Execute the console command.3 */4public function handle(): void5{6 $this->trap(SIGTERM, fn () => $this->shouldKeepRunning = false);78 while ($this->shouldKeepRunning) {9 // ...10 }11}
1/**2 * Execute the console command.3 */4public function handle(): void5{6 $this->trap(SIGTERM, fn () => $this->shouldKeepRunning = false);78 while ($this->shouldKeepRunning) {9 // ...10 }11}
若要同時監聽多個訊號,可提供一組訊號的陣列給 trap
方法:
1$this->trap([SIGTERM, SIGQUIT], function (int $signal) {2 $this->shouldKeepRunning = false;34 dump($signal); // SIGTERM / SIGQUIT5});
1$this->trap([SIGTERM, SIGQUIT], function (int $signal) {2 $this->shouldKeepRunning = false;34 dump($signal); // SIGTERM / SIGQUIT5});
自訂 Stub
Artisan 主控台的 make
指令可以用來建立各種類別,如控制器、任務、資料庫遷移,以及測試。這些類別都是使用「Stub (虛設常式)」來產生的,Stub 會依據給定的輸入來填入不同的值。不過,你可能會想對這些 Artisan 產生的檔案做一些微調。要修改這些 Stub,可以通過 stub:publish
指令來將這些最常見的 Stub 安裝到專案中,如此一來就能自訂這些 Stub:
1php artisan stub:publish
1php artisan stub:publish
安裝的 Stub 會被放在專案根目錄的 stubs
目錄中。對這些 Stub 做出的任何改動都會反應到使用 Artisan 的 make
指令所產生的對應類別上。
事件
Artisan 會在執行指令的時候分派三個事件: Illuminate\Console\Events\ArtisanStarting
, Illuminate\Console\Events\CommandStarting
與 Illuminate\Console\Events\CommandFinished
。ArtisanStarting
事件會在 Artisan 開始執行後馬上被分派。接著,CommandStarting
事件會在指令開始執行前的瞬間被分派。最後,CommandFinished
事件會在指令完成執行後被分派。