首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 依賴注入容器實戰:解耦類依賴、實現自動加載與實例化

     編輯:PHP 相關知識

PHP 依賴注入容器實戰:解耦類依賴、實現自動加載與實例化

傳統開發中,類之間強耦合導致加載順序極其敏感:A 依賴 B,B 依賴 C... 新人接手需花費大量時間理清實例化鏈路。本文從依賴倒置原則 (DIP) 切入,手把手實現一個輕量級 DI 容器,支持構造函數注入、反射自動解析依賴、參數合併、屬性注入、別名映射、閉包工廠、實例緩存,徹底擺脫手動 include 与实例化顺序烦恼。

核心設計思想

  1. 依賴倒置:高層模塊定義接口,低層實現接口,雙方依賴抽象。
  2. 控制反轉 (IoC):對象創建控制權從業務代碼移交給容器。
  3. 反射自動解析:利用 ReflectionClass 讀取構造函數參數類型提示,自動從容器獲取依賴實例。
  4. 單例緩存:同一標識符在容器生命周期內只實例化一次。

容器核心代碼 Container.php

params[$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.php

B->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', ''));

容器能力對比表

特性本容器PimplePHP-DIIlluminate/Container
構造函數自動注入✅❌✅✅
屬性注入✅✅✅✅
閉包工廠✅✅✅✅
別名/接口綁定✅✅✅✅
單例/原型作用域單例可配置可配置可配置
標註驅動 (@Inject)❌❌✅✅
性能 (C擴展/優化)純PHP純PHP純PHP高優化

避坑指南與最佳實踐

  1. 構造函數必須聲明類型提示,否則容器無法推斷依賴類型。
  2. 標量參數 (string/int/bool) 無法自動注入,需通過 set() 預置或 get() 臨時傳參。
  3. 循環依賴會導致無限遞歸,設計時嚴禁 A->B->A,或使用 setter 注入打破循環。
  4. 生產環境建議使用成熟容器 (PHP-DI, Symfony DI, Laravel Container),擁有編譯優化、代理懶加載、標註支持等企業級特性。
  5. 容器不應作為 Service Locator 到處傳遞,僅在啟動階段組裝對象圖,業務代碼依賴具體接口而非容器。

總結

DI 容器通過反射+遞歸解析實現了「聲明依賴、自動組裝」,徹底消除了手動管理實例化順序的痛苦。本文實現的容器雖精簡但涵蓋核心功能,適合學習原理、中小項目或受限環境直接使用。理解其原理後,遷移至任意主流容器皆無障礙。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved