繼承是 OOP 碼復用基石。PHP 的繼承機制既有強類型語言的共性,又有動態語言的獨特規則。本文透過實例深度剖析構造函數鏈、方法覆寫、可見性變更等關鍵行為。
<?php
class Father {
public function __construct() {
echo '父類構造函數執行';
}
protected function say($str = 'Hello World!') {
echo '父類說:' . $str;
}
}
class Child extends Father {
public function __construct() {
echo '子類構造函數執行';
}
}
實例化 Child 只輸出「子類構造函數執行」。PHP 不會自動呼叫父類構造函數,需顯式呼叫:
class Child extends Father {
public function __construct() {
parent::__construct(); // 手動觸發父類構造
echo '子類構造函數執行';
}
}
// 輸出:父類構造函數執行 子類構造函數執行
class Child extends Father {
// 覆寫 protected 方法為 public:允許
public function say($str = '') {
echo '子類公開說:' . $str;
}
// 覆寫為 private:致命錯誤
// private function say() { ... } // Fatal error: Access level must be public or protected
}
核心規則:子類覆寫方法時,可見性 >= 父類。protected → public 合法,protected → private 非法。直觀理解:父親給你的受保護遺產,你可以公開分享,但不能私吞隱藏。
父類 private function say() 子類不可見、不可繼承、不可覆寫。子類定義同名方法視為全新方法,互不干擾。
父類 say($str = 'default'),子類覆寫 say()(無參數)會觸發 E_STRICT 警告:「Declaration should be compatible」。PHP 標準要求參數簽名兼容,可在 php.ini 調整錯誤報告等級抑制,但強烈建議保持簽名一致。
class Grandpa { public function init() { echo 'Grandpa '; } }
class Father extends Grandpa { public function init() { parent::init(); echo 'Father '; } }
class Child extends Father { public function init() { parent::init(); echo 'Child '; } }
$c = new Child(); $c->init(); // Grandpa Father Child
class Base {
final public function critical() { ... } // 禁止覆寫
final class Helper { ... } // 禁止被繼承
}
trait Loggable {
public function log($msg) { error_log($msg); }
}
class Service {
use Loggable;
}
$svc = new Service(); $svc->log('done');
parent::__construct(),除非刻意阻斷父類初始化。E_STRICT 與 LSP 違反。final 封鎖關鍵邏輯被意外覆寫。掌握 PHP 繼承的微妙規則,能寫出更健壯、可維護的物件導向程式碼。