傳統開發中,類之間強耦合導致加載順序極其敏感:A 依賴 B,B 依賴 C... 新人接手需花費大量時間理清實例化鏈路。本文從依賴倒置原則 (DIP) 切入,手把手實現一個輕量級 DI 容器,支持構造函數注入、反射自動解析依賴、參數合併、屬性注入、別名映射、閉包工廠、實例緩存,徹底擺脫手動 include 与实例化顺序烦恼。
ReflectionClass 讀取構造函數參數類型提示,自動從容器獲取依賴實例。Container.phpparams[$id] = $params;
$this->definitions[$id] = $this->normalizeDefinition($id, $def);
}
/** 獲取實例
* @param string $id
* @param array $params 臨時覆蓋參數
* @param array $properties 屬性注入
* @return mixed
*/
public function get($id, $params = [], $properties = []) {
if (!isset($this->definitions[$id])) {
return $this->build($id, $params, $properties); // 未註冊嘗試直接構建
}
$def = $this->definitions[$id];
$params = $this->mergeParams($id, $params);
if (is_callable($def)) { // 閉包工廠
return $def($this, $params, $properties);
} elseif (is_array($def)) { // 配置數組
$class = $def['class'] ?? $id;
unset($def['class']);
$properties = array_merge($def, $properties); // 數組其餘作為屬性
if ($class === $id) {
return $this->build($class, $params, $properties);
} else {
return $this->get($class, $params, $properties); // 別名轉發
}
} elseif (is_object($def)) { // 直接返回實例
return $def;
}
throw new Exception("$id 定義類型不支持");
}
/** 構建實例:反射解析構造函數,遞歸獲取依賴 */
protected function build($class, $params, $properties) {
list($ref, $deps) = $this->getDependencies($class);
$deps = $this->mergeParams($class, $params, $deps); // 合併參數
$deps = $this->resolveDependencies($deps, $ref); // 解析 Instance 佔位符
$obj = $ref->newInstanceArgs($deps);
foreach ($properties as $k => $v) $obj->$k = $v; // 屬性注入
return $obj;
}
/** 獲取構造函數依賴 */
protected function getDependencies($class) {
if (isset($this->reflections[$class])) {
return [$this->reflections[$class], $this->dependencies[$class]];
}
$ref = new ReflectionClass($class);
$ctor = $ref->getConstructor();
$deps = [];
if ($ctor) {
foreach ($ctor->getParameters() as $param) {
if ($param->isDefaultValueAvailable()) {
$deps[] = $param->getDefaultValue();
} else {
$type = $param->getClass();
$deps[] = Instance::getInstance($type ? $type->getName() : null);
}
}
}
$this->reflections[$class] = $ref;
$this->dependencies[$class] = $deps;
return [$ref, $deps];
}
/** 解析 Instance 佔位符為真實實例 */
protected function resolveDependencies($deps, $ref) {
foreach ($deps as $i => $dep) {
if ($dep instanceof Instance) {
if ($dep->id) {
$deps[$i] = $this->get($dep->id);
} elseif ($ref) {
$name = $ref->getConstructor()->getParameters()[$i]->getName();
throw new Exception("實例化 {$ref->getName()} 缺少參數: $name");
}
}
}
return $deps;
}
protected function mergeParams($id, $params, $deps = null) {
$base = $this->params[$id] ?? [];
$merged = $deps ?? $base;
foreach ($params as $k => $v) $merged[$k] = $v;
return $merged;
}
protected function normalizeDefinition($id, $def) {
if (empty($def)) return ['class' => $id];
if (is_string($def)) return ['class' => $def];
if (is_callable($def) || is_object($def)) return $def;
if (is_array($def)) {
if (!isset($def['class'])) $def['class'] = $id;
return $def;
}
throw new Exception("$id 定義格式錯誤");
}
}
/** 依賴標識佔位符 */
class Instance {
public $id;
public function __construct($id) { $this->id = $id; }
public static function getInstance($id) { return new self($id); }
}
Base.php (支持屬性注入)$name = $value; } }
b = $b; }
public function test() { $this->b->test(); }
}
// B.class.php
class B extends Base {
private $c;
public function __construct(C $c) { $this->c = $c; }
public function test() { return $this->c->test(); }
}
// C.class.php
class C extends Base {
public function test() { echo 'this is C!'; }
}
index.phpB->C 鏈路
$a = $container->get('A');
$a->test(); // 輸出: this is C!
// 進階用法:臨時覆蓋參數、屬性注入
$container->set('config', ['class' => 'stdClass', 'debug' => true, 'env' => 'dev']);
$svc = $container->get('A', [], ['logger' => $container->get('config')]);
// 註冊閉包工廠
$container->set('mailer', function($c, $params) {
return new PHPMailer($params['host'], $params['port']);
});
$mailer = $container->get('mailer', ['host'=>'smtp.ex.com','port'=>587]);
// 註冊單例實例
$container->set('db', new PDO('mysql:host=localhost;dbname=test', 'root', ''));
| 特性 | 本容器 | Pimple | PHP-DI | Illuminate/Container |
|---|---|---|---|---|
| 構造函數自動注入 | ✅ | ❌ | ✅ | ✅ |
| 屬性注入 | ✅ | ✅ | ✅ | ✅ |
| 閉包工廠 | ✅ | ✅ | ✅ | ✅ |
| 別名/接口綁定 | ✅ | ✅ | ✅ | ✅ |
| 單例/原型作用域 | 單例 | 可配置 | 可配置 | 可配置 |
| 標註驅動 (@Inject) | ❌ | ❌ | ✅ | ✅ |
| 性能 (C擴展/優化) | 純PHP | 純PHP | 純PHP | 高優化 |
set() 預置或 get() 臨時傳參。DI 容器通過反射+遞歸解析實現了「聲明依賴、自動組裝」,徹底消除了手動管理實例化順序的痛苦。本文實現的容器雖精簡但涵蓋核心功能,適合學習原理、中小項目或受限環境直接使用。理解其原理後,遷移至任意主流容器皆無障礙。