PHP 容器与依赖注入

wen PHP项目 2

本文目录导读:

PHP 容器与依赖注入

  1. 依赖注入(Dependency Injection)
  2. 依赖注入的三种形式
  3. 服务容器(Service Container)
  4. 使用容器
  5. 现代 PHP 框架的实现
  6. 最佳实践
  7. 常见问题和解决方案
  8. 实际应用示例

我来详细讲解 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 应用的基础,理解并正确使用它们能显著提高代码质量。

抱歉,评论功能暂时关闭!