本文目录导读:

我来详细讲解 PHP 中的容器(Container)和依赖注入(Dependency Injection),这是现代 PHP 开发中最重要的设计模式之一。
依赖注入(Dependency Injection)
什么是依赖注入?
依赖注入是一种设计模式,用于实现控制反转(IoC),让对象的依赖从外部传入,而不是在内部创建。
为什么需要依赖注入?
不使用依赖注入的问题:
<?php
// 不好的示例 - 硬编码依赖
class UserService {
private $database;
public function __construct() {
// 在内部创建依赖,导致难以测试和维护
$this->database = new Database('localhost', 'user', 'pass');
}
public function getUser($id) {
return $this->database->query("SELECT * FROM users WHERE id = ?", [$id]);
}
}
使用依赖注入后:
<?php
// 好的示例 - 依赖注入
class UserService {
private $database;
public function __construct(DatabaseInterface $database) {
// 依赖从外部传入
$this->database = $database;
}
public function getUser($id) {
return $this->database->query("SELECT * FROM users WHERE id = ?", [$id]);
}
}
// 可以轻松替换不同的数据库实现
$mysqlDatabase = new MySQLDatabase();
$userService = new UserService($mysqlDatabase);
// 测试时可以使用 mock
$mockDatabase = new MockDatabase();
$userService = new UserService($mockDatabase);
依赖注入的三种形式
构造函数注入(最常用)
<?php
class Car {
private $engine;
private $wheels;
public function __construct(EngineInterface $engine, array $wheels) {
$this->engine = $engine;
$this->wheels = $wheels;
}
}
Setter 注入
<?php
class Mailer {
private $transport;
public function setTransport(TransportInterface $transport) {
$this->transport = $transport;
}
public function send($to, $subject, $body) {
// 使用 $this->transport 发送邮件
}
}
$mailer = new Mailer();
$mailer->setTransport(new SmtpTransport('smtp.gmail.com'));
接口注入
<?php
interface DatabaseInjector {
public function setDatabase(DatabaseInterface $database);
}
class UserRepository implements DatabaseInjector {
private $database;
public function setDatabase(DatabaseInterface $database) {
$this->database = $database;
}
}
服务容器(Service Container)
什么是服务容器?
服务容器是一个管理类依赖的工具,它负责:
- 存储对象实例(单例或每次创建)
- 管理依赖关系
- 自动解决依赖(自动注入)
简单容器实现
<?php
class Container {
private $bindings = [];
private $instances = [];
// 绑定接口到实现类
public function bind($abstract, $concrete) {
$this->bindings[$abstract] = $concrete;
}
// 绑定为单例
public function singleton($abstract, $concrete) {
$this->bind($abstract, $concrete);
$this->instances[$abstract] = null;
}
// 从容器中解析实例
public function resolve($abstract) {
// 如果已有实例,直接返回
if (isset($this->instances[$abstract]) && $this->instances[$abstract] !== null) {
return $this->instances[$abstract];
}
// 根据绑定创建实例
if (isset($this->bindings[$abstract])) {
$concrete = $this->bindings[$abstract];
if ($concrete instanceof Closure) {
// 如果绑定的是闭包,执行闭包
$object = $concrete($this);
} else {
// 否则通过反射创建实例
$object = $this->build($concrete);
}
// 如果是单例,保存实例
if (isset($this->instances[$abstract])) {
$this->instances[$abstract] = $object;
}
return $object;
}
// 没有绑定,尝试直接创建
return $this->build($abstract);
}
// 通过反射构建对象
private function build($class) {
$reflection = new ReflectionClass($class);
// 检查类是否可实例化
if (!$reflection->isInstantiable()) {
throw new Exception("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 && !$type->isBuiltin()) {
// 参数是类,递归解析
$dependencies[] = $this->resolve($type->getName());
} else {
// 处理默认值
if ($parameter->isDefaultValueAvailable()) {
$dependencies[] = $parameter->getDefaultValue();
} else {
throw new Exception("Cannot resolve dependency for parameter: {$parameter->getName()}");
}
}
}
// 创建实例
return $reflection->newInstanceArgs($dependencies);
}
}
使用容器
容器的基本使用
<?php // 创建容器 $container = new Container(); // 绑定接口到实现 $container->bind(DatabaseInterface::class, MySQLDatabase::class); $container->bind(UserRepository::class, UserRepositoryImplementation::class); // 绑定为单例 $container->singleton(LoggerInterface::class, FileLogger::class); // 解析服务 $userRepository = $container->resolve(UserRepository::class);
复杂依赖示例
<?php
// 定义接口
interface PaymentGateway {
public function charge($amount);
}
class StripeGateway implements PaymentGateway {
public function __construct($apiKey) {
$this->apiKey = $apiKey;
}
public function charge($amount) {
// 实现 Stripe 支付逻辑
}
}
class OrderService {
public function __construct(
private PaymentGateway $payment,
private UserRepository $users,
private LoggerInterface $logger
) {
}
public function processOrder(Order $order) {
$user = $this->users->find($order->userId);
$this->payment->charge($order->amount);
$this->logger->info("Order processed for user: {$user->id}");
}
}
// 绑定配置
$container->bind('payment.stripe.key', 'sk_test_xxx');
$container->bind(PaymentGateway::class, function($c) {
return new StripeGateway($c->resolve('payment.stripe.key'));
});
$container->bind(OrderService::class, OrderService::class);
$container->bind(LoggerInterface::class, function() {
return new MonologLogger('app', [new StreamHandler('php://stdout')]);
});
// 解析并使用
$orderService = $container->resolve(OrderService::class);
$orderService->processOrder($order);
现代 PHP 框架的实现
Laravel 的容器
<?php
// Laravel 容器示例
use Illuminate\Container\Container;
$container = Container::getInstance();
// 绑定
$container->bind('logger', function () {
return new Logger('app');
});
// 解析自动注入
$container->make(UserService::class);
// 依赖在 Laravel 中的使用
class EmailService {
public function __construct(Mailer $mailer, Logger $logger)
{
// Laravel 会自动注入这些依赖
}
}
Symfony 的依赖注入
<?php
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
$container = new ContainerBuilder();
// 定义服务
$container->register('database', 'App\Database\MySQLDatabase')
->addArgument('%database.host%')
->addArgument('%database.user%');
$container->register('user_repository', 'App\Repositories\UserRepository')
->addArgument(new Reference('database'))
->addArgument(new Reference('logger'));
$container->register('application', 'App\Application')
->addArgument(new Reference('user_repository'));
最佳实践
使用接口而不是具体类
<?php
// 不好的方式
class OrderService {
public function __construct(MySQLDatabase $db) {}
}
// 好的方式
class OrderService {
public function __construct(DatabaseInterface $db) {}
}
避免服务定位器反模式
<?php
// 不好的方式 - 服务定位器反模式
class OrderService {
public function __construct(Container $container) {
$this->db = $container->get('database');
}
}
// 好的方式 - 明确依赖
class OrderService {
public function __construct(DatabaseInterface $db) {
$this->db = $db;
}
}
合理使用单例
<?php
// 只对真正需要单例的服务使用 singleton
$container->singleton(DatabaseConnection::class, function ($c) {
return new DatabaseConnection(
$c->get('config.db.host'),
$c->get('config.db.database')
);
});
// 对普通的服务使用常规绑定
$container->bind(ProductRepository::class, function ($c) {
return new ProductRepository($c->get(DatabaseConnection::class));
});
常见问题和解决方案
循环依赖处理
<?php
// 循环依赖示例
class ServiceA {
public function __construct(ServiceB $b) {}
}
class ServiceB {
public function __construct(ServiceA $a) {}
}
// 解决方案:使用 setter 注入打破循环
class ServiceA {
private $b;
public function setB(ServiceB $b) {
$this->b = $b;
}
}
class ServiceB {
private $a;
public function setA(ServiceA $a) {
$this->a = $a;
}
}
使用第三方库
<?php
use DI\Container as PHPDIContainer;
$builder = new DI\ContainerBuilder();
$container = $builder->build();
// 自动依赖注入
$service = $container->get(UserService::class);
// 定义绑定
$container->set('database', new Database('localhost'));
实际应用示例
完整的应用示例
<?php
// Database.php
interface DatabaseInterface {
public function query($sql, $params = []);
}
class MySQLDatabase implements DatabaseInterface {
private $connection;
public function __construct($host, $database) {
// 创建连接
}
public function query($sql, $params = []) {
// 执行查询
}
}
// UserRepository.php
class UserRepository {
public function __construct(DatabaseInterface $db) {
$this->db = $db;
}
public function find($id) {
return $this->db->query("SELECT * FROM users WHERE id = ?", [$id]);
}
}
// UserService.php
class UserService {
public function __construct(UserRepository $users, LoggerInterface $logger) {
$this->users = $users;
$this->logger = $logger;
}
public function getUser($id) {
$this->logger->info("Fetching user {$id}");
return $this->users->find($id);
}
}
// container.php - 设置容器
$container = new Container();
// 配置绑定
$container->bind(DatabaseInterface::class, function($c) {
return new MySQLDatabase(
$c->get('db.host'),
$c->get('db.database')
);
});
$container->bind(UserRepository::class, UserRepository::class);
$container->bind(LoggerInterface::class, function($c) {
return new FileLogger(__DIR__ . '/logs/app.log');
});
// 使用
$userService = $container->resolve(UserService::class);
$user = $userService->getUser(123);
依赖注入和服务容器是构建可维护、可测试 PHP 应用的基础,理解并正确使用它们能显著提高代码质量。