PHP 開發中常見的 E_STRICT: Declaration of B::foo() should be compatible with A::foo() 錯誤,往往被誤解為「類定義順序問題」。本文深度剖析錯誤本質,揭示 Liskov 替換原則 (LSP) 在 PHP 中的具體體現,給出標準化修復方案。
<?php
error_reporting(E_ALL | E_STRICT);
abstract class A {
public static function foo() { echo 'bar'; }
}
abstract class B extends A {
public static function foo($str) { echo $str; } // 新增必填參數
}
// Strict Standards: Declaration of B::foo() should be compatible with A::foo()
網上流傳「父類必須在子類前定義」的說法純屬誤導。實測證明:無論定義順序如何,只要方法簽名不兼容,必定觸發 E_STRICT。這是 PHP 5.3+ 強制執行接口契約的機制,而非編譯器 Bug。
子類方法簽名必須兼容父類,具體體現為:
| 規則 | 父類 | 子類允許 | 子類禁止 |
|---|---|---|---|
| 參數個數 | 1個 | 1個,或 1個必填 + 可選參數 | 增加必填參數 |
| 參數類型 (PHP 7.0+) | 無 / 父類型 | 同類型、子類型 (協變) | 父類型、無關類型 (逆變) |
| 返回類型 (PHP 7.0+) | 無 / 類型 | 同類型、子類型 (協變) | 父類型 (逆變) |
| 訪問修飾符 | public | public | protected/private |
| 靜態屬性 | static | static | 非 static |
abstract class B extends A {
public static function foo($str = null) { // 默認值使參數可選
echo $str ?? 'bar';
}
}
原理: 默認值使參數在調用時可省略,保持調用簽名兼容:B::foo() 和 B::foo('test') 均合法,滿足 LSP。
若業務確實需要不同參數,說明繼承關係設計有缺陷,應採用:
error_reporting(E_ALL & ~E_STRICT); // 僅作為緊急臨時方案
class ParentClass {
public function __construct($required) {}
}
class ChildClass extends ParentClass {
public function __construct($required, $optional = null) {} // 允許:新增可選參數
// public function __construct() {} // 禁止:缺少父類必填參數
}
PHP 不強制子類調用 parent::__construct(),但簽名兼容規則依然適用。
E_STRICT 常數已移除,相關錯誤歸類為 E_WARNING 或 Error 異常declare(strict_types=1) 時,類型不兼容直接拋出 TypeErrormixed 類型引入後,兼容性判斷更複雜,需參考 官方文檔error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED),確保兼容性警告不被忽略Declaration should be compatible 不是 Bug,而是 PHP 執行「設計契約」的保護機制。它強迫開發者遵循 LSP,確保多態調用的安全性。真正的解決之道不在於繞過檢查,而在於重新審視類層級設計:如果子類不能無縫替換父類,那就不應該繼承。