題目代碼
<?php
class Foo {
private $name = 'hdj';
public function getName() { return $this->name; }
}
class Bar extends Foo {
public $name = 'deeka';
}
$bar = new Bar;
var_dump($bar->name); // 輸出?
var_dump($bar->getName()); // 輸出?
?>
輸出結果
string(5) "deeka" // $bar->name
string(3) "hdj" // $bar->getName()
核心原理解析
1. 屬性遮蔽而非覆蓋
Foo::$name 為 private,**子類不可見、不可繼承、不可覆蓋**。
Bar::$name 為 public,**新聲明的一個完全獨立屬性**,僅名字相同。
- 對象內存中同時存在兩個
name 槽位:Foo::name(私有)與 Bar::name(公開)。
2. 訪問權限在「定義處」靜態綁定
Foo::getName() 內部 $this->name 編譯時綁定到 Foo::$name(私有)。
- 即便
$this 實際是 Bar 實例,訪問權限檢查基於方法所在類,而非運行時類型。
- 因此
$bar->getName() 返回父類私有屬性 'hdj'。
3. 對象屬性訪問規則
| 訪問方式 | 訪問到的屬性 | 原因 |
$bar->name(外部直接訪問) | Bar::$name (public) | 查找順序:當前類 → 父類;找到 public 即返回 |
$bar->getName()(內部方法訪問) | Foo::$name (private) | 方法定義在 Foo,訪問權限綁定 Foo,private 可見 |
$bar->getName() 若在 Bar 中重寫 | Bar::$name (public) | 方法定義在 Bar,訪問權限綁定 Bar |
驗證代碼:記憶體中確有兩個槽位
<?php
$bar = new Bar;
$ref = new ReflectionObject($bar);
foreach ($ref->getProperties(ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PRIVATE) as $prop) {
$prop->setAccessible(true);
echo $prop->getDeclaringClass()->getName() . '::$' . $prop->getName() . ' = ' . var_export($prop->getValue($bar), true) . "\n";
}
/*
輸出:
Foo::$name = 'hdj'
Bar::$name = 'deeka'
*/
最佳實踐避坑指南
- 避免同名屬性跨層級聲明:子類需新語義時改名(如
$childName),或改用 protected 共享同一槽位。
- 統一使用 getter/setter:外部永不直接訪問屬性,內部邏輯集中在方法中,避免遮蔽導致的認知偏差。
- 理解 private 意為「僅當前類可見」:非「子類不可用」,子類可擁有同名私有屬性,互不干擾。
- 測試覆蓋:針對繼承鏈編寫單元測試,驗證屬性隔離行為。
延伸:protected 與 public 差異
protected:子類可見,可直接 $this->name 讀寫,**同一槽位**,真正共享。
public:同 protected 但外部也可見。
- 若需父子共享狀態 →
protected;若需父子各自獨立狀態 → private + 子類新聲明(但建議改名)。