Skip to content

依赖注入(IoC 容器)

依赖注入(Dependency Injection,DI)是一种设计模式,它将对象的依赖关系从内部创建转移到外部注入,从而实现松耦合。IoC(Inversion of Control,控制反转)容器是 DI 的具体实现,负责管理对象的创建和依赖关系的解析。本节将深入讲解依赖注入的概念、IoC 容器的实现原理和各种注入方式。

前置知识

阅读本节前,建议先了解:

基础概念

什么是依赖注入

依赖注入的核心思想是:不要在类内部创建依赖对象,而是从外部传入。

php
<?php
declare(strict_types=1);

// ❌ 硬编码依赖(紧耦合)
class UserService
{
    private MySQLDatabase $db;

    public function __construct()
    {
        $this->db = new MySQLDatabase('localhost', 'root', 'password');
    }

    // 直接依赖 MySQLDatabase,无法替换为其他数据库
}

// ✅ 依赖注入(松耦合)
class UserService
{
    public function __construct(
        private readonly DatabaseInterface $db
    ) {}
}

// 外部传入依赖
$db = new PostgreSQLDatabase('localhost', 'root', 'password');
$service = new UserService($db);

注入方式

方式说明推荐度
构造函数注入通过构造函数参数注入最推荐
Setter 注入通过 setter 方法注入可选依赖
属性注入直接注入到公共属性不推荐
接口注入通过实现特定接口注入少用

详细说明

1. 构造函数注入

php
<?php
declare(strict_types=1);

namespace App\Services;

use App\Repositories\UserRepositoryInterface;
use Psr\Log\LoggerInterface;
use App\Cache\CacheInterface;

class UserService
{
    public function __construct(
        private readonly UserRepositoryInterface $userRepository,
        private readonly CacheInterface $cache,
        private readonly LoggerInterface $logger,
    ) {}

    public function findUser(int $id): ?User
    {
        $this->logger->info('Finding user', ['id' => $id]);

        $cached = $this->cache->get("user:{$id}");
        if ($cached !== null) {
            return $cached;
        }

        $user = $this->userRepository->findById($id);
        if ($user !== null) {
            $this->cache->set("user:{$id}", $user, 3600);
        }

        return $user;
    }
}

2. Setter 注入

php
<?php
declare(strict_types=1);

namespace App\Services;

use Psr\Log\LoggerInterface;

class UserService
{
    private LoggerInterface $logger;
    private UserRepositoryInterface $userRepository;

    // 必需依赖:构造函数注入
    public function __construct(
        UserRepositoryInterface $userRepository
    ) {
        $this->userRepository = $userRepository;
    }

    // 可选依赖:Setter 注入
    public function setLogger(LoggerInterface $logger): void
    {
        $this->logger = $logger;
    }

    public function findUser(int $id): ?User
    {
        if (isset($this->logger)) {
            $this->logger->info('Finding user', ['id' => $id]);
        }
        return $this->userRepository->findById($id);
    }
}

3. IoC 容器实现

php
<?php
declare(strict_types=1);

/**
 * 简易 IoC 容器实现
 */
class Container implements \Psr\Container\ContainerInterface
{
    /** @var array<string, callable|object> 绑定定义 */
    private array $bindings = [];

    /** @var array<string, object> 已解析的实例(单例) */
    private array $instances = [];

    /**
     * 绑定接口到实现
     */
    public function bind(string $abstract, callable|string $concrete, bool $singleton = false): void
    {
        $this->bindings[$abstract] = [
            'concrete' => $concrete,
            'singleton' => $singleton,
        ];
    }

    /**
     * 绑定单例
     */
    public function singleton(string $abstract, callable|string $concrete): void
    {
        $this->bind($abstract, $concrete, true);
    }

    /**
     * 解析并返回实例
     */
    public function get(string $id): object
    {
        if (isset($this->instances[$id])) {
            return $this->instances[$id];
        }

        if (!isset($this->bindings[$id])) {
            if (class_exists($id)) {
                return $this->resolve($id);
            }
            throw new \RuntimeException("No binding found for: {$id}");
        }

        $concrete = $this->bindings[$id]['concrete'];
        $singleton = $this->bindings[$id]['singleton'];

        if (is_callable($concrete)) {
            $instance = $concrete($this);
        } else {
            $instance = $this->resolve($concrete);
        }

        if ($singleton) {
            $this->instances[$id] = $instance;
        }

        return $instance;
    }

    /**
     * 检查是否已绑定
     */
    public function has(string $id): bool
    {
        return isset($this->bindings[$id]) || isset($this->instances[$id]) || class_exists($id);
    }

    /**
     * 自动解析类依赖
     */
    private function resolve(string $className): object
    {
        $reflector = new ReflectionClass($className);

        if (!$reflector->isInstantiable()) {
            throw new \RuntimeException("Class {$className} is not instantiable");
        }

        $constructor = $reflector->getConstructor();

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

        $parameters = $constructor->getParameters();
        $dependencies = $this->resolveDependencies($parameters);

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

    /**
     * 解析方法参数的依赖
     */
    private function resolveDependencies(array $parameters): array
    {
        $deps = [];

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

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

        return $deps;
    }
}

4. 使用容器

php
<?php
declare(strict_types=1);

$container = new Container();

// 绑定接口到实现
$container->bind(DatabaseInterface::class, MySQLDatabase::class);
$container->bind(LoggerInterface::class, MonologLogger::class);
$container->bind(UserRepositoryInterface::class, MySQLUserRepository::class);

// 绑定单例
$container->singleton(CacheInterface::class, RedisCache::class);

// 自动解析
$userService = $container->get(UserService::class);
// 容器自动注入所有依赖:
// - UserRepositoryInterface → MySQLUserRepository
// - CacheInterface → RedisCache(单例)
// - LoggerInterface → MonologLogger

// 直接从容器获取
$db = $container->get(DatabaseInterface::class);

5. 自动绑定(Autowiring)

php
<?php
declare(strict_types=1);

// Autowiring:容器通过反射自动解析依赖
// 无需手动 bind

interface MailerInterface {}

class SmtpMailer implements MailerInterface {}
class SendmailMailer implements MailerInterface {}

class NotificationService
{
    public function __construct(
        private readonly MailerInterface $mailer
    ) {}
}

// 容器使用策略:
// 1. 如果 MailerInterface 有绑定 → 使用绑定的实现
// 2. 如果没有绑定但有具体实现类 → 创建该类
$container->bind(MailerInterface::class, SmtpMailer::class);

$service = $container->get(NotificationService::class);
// 自动注入 SmtpMailer

实战示例

2. 工厂模式与容器

php
<?php
declare(strict_types=1);

interface UserRepositoryInterface
{
    public function findById(int $id): ?User;
}

class MySQLUserRepository implements UserRepositoryInterface {}
class RedisUserRepository implements UserRepositoryInterface {}

class UserRepositoryFactory
{
    public function __construct(
        private readonly Container $container
    ) {}

    public function create(string $driver): UserRepositoryInterface
    {
        return match ($driver) {
            'mysql' => $this->container->get(MySQLUserRepository::class),
            'redis' => $this->container->get(RedisUserRepository::class),
            default => throw new \InvalidArgumentException("Unknown driver: {$driver}"),
        };
    }
}

场景三:Laravel 中的依赖注入

php
<?php
// Laravel 控制器方法注入
namespace App\Http\Controllers;

use App\Services\PaymentService;
use Illuminate\Http\Request;
use Psr\Log\LoggerInterface;

class OrderController extends Controller
{
    // 构造函数注入
    public function __construct(
        private readonly PaymentService $paymentService,
    ) {}

    // 方法注入
    public function store(
        Request $request,
        LoggerInterface $logger,
    ): JsonResponse {
        $logger->info('Creating order');

        $result = $this->paymentService->process(
            amount: $request->float('amount'),
            userId: $request->integer('user_id'),
        );

        return response()->json($result);
    }
}

场景四:Symfony 中的自动注入

yaml
# config/services.yaml
services:
    _defaults:
        autowire: true
        autoconfigure: true

    App\:
        resource: '../src/'
        exclude:
            - '../src/Entity/'
            - '../src/Kernel.php'
php
<?php
// Symfony 自动注入
namespace App\Service;

use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;

class OrderService
{
    // Symfony 自动通过类型提示注入
    public function __construct(
        private readonly EntityManagerInterface $em,
        private readonly LoggerInterface $logger,
    ) {}

    public function createOrder(array $data): Order
    {
        $this->logger->info('Creating order');
        // ...
    }
}

注意事项

1. 服务定位器反模式

php
<?php
// ❌ 服务定位器反模式:在类中使用容器
class BadService
{
    public function __construct(
        private readonly Container $container
    ) {}

    public function doSomething(): void
    {
        $db = $this->container->get(DatabaseInterface::class);
        $logger = $this->container->get(LoggerInterface::class);
    }
}

// ✅ 正确方式:通过构造函数注入具体依赖
class GoodService
{
    public function __construct(
        private readonly DatabaseInterface $db,
        private readonly LoggerInterface $logger
    ) {}
}

2. 避免过度注入

php
<?php
// ❌ 过多的依赖(可能是上帝类)
class GodService
{
    public function __construct(
        private readonly DatabaseInterface $db,
        private readonly CacheInterface $cache,
        private readonly LoggerInterface $logger,
        private readonly MailerInterface $mailer,
        private readonly QueueInterface $queue,
        private readonly EventDispatcherInterface $dispatcher,
        private readonly FileSystemInterface $fs,
        private readonly HttpClientInterface $http,
    ) {}
}

// ✅ 拆分为多个小类
class UserService
{
    public function __construct(
        private readonly DatabaseInterface $db,
        private readonly CacheInterface $cache,
    ) {}
}

最佳实践

1. 面向接口编程

php
<?php
// ✅ 依赖接口
class UserController
{
    public function __construct(
        private readonly UserRepositoryInterface $userRepository
    ) {}
}

// ❌ 依赖具体实现
class UserController
{
    public function __construct(
        private readonly MySQLUserRepository $userRepository
    ) {}
}

2. 使用 PSR-11 容器

php
<?php
// 使用 PSR-11 标准的容器接口
use Psr\Container\ContainerInterface;

class MyApplication
{
    public function __construct(
        private readonly ContainerInterface $container
    ) {}

    public function run(): void
    {
        $service = $this->container->get(MyService::class);
    }
}

下一节

继续学习:事件系统 — 了解观察者模式和事件驱动架构。

参考链接