PHP 依赖注入容器从零实现:控制反转、手动注入与容器管理全解析

1. 引言

在 PHP 项目开发中,随着业务逻辑不断膨胀,类与类之间的依赖关系会变得越来越复杂。如果直接在类内部 new 出依赖对象,代码就会耦合在一起,难以测试和维护。依赖注入(Dependency Injection,DI)与控制反转(Inversion of Control,IoC)正是为了解决这一问题而生的设计思想。

本文将从最基础的概念讲起,逐步手写一个轻量级的依赖注入容器,覆盖手动注入、反射解析构造函数依赖、单例注册、自动装配以及循环依赖检测等核心能力。读完本文,你将彻底理解依赖注入容器的工作原理,并能独立实现一个可用的容器。

2. 控制反转与依赖注入基础概念

2.1 什么是控制反转

控制反转是一种设计原则,核心思想是:将对象创建和依赖管理的控制权从调用方手中反转给容器或框架。传统写法中,类自己决定依赖谁、何时创建依赖;而 IoC 模式下,类只声明自己需要什么,由外部容器负责提供。

2.2 什么是依赖注入

依赖注入是控制反转的一种具体实现方式,指通过构造函数、Setter 方法或属性等方式,将依赖对象传递给使用方。常见的注入方式有三种:

  • 构造函数注入:依赖在对象创建时通过构造参数传入,最推荐使用。
  • Setter 注入:通过公开的 setter 方法注入依赖,适合可选依赖。
  • 属性注入:直接给公开属性赋值,简单但破坏封装。

2.3 为什么需要依赖注入容器

当项目规模变大,手动管理依赖会变得繁琐。依赖注入容器负责统一管理对象的创建、配置和生命周期,让开发者只需关注业务逻辑,而无需关心依赖如何组装。

3. 手动注入 vs 容器管理

3.1 手动注入的写法

手动注入是指开发者自己创建依赖对象并传入。这种方式在小项目中足够,但存在明显问题:

php 复制代码
<?php

class Database
{
    public function query(string $sql): array
    {
        // 模拟查询
        return ['result' => $sql];
    }
}

class UserRepository
{
    private Database $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function findUser(int $id): array
    {
        return $this->db->query("SELECT * FROM users WHERE id = {$id}");
    }
}

// 手动创建依赖
$db = new Database();
$repo = new UserRepository($db);

手动注入的缺点:

  • 依赖较多时,创建代码冗长且重复。
  • 依赖关系变化时,需要修改所有创建点。
  • 难以统一管理单例和生命周期。

3.2 容器管理的优势

使用容器后,上述创建过程被简化为一行代码:

php 复制代码
$repo = $container->get(UserRepository::class);

容器自动完成依赖的解析与组装,开发者无需关心 Database 何时创建、如何传入。容器还支持单例注册、延迟加载和统一配置,让依赖管理变得集中且可控。

4. 从零实现一个依赖注入容器

4.1 容器的基础结构

我们先定义一个最简容器类,支持绑定和获取两个核心操作:

php 复制代码
<?php

class Container
{
    private array $bindings = [];

    /**
     * 注册一个绑定
     */
    public function bind(string $abstract, callable $factory): void
    {
        $this->bindings[$abstract] = $factory;
    }

    /**
     * 从容器中解析一个实例
     */
    public function get(string $abstract)
    {
        if (!isset($this->bindings[$abstract])) {
            throw new RuntimeException("No binding for {$abstract}");
        }

        $factory = $this->bindings[$abstract];
        return $factory($this);
    }
}

使用方式:

php 复制代码
$container = new Container();

$container->bind(Database::class, function () {
    return new Database();
});

$container->bind(UserRepository::class, function (Container $c) {
    return new UserRepository($c->get(Database::class));
});

$repo = $container->get(UserRepository::class);

4.2 支持单例注册

单例模式保证同一个类在容器中只被实例化一次。我们增加 singleton 方法和缓存机制:

php 复制代码
<?php

class Container
{
    private array $bindings = [];
    private array $singletons = [];

    public function bind(string $abstract, callable $factory): void
    {
        $this->bindings[$abstract] = $factory;
    }

    public function singleton(string $abstract, callable $factory): void
    {
        $this->bindings[$abstract] = $factory;
        $this->singletons[$abstract] = true;
    }

    public function get(string $abstract)
    {
        // 单例已实例化,直接返回缓存
        if (isset($this->singletons[$abstract]) && isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        if (!isset($this->bindings[$abstract])) {
            throw new RuntimeException("No binding for {$abstract}");
        }

        $factory = $this->bindings[$abstract];
        $instance = $factory($this);

        // 单例绑定则缓存实例
        if (isset($this->singletons[$abstract])) {
            $this->instances[$abstract] = $instance;
        }

        return $instance;
    }
}

4.3 反射解析构造函数依赖

手动注册每个类的工厂函数仍然繁琐。利用 PHP 的反射机制,容器可以自动分析类的构造函数参数,并递归解析每个参数对应的依赖,这就是自动装配。

php 复制代码
<?php

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

    public function bind(string $abstract, callable $factory): void
    {
        $this->bindings[$abstract] = $factory;
    }

    public function singleton(string $abstract, callable $factory): void
    {
        $this->bindings[$abstract] = $factory;
        $this->singletons[$abstract] = true;
    }

    public function get(string $abstract)
    {
        // 单例缓存
        if (isset($this->singletons[$abstract]) && isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        // 有显式绑定则使用工厂
        if (isset($this->bindings[$abstract])) {
            $factory = $this->bindings[$abstract];
            $instance = $factory($this);
        } else {
            // 否则通过反射自动装配
            $instance = $this->resolve($abstract);
        }

        if (isset($this->singletons[$abstract])) {
            $this->instances[$abstract] = $instance;
        }

        return $instance;
    }

    /**
     * 通过反射解析类的构造函数依赖
     */
    private function resolve(string $class)
    {
        $reflection = new ReflectionClass($class);

        if (!$reflection->isInstantiable()) {
            throw new RuntimeException("Class {$class} is not instantiable");
        }

        $constructor = $reflection->getConstructor();

        // 没有构造函数,直接实例化
        if ($constructor === null) {
            return new $class();
        }

        $parameters = $constructor->getParameters();
        $dependencies = [];

        foreach ($parameters as $parameter) {
            $type = $parameter->getType();

            // 参数有类类型提示,递归解析
            if ($type instanceof ReflectionNamedType && !$type->isBuiltin()) {
                $dependencies[] = $this->get($type->getName());
            } else {
                // 基本类型参数,尝试取默认值
                if ($parameter->isDefaultValueAvailable()) {
                    $dependencies[] = $parameter->getDefaultValue();
                } else {
                    throw new RuntimeException(
                        "Cannot resolve parameter \${$parameter->getName()} of {$class}"
                    );
                }
            }
        }

        return $reflection->newInstanceArgs($dependencies);
    }
}

现在无需手动注册 UserRepository,容器会自动解析:

php 复制代码
$container = new Container();
$container->singleton(Database::class, function () {
    return new Database();
});

// 自动装配,无需注册 UserRepository
$repo = $container->get(UserRepository::class);

5. 循环依赖检测

5.1 什么是循环依赖

当两个类互相依赖时,就会产生循环依赖。例如 A 的构造函数需要 B,而 B 的构造函数又需要 A。如果不加处理,容器会陷入无限递归,最终导致栈溢出。

5.2 检测与报错

我们在容器中维护一个解析栈,当发现某个类正在解析中又被重复请求时,立即抛出异常:

php 复制代码
<?php

class Container
{
    private array $bindings = [];
    private array $singletons = [];
    private array $instances = [];
    private array $resolving = [];

    public function get(string $abstract)
    {
        // 检测循环依赖
        if (isset($this->resolving[$abstract])) {
            $chain = implode(' -> ', array_keys($this->resolving)) . ' -> ' . $abstract;
            throw new RuntimeException("Circular dependency detected: {$chain}");
        }

        if (isset($this->singletons[$abstract]) && isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        $this->resolving[$abstract] = true;

        try {
            if (isset($this->bindings[$abstract])) {
                $factory = $this->bindings[$abstract];
                $instance = $factory($this);
            } else {
                $instance = $this->resolve($abstract);
            }
        } finally {
            unset($this->resolving[$abstract]);
        }

        if (isset($this->singletons[$abstract])) {
            $this->instances[$abstract] = $instance;
        }

        return $instance;
    }

    private function resolve(string $class)
    {
        $reflection = new ReflectionClass($class);

        if (!$reflection->isInstantiable()) {
            throw new RuntimeException("Class {$class} is not instantiable");
        }

        $constructor = $reflection->getConstructor();

        if ($constructor === null) {
            return new $class();
        }

        $parameters = $constructor->getParameters();
        $dependencies = [];

        foreach ($parameters as $parameter) {
            $type = $parameter->getType();

            if ($type instanceof ReflectionNamedType && !$type->isBuiltin()) {
                $dependencies[] = $this->get($type->getName());
            } else {
                if ($parameter->isDefaultValueAvailable()) {
                    $dependencies[] = $parameter->getDefaultValue();
                } else {
                    throw new RuntimeException(
                        "Cannot resolve parameter \${$parameter->getName()} of {$class}"
                    );
                }
            }
        }

        return $reflection->newInstanceArgs($dependencies);
    }
}

测试循环依赖检测:

php 复制代码
class A
{
    public function __construct(B $b) {}
}

class B
{
    public function __construct(A $a) {}
}

$container = new Container();

try {
    $container->get(A::class);
} catch (RuntimeException $e) {
    echo $e->getMessage(); // Circular dependency detected: A -> B -> A
}

6. 完整容器示例与测试

下面给出一个整合了绑定、单例、自动装配和循环检测的完整容器,并附上测试用例:

php 复制代码
<?php

class Container
{
    private array $bindings = [];
    private array $singletons = [];
    private array $instances = [];
    private array $resolving = [];

    public function bind(string $abstract, callable $factory): void
    {
        $this->bindings[$abstract] = $factory;
    }

    public function singleton(string $abstract, callable $factory): void
    {
        $this->bindings[$abstract] = $factory;
        $this->singletons[$abstract] = true;
    }

    public function get(string $abstract)
    {
        if (isset($this->resolving[$abstract])) {
            $chain = implode(' -> ', array_keys($this->resolving)) . ' -> ' . $abstract;
            throw new RuntimeException("Circular dependency detected: {$chain}");
        }

        if (isset($this->singletons[$abstract]) && isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        $this->resolving[$abstract] = true;

        try {
            if (isset($this->bindings[$abstract])) {
                $instance = $this->bindings[$abstract]($this);
            } else {
                $instance = $this->resolve($abstract);
            }
        } finally {
            unset($this->resolving[$abstract]);
        }

        if (isset($this->singletons[$abstract])) {
            $this->instances[$abstract] = $instance;
        }

        return $instance;
    }

    private function resolve(string $class)
    {
        $reflection = new ReflectionClass($class);

        if (!$reflection->isInstantiable()) {
            throw new RuntimeException("Class {$class} is not instantiable");
        }

        $constructor = $reflection->getConstructor();

        if ($constructor === null) {
            return new $class();
        }

        $dependencies = [];

        foreach ($constructor->getParameters() as $parameter) {
            $type = $parameter->getType();

            if ($type instanceof ReflectionNamedType && !$type->isBuiltin()) {
                $dependencies[] = $this->get($type->getName());
            } else {
                if ($parameter->isDefaultValueAvailable()) {
                    $dependencies[] = $parameter->getDefaultValue();
                } else {
                    throw new RuntimeException(
                        "Cannot resolve parameter \${$parameter->getName()} of {$class}"
                    );
                }
            }
        }

        return $reflection->newInstanceArgs($dependencies);
    }
}

// ---------- 测试用例 ----------

class Logger
{
    public function log(string $msg): void
    {
        echo "[LOG] {$msg}\n";
    }
}

class Mailer
{
    private Logger $logger;

    public function __construct(Logger $logger)
    {
        $this->logger = $logger;
    }

    public function send(string $to): void
    {
        $this->logger->log("Sending mail to {$to}");
    }
}

$container = new Container();

// 注册 Logger 为单例
$container->singleton(Logger::class, function () {
    return new Logger();
});

// Mailer 无需注册,自动装配
$mailer1 = $container->get(Mailer::class);
$mailer2 = $container->get(Mailer::class);

// 验证单例:两次获取的 Logger 是同一个实例
$logger1 = $container->get(Logger::class);
$logger2 = $container->get(Logger::class);
var_dump($logger1 === $logger2); // bool(true)

$mailer1->send('user@example.com');

7. 总结

本文从控制反转与依赖注入的基本概念出发,逐步实现了一个功能完整的 PHP 依赖注入容器。核心要点回顾:

  • 控制反转将依赖创建的控制权交给容器,降低类间耦合。
  • 手动注入适合小项目,但依赖复杂时维护成本高。
  • 容器管理通过绑定、单例和自动装配,让依赖组装集中且可复用。
  • 反射自动装配 利用 ReflectionClass 解析构造函数参数,递归解析依赖。
  • 循环依赖检测通过解析栈标记正在解析的类,遇到重复请求立即抛出异常。

实际项目中,你可以直接使用成熟的容器实现(如 PHP-DI、Laravel 的容器),但理解其底层原理,能帮助你在遇到复杂依赖场景时更快定位问题,也能让你写出更优雅、可维护的代码。

相关推荐
运维行者_3 小时前
PHP性能监控怎么做?从响应时间到慢函数的6个关键指标
运维·服务器·开发语言·网络·支持向量机·php·接口隔离原则
天空鸟_时光不老3 小时前
01-我不转Python把AI塞进Java里
java·人工智能·spring boot·后端·spring·spring cloud·架构
hsfxuebao3 小时前
Hermes Agent能力篇:会话、工具、MCP、记忆
人工智能·后端
vx_Biye_Design3 小时前
flask学生课程笔记共享系统29026-计算机课程设计、毕业设计
java·javascript·spring boot·后端·elasticsearch·flask·课程设计
卷无止境3 小时前
用Rust重写:三条清晰的收益曲线
后端·rust
深入云栈4 小时前
Netty 4.2.x 源码深度解析 (十七):Channel 底层读写 —— Unsafe 的 I/O 操作内核
java·后端·架构
Do_It_Today4 小时前
php xml 入门学习资料
开发语言·php
DS小龙哥4 小时前
STM32+华为云IoT+Qt:手把手教你打造跨平台智能家居控制系统
后端