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
想找個能與你的 Laravel 應用程式互動的圖形化 UI 嗎?試試 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// 可選引數...2'mail:send {user?}'34// 有預設值的可選引數...5'mail:send {user=foo}'
1// 可選引數...2'mail:send {user?}'34// 有預設值的可選引數...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}';
為未提供的輸入進行提示
當指令有包含必填的引數時,若使用者未提供這些引數,則會產生錯誤訊息。除了產生錯誤訊息外,只要實作 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')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')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// 取得特定選項...2$queueName = $this->option('queue');34// 將所有選項作為陣列取得...5$options = $this->options();
1// 取得特定選項...2$queueName = $this->option('queue');34// 將所有選項作為陣列取得...5$options = $this->options();
為輸入進行提示
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}
secret
方法與 ask
類似,但使用者在指令列輸入的過程中將看不到他們自己的輸入值。這個方法適用於像使用者詢問如密碼等機密資訊的時候:
1$password = $this->secret('What is the password?');
1$password = $this->secret('What is the password?');
要求確認
若需要使用者回答簡單的「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 // 回傳自動補全的選項...3});
1$name = $this->anticipate('What is your address?', function (string $input) {2 // 回傳自動補全的選項...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// 寫入一行空行...2$this->newLine();34// 寫入三行空行...5$this->newLine(3);
1// 寫入一行空行...2$this->newLine();34// 寫入三行空行...5$this->newLine(3);
表格
通過 table
方法可以很輕鬆地正確為多行列資料進行格式化。只需要提供表格的欄位名稱與表格的資料,Laravel 就會自動計算適當的表格寬高:
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
事件會在指令完成執行後被分派。