依赖注入(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);
}
}下一节
继续学习:事件系统 — 了解观察者模式和事件驱动架构。