--- tags: laravel --- # Artisan 命令列 ![](https://i.imgur.com/pzNNkcJ.jpg) ![](https://i.imgur.com/1QZ0J3B.jpg) ## 簡介 Artisan 是 Laravel 自帶的命令列接口, 它提供了相當多的命令來幫助你建構應用。 你可以通過 list 命令查看所有可用的 Artisan 命令 `php artisan list` 每個命令都包含幫助界面,它會簡介命令的可用參數及選項。只需要在命令前加上 help 即可查看命令幫助界面 `php artisan help migrate` ### Laravel Sail 假如你使用 Laravel Sail 作為你的本地端開發環境,記得使用 sail 命令來觸發 Artisan 命令。 Sail將會在你的應用 Docker 容器 去執行 Artisan 命令 `./sail artisan list` ### Tinker (REPL) 所有 Laravel 應用都包含了 Tinker,一個由 PsySH 包提供支持的交互式命令介面 #### 安裝 所有 Laravel 應用預設都包含了 Tinker。但你仍可以使用 Composer 在需要時手動安裝: `composer require laravel/tinker` > 提示: > > 尋找能與 Laravel 互動的圖形UI界面嗎? 試看看 Tinkerwell 吧 #### 用法 Tinker 讓你在命令列與整個 Laravel 應用進行互動。包括但不限於 Eloquent ORM、任務、事件等等。通過運行 Artisan 命令 tinker 來進入 Tinker 環境 `php artisan tinker` 你可以通過 vendor:publish 命令來發布 Tinker 設定文件: `php artisan vendor:publish --provider="Laravel\Tinker\TinkerServiceProvider"` > 注意: > > Dispatchable 類別中的 dispatch() 幫助函數和方法已被棄用以將任務添加至隊列中。因此當你使用 Tinker 時,請使用 Bus::dispatch() 或 Queue::push() 來發送任務 #### 命令白名單 Tinker 採用白名單來確定允許哪些 Artisan 命令可以在 shell 中執行。預設情況下你可以運行 clear-compiled、down、env、inspire、migrate、optimize 和 up 命令。如果你想將更多命令添加到白名單,請將該命令添加到 tinker.php 配置檔案中的 commands 陣列中 ``` 'commands' => [ // App\Console\Commands\ExampleCommand::class, ], ``` #### 別名黑名單 大多數情況下,Tinker 會在你引入類別時自動為其添加別名。然而,你可能不希望為某些類別添加別名。你可以在 tinker.php 配置檔案中的dont_alias 陣列裡列舉這些類別來完成此操作: ``` 'dont_alias' => [ App\Models\User::class, ], ``` ## 撰寫命令 除了 Artisan 內建的命令外,你也可以撰寫自己的自定義命令。命令在多數情況下位於 app/Console/Commands 資料夾中;不過只要你的命令可以由 Composer 加載,你就可以自由選擇自己的存放位置 ### 生成命令 你可以使用 Artisan 命令 make:command 來創建一個新的命令。make:command 命令會在 app/Console/Commands 目錄中創建一個新的命令類別。如果該資料夾不存在,它會在你第一次運行 make:command 命令時自動創建。生成的命令將包含所有命令中預設存在的屬性與方法 `php artisan make:command SendEmails` ### 命令結構 創建一個新命令後,你應該先修改 signature 和 description 屬性以便你在使用 Artisan 命令 list 時能夠清楚說明該命令的用法。 signature 屬性能讓你定義命令的預期輸入格式。而 handle() 方法則是在執行命令時會呼叫,你可以在這個方法中撰寫命令邏輯 這是一個簡單的例子,在命令類別的 handle() 中,我們可以注入任何需要的依賴項。Laravel 服務容器將會自動注入所有在建構子中帶型別提示的依賴 ``` <?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'; /** * Create a new command instance. * * @return void */ public function __construct() { parent::__construct(); } /** * Execute the console command. * * @param \App\Support\DripEmailer $drip * @return mixed */ public function handle(DripEmailer $drip) { $drip->send(User::find($this->argument('user'))); } } ``` >小技巧: > >為了更好的實現模組化,請儘量讓你的命令類別保持輕巧並且能夠延遲到應用服務完成。在下面的例子中,我們將注入一個服務類別來完成發送郵件 ### Closure 命令 基於 Closure 的命令提供了一個替代用類別來定義控制台命令的方法。就如同,在路由檔內直接撰寫 Closure 也是一種替代控制器的方法,而 Closure 命令可以說是命令類別的替代方法。在 app/Console/Kernel.php 檔案中的 commands() 中,Laravel 加載了 routes/console.php 檔案: ``` /** * Register the closure based commands for the application. * * @return void */ protected function commands() { require base_path('routes/console.php'); } ``` 儘管該檔案沒有定義 HTTP 路由,但它定義了進入應用的基於控制台的入口(routes)。 在這個檔案中,你可以使用 Artisan::command() 定義所有的 Closure 路由。 command() 接受兩個參數: 命令名稱 和用以呼叫的 Closure(可以接受命令參數及選項): ``` Artisan::command('mail:send {user}', function ($user) { $this->info("Sending email to: {$user}!"); }); ``` Closure 綁定了底層的命令實例,因此你可以訪問能夠在命令類別中使用的所有幫助函式 #### 型別提示依賴 除了接受命令參數及選項外,命令 Closure 也可以使用型別提示從服務容器中解析其他的依賴關係: ``` use App\Models\User; use App\Support\DripEmailer; Artisan::command('mail:send {user}', function (DripEmailer $drip, $user) { $drip->send(User::find($user)); }); ``` #### Closure 命令描述 在你定義一個 Closure 命令時,你可以通過 describe() 來為命令添加描述。這個描述會在你執行 php artisan list 或 php artisan help 命令時顯示: ``` Artisan::command('mail:send {user}', function ($user) { // ... })->purpose('Send a marketing email to a user'); ``` ## 定義輸入期望值 在撰寫控制台命令時,通常是通過參數和選項來收集用戶輸入。Laravel 讓你可以非常方便地在 signature 屬性中定義你期望用戶輸入的內容。signature 屬性允許使用單一且可讀性高,類似路由的語法來定義命令的名稱、參數和選項 ### 參數 所有用戶提供的參數和選項都被包含在大括號中。在下方的例子,這個命令定義了一個必填的參數 user ``` /** * The name and signature of the console command. * * @var string */ protected $signature = 'mail:send {user}'; ``` 你也可以創建可選參數或為參數定義預設值: ``` // 可選參數... mail:send {user?} // 帶預設值的可選參數... mail:send {user=foo} ``` ### 選項 選項類似於參數,是用戶輸入的另一種形式。在命令列中指定選項的時候,它們以兩個短橫線(--)作為前綴。共有兩種類型的選項:接收值和不接受值。不接收值的選項就像是一個布林值「開關」,讓我們看一下這種類型的選項的例子: ``` /** * The name and signature of the console command. * * @var string */ protected $signature = 'mail:send {user} {--queue}'; ``` 在這個例子中,在呼叫 Artisan 命令時可以指定 --queue 的開關。如果傳入了 --queue 選項,該選項的值將會是 true 。否則,其值將會是 false : `php artisan mail:send 1 --queue` #### 帶值的選項 接下來讓我們看一下需要帶值的選項,如果用戶需要為一個選項指定一個值,則需要在選項名稱的後面追加一個等號(=) ``` /** * The name and signature of the console command. * * @var string */ protected $signature = 'mail:send {user} {--queue=}'; ``` 在這個例子中,用戶可以如下的方式來傳遞該選項的值,假如在呼叫此命令時並沒有指定選項的值,該值將為 null `php artisan mail:send 1 --queue=default` 你可以在選項名稱後指定其預設值,如果用戶沒有傳入值給選項,將使用預設的值 `mail:send {user} {--queue=default}` #### 選項簡寫 要在定義選項的時候指定一個簡寫,您可以在選項名稱前面使用 | 分隔符號來將選項名稱與其簡寫分隔開來 `mail:send {user} {--Q|queue}` ### 輸入陣列 如果您想要接收多個輸入值的參數或者選項,可以使用 * 字元。首先,讓我們看一下指定了多個輸入值參數的例子: `mail:send {user*}` 當呼叫這個方法的時候,user 參數的輸入參數將按順序傳入給命令並轉成陣列。例如以下命令將會設置 user 的值為 ['foo', 'bar'] : `php artisan mail:send foo bar` #### 選項陣列 當定義一個接收陣列的選項時,傳入命令的每一個選項值都應該添加選項名稱作為前綴 ``` mail:send {user} {--id=*} php artisan mail:send --id=1 --id=2 ``` ### 輸入描述 你可以使用冒號來將參數和描述開來,以實現為輸入參數和選項指定描述。如果需要一些額外的空間來定義命令,可以將其隨意切成多行: ``` /** * 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}'; ``` ## 命令 I/O ### 解析輸入 在執行命令時,你可能需要獲取命令接收到的參數或選項的值。可以使用 argument() 和 option() 來實現,如果不存在的話就會回傳 null: ``` /** * Execute the console command. * * @return int */ public function handle() { $userId = $this->argument('user'); // } ``` 你可以使用 arguments() 方法以陣列的形式來解析參數: `$arguments = $this->arguments();` 和獲取參數的方式類似,使用 option() 可以很容易的獲取選項的值。你亦可呼叫 options() 來以陣列的形式解析所有的選項: ``` // Retrieve a specific option... $queueName = $this->option('queue'); // Retrieve all options as an array... $options = $this->options(); ``` ### 交互式輸入 除了顯示輸出以外,你亦可在執行命令的時候通過詢問用戶的方式來提供輸入。ask() 將詢問用戶指定的問題來接收用戶輸入,然後用戶輸入將會傳入命令中: ``` /** * Execute the console command. * * @return mixed */ public function handle() { $name = $this->ask('What is your name?'); } ``` secret() 與 ask() 相似,但是用戶在輸入的時候,用戶輸入將被隱藏。這個方法在詢問一些注入密碼之類的敏感資料時是非常有用的: `$password = $this->secret('What is the password?');` #### 詢問確認 如果你需要請求用戶進行一個簡單的確認,可以使用 confirm() 來實現。預設情況下這個方法會返回 false 。當然,如果用戶輸入 y 或 yes ,這個方法將會返回 true ``` if ($this->confirm('Do you wish to continue?')) { // } ``` 如有必要,你能假定確認交互的預設值為 true ,透過將 true 作為第二參數傳入 confirm() ``` if ($this->confirm('Do you wish to continue?', true)) { // } ``` #### 自動完成 anticipate() 可用於為可能的選項提供自動完成功能。用戶依然可以忽略自動完成的提示,進行自己的回答: `$name = $this->anticipate('What is your name?', ['Taylor', 'Dayle']);` 或者你可以將一個 Closure 作為第二參數傳遞給 anticipate() 。每當用戶輸入字元時, Closure 都會被呼叫。 Closure 應該接受一個包含用戶輸入的字串形式的參數,並返回一個可供自動完成的選項的陣列: ``` $name = $this->anticipate('What is your address?', function ($input) { // Return auto-completion options... }); ``` #### 多選問題 你可以使用 choice() 給用戶提供一些預設的選擇,亦可設定陣列的索引預設值,以處理用戶沒有選擇的情況,作法是將索引值傳入作為第三參數: ``` $name = $this->choice( 'What is your name?', ['Taylor', 'Dayle'], $defaultIndex ); ``` 此外,choice() 接收可選的第四個和第五個參數,用於確定選擇一個有效的回應可嘗試的最大次數以及是否可以選擇多個答案: ``` $name = $this->choice( 'What is your name?', ['Taylor', 'Dayle'], $defaultIndex, $maxAttempts = null, $allowMultipleSelections = false ); ``` ### 撰寫輸出 要發送輸出到控制台,請使用 line,info,comment,question 和 error 方法。每一個方法都使用適當的 ANSI 顏色以表明其目的。例如,讓我們為用戶顯示一些普通訊息。正常情況下,info() 將會在控制台中顯示綠色文字: ``` /** * Execute the console command. * * @return mixed */ public function handle() { // ... $this->info('The command was successful!'); } ``` 你可以使用 error() 來顯示錯誤訊息。錯誤訊息通常以紅色文字顯示: `$this->error('Something went wrong!');` 如果想要顯示沒有顏色的輸出文字,請使用 line() : `$this->line('Display this on the screen');` 如需斷行,可以使用 newLine() : ``` // Write a single blank line... $this->newLine(); // Write three blank lines... $this->newLine(3); ``` #### 表格布局 table() 讓多行多列資料的格式化輸出變得非常輕鬆。它將基於表頭和行資訊內容,動態的去計算寬高: ``` use App\Models\User; $this->table( ['Name', 'Email'], User::all(['name', 'email'])->toArray() ); ``` #### 進度條 在執行較為耗時任務的時候,提供一個進度條時來提示使用者是很有必要的。使用 withProgressBar() , Laravel 將會顯示進度條並在每一次的遞迴中逐步推進 ``` use App\Models\User; $users = $this->withProgressBar(User::all(), function ($user) { $this->performTask($user); }); ``` 有時候,你可能需要手動來管理進度條的進程。首先,定義進度條總共要走多少步。接下來,每當處理好一個項目之後就推進進程。 ``` $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 [進度條組件文件](https://symfony.com/doc/current/components/console/helpers/progressbar.html)來獲取進度條的更多進階用法 ## 註冊命令 所有的控制台命令都註冊於 App\Console\Kernel 類別,它是應用的控制台核心。 由於控制台核心的 commands() 呼叫了 load() ,位於 app/Console/Commands 資料夾中的所有命令都將自動註冊。事實上你亦可隨時呼叫 load() 來掃描其他資料夾中的 Artisan 命令: ``` /** * Register the commands for the application. * * @return void */ protected function commands() { $this->load(__DIR__.'/Commands'); $this->load(__DIR__.'/../Domain/Orders/Commands'); // ... } ``` 你也可以在 app/Console/Kernel.php 檔案的 $commands 屬性中手動註冊命令的類別名稱。當 Artisan 啟動時,該屬性里列出的所有命令都將被服務容器解析並通過 Artisan 註冊: ``` protected $commands = [ Commands\SendEmails::class ]; ``` ## 透過程式來呼叫命令 有時,你可能想要不使用 CLI 而是透過程式去運行 Artisan 命令。例如在一個路由或控制器中執行一個 Artisan 命令。你可以使用 Artisan Facade的 call() 來實現。call() 的第一個參數為命令的名稱或類別名,第二個參數為陣列形式的命令參數。該方法將會返回退出碼: ``` use Illuminate\Support\Facades\Artisan; Route::post('/user/{user}/mail', function ($user) { $exitCode = Artisan::call('mail:send', [ 'user' => $user, '--queue' => 'default' ]); // }); ``` 此外,你也可以以字串的形式來傳遞完整的 Artisan 命令到 call() 裏頭: `Artisan::call('mail:send 1 --queue=default');` ### 傳遞陣列值 如果你的命令定義了一個接受陣列的選項,那便可直接傳遞陣列到該選項: ``` use Illuminate\Support\Facades\Artisan; Route::post('/mail', function () { $exitCode = Artisan::call('mail:send', [ '--id' => [5, 13] ]); }); ``` ### 傳遞布林值 如果你需要指定一個不接受字串的選項時,比如 migrate:refresh 命令中的 --force ,你可傳入 true 或 false : ``` $exitCode = Artisan::call('migrate:refresh', [ '--force' => true, ]); ``` ### 隊列 Artisan 命令 你甚至可以使用 Artisan Facade的 queue() 來進行 Artisan 命令隊列化,此舉能夠讓隊列工作進程將其置於後台處理。在使用該方法前,請確保已經配置了隊列並運行了隊列偵聽器: ``` use Illuminate\Support\Facades\Artisan; Route::post('/user/{user}/mail', function ($user) { Artisan::queue('mail:send', [ 'user' => $user, '--queue' => 'default' ]); // }); ``` 你也可以指定 Artisan 命令分配的連接或隊列: ``` Artisan::queue('mail:send', [ 'user' => 1, '--queue' => 'default' ])->onConnection('redis')->onQueue('commands'); ``` ### 從命令中去呼叫其他命令 有時可能會有在命令間互相呼叫的需求,你可以使用 call() 來滿足在一個已經存在的 Artisan 命令中呼叫其他命令的需求。call() 方法接收命令名稱和陣列形式的參數或選項: ``` /** * Execute the console command. * * @return mixed */ public function handle() { $this->call('mail:send', [ 'user' => 1, '--queue' => 'default' ]); // } ``` 你可以使用 callSilent() 來實現呼叫另一個命令但排除所有的輸出。callSilent() 的用法和 call 方法完全相同: ``` $this->callSilently('mail:send', [ 'user' => 1, '--queue' => 'default' ]); ``` ## 自定義 Stub Artisan 的 make 命令可用於建立多種多樣的類別,比如:控制器、任務、遷移和測試。這些類別是使用 stub 檔案來生成的,它將根據你的輸入來填充值。有時,你可能想要對 Artisan 命令生成的檔案進行一些很小的改動。您可以使用 stub:publish 命令來發布最常見的自定義 stub 來進行修改: `php 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 事件會在命令執行完成後發出