Behavior 如何將自身屬性/方法「注入」到元件?本文從源碼層面剖析 __get()/__set()/__call() 魔術方法實現、ensureBehaviors() 觸發時機、canGetProperty()/hasMethod() 可見性判斷邏輯,並附帶 DI 容器實戰應用。
當存取 $component->property1 時:
public function __get($name) {
$getter = 'get' . $name;
if (method_exists($this, $getter)) {
return $this->$getter(); // 1. 優先查找元件自身 getter
} else {
// 2. 元件無 getter → 檢查行為注入屬性
$this->ensureBehaviors(); // 確保行為已綁定
foreach ($this->_behaviors as $behavior) {
if ($behavior->canGetProperty($name)) {
return $behavior->$name; // 代理存取行為屬性
}
}
}
// 3. 皆無 → 拋出異常
throw new UnknownPropertyException(...);
}
關鍵差異:yii\base\Object::__get() 直接拋異常;Component::__get() 在無 getter 時會嘗試行為注入。
public function __call($name, $params) {
$this->ensureBehaviors();
foreach ($this->_behaviors as $object) {
if ($object->hasMethod($name)) {
return call_user_func_array([$object, $name], $params); // 代理呼叫
}
}
throw new UnknownMethodException(...);
}
三者均定義於 yii\base\Object:
public function canGetProperty($name, $checkVars = true) {
return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name);
}
public function canSetProperty($name, $checkVars = true) {
return method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name);
}
public function hasMethod($name) {
return method_exists($this, $name);
}
實際可存取規則:
| 成員類型 | 判斷通過條件 | 實際可存取 |
|---|---|---|
| 屬性(讀) | 有 getter 或成員變數存在(任何可見性) | 僅 public 成員變數可直接存取 |
| 屬性(寫) | 有 setter 或成員變數存在 | 僅 public 成員變數可直接寫入 |
| 方法 | method_exists 返回 true(任何可見性) | 僅 public 方法可呼叫 |
原因:判斷邏輯用 property_exists()/method_exists() 不限可見性,但實際代理存取時($behavior->$name、call_user_func_array())受 PHP 可見性限制。
Yii 提供 yii\di\Container 實現三類注入:
// 註冊依賴
$container = new \yii\di\Container;
$container->set('yii\db\Connection', [
'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
]);
// 解析建立(遞迴解決依賴)
$db = $container->get('yii\db\Connection');
全域容器:Yii::$container 可全域自定義核心類預設值(如 LinkPager::maxButtonCount),並支援建構子自動注入(如 Controller 依賴 Service)。
ensureBehaviors() 機制:理解其在 __get/__set/__call/canGetProperty/hasMethod/on/off 等處的觸發setSingleton(),避免重複實例化深入理解注入原理,能讓你在除錯「為何屬性存取失效」或「方法呼叫異常」時快速定位問題,也是設計高擴展性 Yii 應用的基石。