傳統寫法在類內部 new 依賴,導致高耦合、難測試、難替換。依賴注入(DI)將「建立依賴」的職責外移,由呼叫端或容器提供,實現鬆耦合。
class SomeComponent {
public function someDbTask() {
$conn = new Connection(['host'=>'localhost', ...]); // 死綁定
}
}
❌ 無法替換資料庫類型、參數、測試用 Mock
class SomeComponent {
protected $conn;
public function setConnection(Connection $conn) { $this->conn = $conn; }
}
$comp = new SomeComponent();
$comp->setConnection(new Connection($config));
✅ 可外部替換,但每次使用都要手動組裝,依賴多時極其繁瑣
class Registry {
public static function getSharedConnection() {
static $conn = null;
return $conn ??= new Connection($config);
}
}
$comp->setConnection(Registry::getSharedConnection());
✅ 解決單例共享,但引入全域狀態,隱式依賴難追蹤
class SomeComponent {
public static function factory() {
return new self(
new Connection(), new Session(), new FileSystem(), ...
);
}
}
✅ 封裝建立邏輯,但工廠本身成為「上帝類」,違反單一職責
class SomeComponent {
protected $di;
public function __construct($di) { $this->di = $di; }
public function someDbTask() {
$conn = $this->di->get('db'); // 每次新實例
$conn = $this->di->getShared('db'); // 共享單例
}
}
$di = new Phalcon\DI();
$di->set('db', fn() => new Connection($config));
$di->set('filter', fn() => new Filter());
$comp = new SomeComponent($di);
| 特性 | 說明 |
|---|---|
| 延遲載入 | 只有真正呼叫 get() 時才實例化,節省資源 |
| 單例管理 | getShared() 確保同一請求內複用實例 |
| 參數覆蓋 | 呼叫時動態傳參:$di->get('comp', [$param1, $param2]) |
| 多種註冊方式 | 類名、閉包、實例、陣列定義,靈活適應場景 |
| 解耦測試 | 測試時輕鬆替換為 Mock 物件 |
// 1. 類名(簡單、無參數)
$di->set('request', 'Phalcon\Http\Request');
// 2. 閉包(延遲載入、可注入參數、最推薦)
$di->set('db', function() {
return new Phalcon\Db\Adapter\Pdo\Mysql([
'host' => 'localhost', 'username' => 'root',
'password' => 'secret', 'dbname' => 'blog'
]);
});
// 3. 直接實例(立即建立、無延遲)
$di->set('request', new Phalcon\Http\Request());
// 4. 陣列定義(可後續修改參數)
$di->set('db', [
'className' => 'Phalcon\Db\Adapter\Pdo\Mysql',
'parameters' => ['host' => 'localhost', ...]
]);
// 後續可動態修改:
$di->setParameter('db', 0, ['host' => 'newhost']);
// 獲取服務
$request = $di->get('request'); // 新實例
$request = $di->getShared('request'); // 共享單例
$request = $di->getRequest(); // 魔術方法別名
⚠️ 反模式警示:在類中直接呼叫 $di->get('service') 屬於「服務定位器」模式,隱藏了真實依賴。
✅ 最佳實踐:建構子注入宣告依賴,容器自動解析注入(如 Laravel、Symfony 現代框架)。
依賴注入不只是「傳參數」,而是一套管理物件生命週期、解耦業務邏輯與基礎設施、支援測試與擴展的架構模式。從手動注入到 DI 容器,是從「手工組裝」進化到「自動化裝配線」的必經之路。