Laravel 實用小技巧 —— Artisan 入門(上)

快樂的皮拉夫發表於2023-05-17

我們都知道,Laravel 提供了強大的命令列功能 —— Artisan,藉助這個工具我們可以很方便地實現各種需要在控制檯下處理的功能。接下來,我們就來介紹一下,如何在實際工作中高效地使用它。
考慮文章篇幅限制,我們將分成兩篇文章為大家介紹。

起步

接下來,我們就從建立自定義的 Artisan 命令開始吧。

建立命令檔案

自定義的命令一般放在 app/Console/Commands 目錄下,如果選擇其他目錄,需要保證能夠被 Composer 載入到。一般我們在 App\Console\Kernel 類中的 commands 方法中進行註冊命令:

protected function commands(): void
{
    $this->load(__DIR__.'/Commands');
    $this->load(__DIR__.'{自定義命令目錄}');
}

建立自定義命令非常簡單,比如我們現在要實現一個傳送郵件的命令,只需要在專案根目錄下執行以下命令即可:

php artisan make:command SendMail

執行完以後,會在 app/Console/Commands 目錄下生成一個 SendMail 的類檔案。接下來我們就來看一下命令檔案的「內部構造」。

定義命令結構

這裡先來介紹一下該檔案中我們需要關注的點,檔案主要結構如下:

...
protected $signature = 'mail:send'; // 命令簽名
protected $description = '傳送郵件'; // 命令描述
// 命令入口
public function handle()
{
    return 0;
}
...
  • signature 屬性: 命令簽名,由命令名稱、引數和選項組成
  • description 屬性: 命令描述
  • handle 方法: 命令處理的邏輯,相當於命令入口

當我們在命令列執行 php artisan list 命令時,會看到如下展示:

pN9zKVJh9k.png

這裡我們在定義命令名稱的時候,並沒有選擇和類名稱保持一致。其實透過上面的顯示效果不難發現,我們命令名稱使用了冒號分隔。冒號前的部分相當於「名稱空間」,冒號後的部分相當於「具體命令」。比如當我們再新增一個刪除郵件的命令時,就可以定義一個 mail:delete 命令,這時再執行 php artisan list 命令,會發現兩個命令都放在 mail 這個「名稱空間」下了:

uPyWYeLzek.png

那麼命令名稱裡這個冒號分隔符是必須的嗎?並不是。如果你定義了一個 test 命令,這時你再執行 php artisan list,發現系統並沒有報錯,依然能顯示可用的命令,只不過這個命令跑到了 Available commands 這個「名稱空間」下,如下圖:

mWnVTD5JIJ.png

看似沒什麼問題,而且命令名稱更短了,我們能直接這麼用呢?

不急,我們先把這個命令刪掉,然後再執行 php artisan list 看一下:

1adC2wDHzx.png

神馬情況?test 命令不是已經刪除了麼,為什麼還在呢?仔細看命令描述不難發現,這並不是我們定義的那個 test 命令,而是系統執行測試用例的原生命令。

現在應該知道為什麼我們需要指定「名稱空間」了吧 —— 沒錯,就是為了保持規範,避免與系統預設的命令衝突。因為當出現命令衝突的情況,系統並不會報錯,而是優先使用使用者自定義的命令。

那是不是意味著用了「名稱空間」的寫法就萬事大吉了呢?也不是,因為系統或者安裝的擴充套件包中預製的命令也使用了「名稱空間」,比如我們執行 php artisan list cache,會發現 cache 這個名稱空間下預製了以下命令:

31taJQ3dK5.png!large

我們在定義自己的命令時,就需要避開 cache 這個名稱空間了。一個良好的習慣就是我們在定義我們的命令時,首先使用 php artisan list {namespace} 來檢查下 namespace 是否可用,如果已被佔用的話,就需要考慮換一個名字了。

講完了定義命令的規範,我們再來看一看引數(argument)這個東西。定義引數透過以下方式定義:

protected $signature = '{命令名稱} {引數名稱}';

比如我們需要在呼叫命令時指定給誰傳送郵件,就可以定義一個 user 的引數,引數格式如下:

protected $signature = 'mail:send {user}';

然後就可以透過以下方式呼叫命令了:

php artisan mail:send Tom

handle 方法中透過 $this->argument('{引數名稱}') 就可以獲取到傳遞的引數了。

Artisan 支援傳遞多個引數。比如我們需要在傳送郵件時指定一下郵件的性質,就可以再增加一個 type 的引數:

protected $signature = 'mail:send {user} {type}';

這時我們就可以透過以下的方式來使用命令:

php artisan mail:send Tom notify

透過獲取 type 引數我們可以知道這是一封通知型別的郵件。

因為我們在命令列中傳遞引數時並不能指定引數名稱,所以必須嚴格按照引數定義的順序傳遞。

如果我們想給多個人傳送郵件該怎麼辦呢?也很簡單,Artisan 的引數支援陣列型別的引數,我們只需要在引數後加上 * 就可以了:

protected $signature = 'mail:send {users*}';

然後我們在呼叫命令的時候透過空格分隔的方式來傳遞引數就可以了:

php artisan mail:send Tom Sam

這時候,命令名稱後傳遞的引數就被當成一個陣列接收了。列印 $this->argument('users') 顯示如下:

array:2 [
  0 => "Tom"
  1 => "Sam"
]

這裡你會發現,我在上一個示例中把 type 引數去掉了,這是為什麼呢?如果帶著 type 引數會怎麼樣呢?我們按如下格式定義命令:

protected $signature = 'mail:send {users*} {type}';

呼叫命令方式如下:

php artisan mail:send Tom Sam notify

這時命令列會丟擲一個錯誤:

Cannot add a required argument "type" after an array argument "users".

錯誤的意思就是:不能在陣列引數後面新增必傳引數

其實仔細看看我們呼叫的命令就可以看出端倪來,當我們在陣列引數後面追加一個引數時,我們是無法在引數上區分出陣列的邊界的。所以 Artisan 在底層封裝時就杜絕了這種情況的存在。

那如果我們還需要這個 type 引數該怎麼辦呢?如果一定透過引數形式實現的話,只能把 type 引數放在陣列引數前,這樣就不會報錯了。但是切記,type 一定不能定義成陣列引數 —— 因為 Artisan 只能新增一個陣列引數,新增兩個陣列引數時一樣會報上面的錯誤,道理是一樣的。

到這裡,你可能已經感受到引數用法的侷限性了,這還不止,當你需要定義多個引數時,你還必須要記住每個引數對應的位置——是不是瞬間頭大!

當你遇到這種困惑時,或許你就該考慮使用選項(option)了。

比如你可以在發郵件時指定是立即傳送還是稍後傳送,你就可以定義一個 send-now 的引數,格式如下:

protected $signature = 'mail:send {--send-now}';

選項和引數在格式上的不同之處在於選項前面多了 --,同時我們在呼叫命令的方式需要按照如下的方式呼叫:

php artisan mail:send --send-now

這時在 handle 中透過 $this->option('--send-now') 獲取到的是 true

這是選項的第一種使用方式,即不指定選項值的方式,這種情況選項的值就是 true 或者 false :不傳遞選項的時候獲取到的值是 false,指定選項名稱的時候獲取到的是 true。這種情況在選項值僅有 truefalse 時非常方便。

另一種方式是需要指定選項值的方式,比如我需要在傳送郵件時指定具體的傳送時間,我們可以定義一個 send-at 的選項:

protected $signature = 'mail:send {--send-at=}';

帶值的選項後面多了一個 =,這就要求我們在呼叫命令的時候必須按照如下方式傳遞選項:

php artisan mail:send --send-at='2023-05-17 12:00'

引數如果想作為可選引數的話,需要在引數名稱後面追加一個 ?,如 {user?} ,選項則不需要,如果選項指定傳遞值的話,命令列如果沒有傳遞選項,並不會丟擲異常,而是將選項值置為預設的 null

如果你嫌定義的選項太長,傳遞起來不方便的話,你還可以給他定義一個縮寫:

protected $signature = 'mail:send {--S|send-at=}';

這樣就可以使用縮寫方式來傳遞選項了:

php artisan mail:send -S'2023-05-17 12:00'
或者 
php artisan mail:send -S '2023-05-17 12:00'

這裡需要注意三點:

  1. 縮寫必須是一位字母,大小寫均可,一般使用大寫。
  2. 使用縮寫的方式傳遞選項時,選項名稱縮寫前是 -,與全稱的 -- 注意區分,同時選項名稱和選項值之間不需要 = 連線。可以加一個空格,也可以緊跟選項名稱縮寫之後。
  3. 選項值中如果有空格的情況下,需要用 ' 或者 " 將值包裹起來,否則會報錯。

此外,選項也是支援陣列傳值的,但是和引數的陣列傳值方式有所區別,選項是透過傳多次值的方式來實現陣列傳值的,比如我們指定在 2023-05-17 12:002023-05-17 15:00 兩個時間來傳送郵件,就可以把引數中的 {--S|send-at=} 定義為 {--S|send-at=*} ,呼叫方式如下:

php artisan mail:send -S '2023-05-17 12:00' -S '2023-05-17 15:00'

這樣就可以給選項傳遞多個值了。

當你在設計命令的時候,如果需要定義兩個以上的引數的時候,這時不妨考慮使用選項的方式來替代,後者在這種情況下使用起來更加靈活方便。

完善引數說明

當我們想了解某個命令的用法時,我們一般使用如下命令:

php artisan help mail:send

顯示內容如下:

JORJNkqUL2.png!large

在選項說明裡,我們可以看到除了我們自定義的引數和選項外,還有系統預設的一些選項,而且這些選項後面還帶了說明資訊,這樣能幫助我們快速瞭解選項的具體作用。那我們自定義的引數和選項能不能新增這些說明資訊呢?

很簡單,只需要在引數和選項後跟一個分隔的 : ,然後跟上說明資訊即可:

...
protected $signature = 'mail:send
                        {user : The receiver of mail}
                        {type : The type of mail}
                        {--I|send-now : Whether to send the mail immediately}
                        {--S|send-at=* : Specifies when to send}';
...

配置完後,我們在列印命令的幫助資訊時,就能看到引數及選項的說明了:

tSwtOldCLV.png

好了,這篇文章就為大家介紹到這裡。在下一篇文章中,我們會為大家介紹命令輸入輸出相關的內容,感謝大家的持續關注~

本作品採用《CC 協議》,轉載必須註明作者和本文連結
你應該瞭解真相,真相會讓你自由。

相關文章