昨天講完「這個套件沒有靜態分析工具」,今天實際動手裝一次,看看真的跑起來會冒出什麼。先說清楚:以下是我在一份本機暫存複本上做的實驗,不是套件已經正式導入的工具,也還沒有回報或修正到套件原始碼裡——這個系列到這裡為止,都還停留在「診斷現況」,動手修正是第四部才會做的事。
phpstan.neonparameters:
level: 5
paths:
- src
跑 vendor/bin/phpstan analyse,得到 7 個錯誤。
第一類:宣告的型別跟實際不符——AcceptNotificationRequest::getNotificationResponse() 宣告要回傳 AcceptNotificationResponse,但因為程式邏輯上實際會回傳 Omnipay\Common\Message\ResponseInterface 這個更廣的介面型別,PHPStan 認為簽章跟實際行為對不上。
第二類:透過 static:: 呼叫 private 方法——PurchaseRequest::filterValues()、PurchaseResponse::htmlToArray() 兩處都是私有方法卻透過 static:: 呼叫,PHPStan 判定這是「不安全的呼叫」(staticClassAccess.privateMethod):如果未來有子類別繼承這些類別,static:: 的呼叫行為可能不是原本預期的那個私有方法。
第三類:呼叫了介面上不存在的方法、以及兩處恆為 false 的判斷式——PurchaseResponse.php 呼叫了 RequestInterface 介面上並沒有定義的 getEndpoint() 方法;另外兩處 ! 否定判斷式,PHPStan 分析後認為條件恆為 false,屬於實際上不會被執行到的死碼。
stirng這 7 個錯誤裡最值得記錄下來的是這一個:
/**
* 週期種類.
*
* ...
*
* @param stirng $value
* @return $this
*/
public function setPeriodType($value)
{
return $this->setParameter('PeriodType', $value);
}
PHPDoc 裡把 string 打成了 stirng。這種打字錯誤在日常開發裡幾乎不會造成任何實際影響——程式本身照樣能執行,測試照樣能過,IDE 的型別提示可能也不會特別警示。但 PHPStan 分析 PHPDoc 時,會把 @param 後面那個字當成一個型別名稱去解析,stirng 不是 PHP 內建型別、也找不到對應的類別,於是回報:
Parameter $value of method ...::setPeriodType() has invalid type
Omnipay\ECPay\Traits\stirng.
PHPStan 甚至把它解讀成「你是不是想用一個叫 stirng 的類別,但這個類別在目前這個命名空間下找不到」——這個錯誤訊息本身有點好笑,但它精準示範了一件事:PHPDoc 裡的型別標註,人眼掃過去很容易就當作看懂了,靜態分析工具卻是逐字比對,任何一個打字錯誤都逃不掉。
❌ 靠 code review 時人眼複查 PHPDoc
/**
* @param stirng $value
* @return $this
*/
public function setPeriodType($value) { ... }
→ Reviewer 掃過去,看到「@param 某個型別 $value」的形狀就跳過,
很少有人會真的一個字母一個字母核對 "string" 拼對了沒
✅ 讓靜態分析工具做逐字比對
$ vendor/bin/phpstan analyse
Parameter $value of method ...::setPeriodType() has invalid type
Omnipay\ECPay\Traits\stirng.
→ 工具沒有「大概看得懂就好」這種模式,任何一個打字差異都會被攤開來
人眼複查適合抓「邏輯有沒有寫對」,抓不住「這個字有沒有拼對」這種瑣碎但工具最擅長的錯誤——這正是「靠人的自覺」跟「靠工具的機制」該分工的地方。
這個系列到 Day 19 為止的所有發現,都刻意停留在「診斷」階段,還沒有動手修正這 7 個錯誤,也還沒有把 PHPStan 正式導入這個套件。理由很直接:發現問題,跟決定怎麼處理問題,是兩個不同層次的判斷——第一類(宣告型別跟實際不符)可能需要重新設計方法簽章;第二類(static:: 呼叫 private)可能牽涉到要不要把方法改成 self:: 呼叫,還是重新思考類別的繼承設計;stirng 這種純打字錯誤則是幾乎零風險、可以直接修的那種。貿然一次全部修完,反而可能因為沒想清楚每個警告背後真正的原因,把原本可運作的程式碼改壞。 這幾類判斷會留到第四部再實際動手處理。
如果你的專案還沒有裝過 PHPStan/Psalm,猜猜看:第一次用最低規則等級掃描 src/,你覺得會冒出幾個錯誤?裝起來實際跑一次,答案通常會比你猜的更多。
string 被打成 stirng,被 PHPStan 誤判成一個不存在的型別明天看另一個被遺忘的工具:composer.json 裡定義的 check-style 指令,從來沒被 CI 呼叫過。實際跑一次,會發現它從一開始就會是紅燈——這可能正是它至今沒被接進 CI 的原因。