「支援 PHP 7.1 到 8.3,程式碼裡是不是寫了一堆 if (version_compare(...)) 這種版本分支?」
翻遍 src/ 目錄,找不到任何一處版本判斷。今天來看這個套件是怎麼在完全不寫版本分支的情況下,撐住橫跨 8 個 PHP 版本的 CI 矩陣。
src/ 裡沒有用到任何 PHP 8 限定語法PHP 8.0 帶來了 union return type、建構子屬性提升;PHP 8.0 的 nullsafe operator ?->;PHP 8.0 的 match 表達式。核對 src/ 目錄底下全部檔案,這三種語法一次都沒出現過。
這代表這個套件的程式碼寫法,本身就停留在一個「PHP 7 時代也寫得出來」的風格——不是刻意為了相容性去避開新語法,比較像是這批程式碼從 2021 年寫下來之後,維護時多半是加欄位、加付款方式,沒有機會去動用到那些新語法能帶來的好處。相容性在這裡不是設計出來的成果,是程式碼風格長期沒有更新的副作用。
❌ 刻意寫相容層:程式碼裡混雜版本判斷
public function getPaymentType()
{
if (PHP_VERSION_ID >= 80000) {
return match ($this->getChoosePayment()) {
'Credit' => 'credit_card',
'ATM' => 'atm',
default => 'unknown',
};
}
switch ($this->getChoosePayment()) {
case 'Credit':
return 'credit_card';
case 'ATM':
return 'atm';
default:
return 'unknown';
}
}
✅ 現況:從頭到尾只寫一種寫法,天生就通吃全版本
public function getPaymentType()
{
switch ($this->getChoosePayment()) {
case 'Credit':
return 'credit_card';
case 'ATM':
return 'atm';
default:
return 'unknown';
}
}
正例(也就是 omnipay-ecpay 目前的實際寫法邏輯)不是「更聰明」,而是還沒有機會走到需要用新語法解決問題的地步。這種相容性策略的優點是零維護成本——沒有版本分支要顧,也不用擔心某個版本的分支寫錯;但代價是新語法帶來的表達力、型別安全(例如 union return type 能讓函式簽章更精確)完全沒有被用上。
這裡有一個值得注意的真實發現:在本機用比 CI 矩陣宣告的最高版本(8.3)更新的 PHP 8.5.10 環境跑這個套件的測試時,跑 vendor/bin/phpunit --coverage-text 會跳出這樣的訊息:
Deprecated: GuzzleHttp\Promise\queue(): Implicitly marking parameter $assign as
nullable is deprecated, the explicit nullable type must be used instead in
.../vendor/guzzlehttp/promises/src/functions.php on line 24
這個警告不是 omnipay-ecpay 自己的程式碼造成的——它是這個套件依賴鏈裡的間接套件 guzzlehttp/promises,內部某個函式參數用了「隱式標記為 nullable」的舊寫法(例如 function foo(SomeType $x = null) 而不是明確寫 ?SomeType $x = null),這種寫法從 PHP 8.4 開始被標記為已棄用。
跨版本相容性從來不是只看你自己寫的那幾個檔案,是整條依賴鏈裡,只要有任何一個套件先倒下,你的使用者體驗就會先受影響——即使 omnipay-ecpay 自己的程式碼完全沒有相容性問題,使用者在更新的 PHP 版本上執行時,還是會先看到來自別人程式碼的警告。這也是為什麼「這個套件相容性很好」這句話,永遠只能講到「以目前這個依賴版本組合來說」為止,而不是一個永遠成立的保證。
如果你的套件也依賴其他第三方函式庫,你有沒有定期用比你 CI 矩陣宣告的版本更新的 PHP(或其他語言的執行環境)跑一次測試,看看依賴鏈裡有沒有人先開始出現棄用警告?
src/ 目錄完全沒有用到 PHP 8 限定語法,相容性是「沒用新東西」換來的副作用,不是刻意設計的相容層guzzlehttp/promises)先出現棄用警告,會比你自己的程式碼更早讓使用者感受到相容性問題明天看另一種「承諾沒跟上現況」的樣子:README 上的徽章,連到的還是幾年前用過的 CI 服務,跟現在真正在跑的 GitHub Actions 已經是兩條不相干的線。