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

深度理解 PHP 依賴注入(DI)與控制反轉(IoC)

     編輯:PHP 綜合知識

核心概念辨析

維度依賴注入 (DI)控制反轉 (IoC)
視角應用程序視角容器視角
描述應用程序依賴容器創建並注入所需外部資源容器控制應用程序,反向向應用注入資源
關鍵點「注入」動作「控制權」轉移

本質上是同一體系的兩種描述方式:IoC 是設計原則,DI 是實現模式。

三大參與者

  1. 消費者:需要外部資源的類(如 OrderService 需要 PaymentGateway)。
  2. 提供者:被依賴的資源(接口實現、配置、連接等)。
  3. 容器:負責實例化、裝配、管理生命週期的框架組件。

為什麼需要 DI/IoC?

// 傳統強耦合寫法
class OrderService {
    private $payment;
    public function __construct() {
        $this->payment = new AlipayGateway(); // 死綁定具體實現
    }
}

痛點:難以測試(無法 Mock)、難以擴展(換支付需改代碼)、違反開閉原則。

三種主流注入模式

1. 構造器注入(推薦)

class OrderService {
    private $payment;
    public function __construct(PaymentGatewayInterface $payment) {
        $this->payment = $payment;
    }
}
// 容器自動解析構造函數參數類型提示,注入綁定的實現

優點:依賴不可變、必須依賴、易於單元測試。

2. Setter 注入

class OrderService {
    private $payment;
    public function setPayment(PaymentGatewayInterface $payment) {
        $this->payment = $payment;
    }
}
// 適用於可選依賴、運行時動態切換

3. 接口注入

類實現特定接口(如 InjectableInterface),容器調用接口方法注入。較少用,耦合度較高。

容器實現核心:綁定與解析

class Container {
    private $bindings = [];
    private $instances = [];

    // 綁定接口到實現(支持閉包延遲實例化)
    public function bind(string $abstract, $concrete = null, bool $shared = false) {
        $this->bindings[$abstract] = ['concrete' => $concrete ?? $abstract, 'shared' => $shared];
    }

    // 別名綁定
    public function alias(string $abstract, string $alias) {
        $this->bind($alias, $abstract);
    }

    // 解析實例
    public function make(string $abstract, array $parameters = []) {
        if (isset($this->instances[$abstract])) return $this->instances[$abstract];

        $binding = $this->bindings[$abstract] ?? ['concrete' => $abstract, 'shared' => false];
        $concrete = $binding['concrete'];

        // 閉包工廠
        if ($concrete instanceof Closure) {
            $instance = $concrete($this, $parameters);
        } else {
            $instance = $this->build($concrete, $parameters);
        }

        if ($binding['shared']) $this->instances[$abstract] = $instance;
        return $instance;
    }

    private function build(string $concrete, array $parameters) {
        $reflector = new ReflectionClass($concrete);
        if (!$reflector->isInstantiable()) throw new Exception("Class $concrete not instantiable");

        $constructor = $reflector->getConstructor();
        if (!$constructor) return new $concrete;

        $dependencies = $this->resolveDependencies($constructor->getParameters(), $parameters);
        return $reflector->newInstanceArgs($dependencies);
    }

    private function resolveDependencies(array $params, array $given) {
        return array_map(function($param) use ($given) {
            if (isset($given[$param->name])) return $given[$param->name];
            if ($param->getClass()) return $this->make($param->getClass()->name);
            if ($param->isDefaultValueAvailable()) return $param->getDefaultValue();
            throw new Exception("Unresolvable dependency: {$param->name}");
        }, $params);
    }
}

// 使用示例
$container = new Container();
$container->bind(PaymentGatewayInterface::class, AlipayGateway::class, true); // 單例
$container->bind(OrderService::class); // 自動解析構造函數

$orderService = $container->make(OrderService::class);

PSR-11 容器接口標準

現代框架(Laravel、Symfony、Yii、ThinkPHP)均實現 Psr\Container\ContainerInterface:

interface ContainerInterface {
    public function get(string $id);
    public function has(string $id): bool;
}

遵循標準意味著組件可在不同框架間復用,生態互通。

實戰最佳實踐

  1. 依賴接口而非實現:業務代碼只依賴 Interface,綁定在啟動階段集中配置。
  2. 構造器注入為主:強制依賴走構造器,可選依賴用 Setter 或方法注入。
  3. 避免服務定位器模式:不要在類中直接調用 $container->make(),這會隱藏依賴關係。
  4. 區分單例與原型:無狀態服務(Gateway、Repository)用單例;有狀態對象(Form、DTO)用原型。
  5. 自動裝配:利用 PHP 反射自動解析構造函數參數,減少手動綁定代碼。

總結

DI/IoC 不是為了「高大上」而引入,而是為了解決耦合與可測試性這兩個軟件工程核心問題。掌握容器原理,能讓你在任何框架中游刃有餘地管理依賴關係。

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