PHP依赖注入容器原理与实现
依赖注入是现代框架的核心。它让类之间的耦合降低,代码更容易测试和维护。今天从零实现一个依赖注入容器,理解它的工作原理。
依赖注入的基本思想是:一个类需要的依赖由外部传入,而不是自己在内部创建。
```php
// 不用依赖注入
class UserController
{
private UserRepository $repository;
public function __construct()
{
// 在内部创建依赖,紧耦合
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$this->repository = new UserRepository($pdo);
}
}
// 用依赖注入
class UserControllerDI
{
public function __construct(
private UserRepository $repository // 外部传入,松耦合
) {}
}
// 外部创建依赖
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$repository = new UserRepository($pdo);
$controller = new UserControllerDI($repository);
?>
```
容器是依赖注入的管理器。它可以自动解析依赖,递归地创建所有需要的对象。
```php
class Container
{
private array $bindings = [];
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] = function () use ($factory) {
static $instance = null;
if ($instance === null) {
$instance = $factory($this);
}
return $instance;
};
}
public function instance(string $abstract, object $instance): void
{
$this->instances[$abstract] = $instance;
}
public function make(string $abstract): mixed
{
// 如果有实例,直接返回
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
// 如果有工厂,调用工厂
if (isset($this->bindings[$abstract])) {
$object = ($this->bindings[$abstract])($this);
return $object;
}
// 否则自动解析
return $this->autoResolve($abstract);
}
public function has(string $abstract): bool
{
return isset($this->bindings[$abstract]) || isset($this->instances[$abstract]);
}
public function remove(string $abstract): void
{
unset($this->instances[$abstract], $this->bindings[$abstract]);
}
private function autoResolve(string $class): object
{
if (!class_exists($class)) {
throw new RuntimeException("无法解析: $class");
}
$reflection = new ReflectionClass($class);
$constructor = $reflection->getConstructor();
if ($constructor === null) {
return $reflection->newInstance();
}
$dependencies = [];
foreach ($constructor->getParameters() as $param) {
$type = $param->getType();
if ($type === null) {
// 没有类型声明,用默认值
if ($param->isDefaultValueAvailable()) {
$dependencies[] = $param->getDefaultValue();
} else {
throw new RuntimeException("无法解析参数: {$param->getName()}");
}
} elseif ($type->isBuiltin()) {
// 内置类型,用默认值
if ($param->isDefaultValueAvailable()) {
$dependencies[] = $param->getDefaultValue();
} else {
throw new RuntimeException("无法解析内置类型参数: {$param->getName()}");
}
} else {
// 类类型,递归解析
$typeName = $type->getName();
$dependencies[] = $this->make($typeName);
}
}
return $reflection->newInstanceArgs($dependencies);
}
public function call(callable $callable, array $params = []): mixed
{
if (is_array($callable)) {
$ref = new ReflectionMethod($callable[0], $callable[1]);
} elseif (is_object($callable) && !$callable instanceof Closure) {
$ref = new ReflectionMethod($callable, '__invoke');
} else {
$ref = new ReflectionFunction($callable);
}
$args = [];
foreach ($ref->getParameters() as $param) {
$name = $param->getName();
if (isset($params[$name])) {
$args[] = $params[$name];
} elseif ($param->getType() && !$param->getType()->isBuiltin()) {
$args[] = $this->make($param->getType()->getName());
} elseif ($param->isDefaultValueAvailable()) {
$args[] = $param->getDefaultValue();
} else {
throw new RuntimeException("无法解析参数: $name");
}
}
return $ref->invokeArgs($args);
}
}
?>
```
接口绑定让容器可以根据接口解析具体的实现类:
```php
interface LoggerInterface
{
public function log(string $level, string $message): void;
}
class FileLogger implements LoggerInterface
{
public function log(string $level, string $message): void
{
$entry = sprintf("[%s] %s: %s\n", date('Y-m-d H:i:s'), $level, $message);
file_put_contents('/tmp/app.log', $entry, FILE_APPEND);
}
}
class DatabaseLogger implements LoggerInterface
{
private PDO $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
public function log(string $level, string $message): void
{
$stmt = $this->pdo->prepare(
"INSERT INTO logs (level, message, created_at) VALUES (?, ?, NOW())"
);
$stmt->execute([$level, $message]);
}
}
class UserService
{
public function __construct(
private LoggerInterface $logger
) {}
public function register(string $name, string $email): void
{
// 业务逻辑...
$this->logger->log('INFO', "用户注册: $name ($email)");
}
}
// 使用容器
$container = new Container();
// 绑定接口到具体实现
$container->bind(LoggerInterface::class, function ($c) {
return new FileLogger();
});
// 自动解析UserService
$service = $container->make(UserService::class);
$service->register('张三', 'zhangsan@test.com');
echo "用户服务运行正常\n";
?>
```
容器还可以管理参数传递,比如数据库连接配置:
```php
class DatabaseConfig
{
public function __construct(
public string $host = 'localhost',
public int $port = 3306,
public string $database = 'test',
public string $username = 'root',
public string $password = '',
) {}
}
$container = new Container();
// 绑定配置
$container->instance(DatabaseConfig::class, new DatabaseConfig(
host: '192.168.1.100',
port: 3307,
database: 'production_db',
));
// 绑定数据库连接,依赖配置
$container->singleton(PDO::class, function ($c) {
$config = $c->make(DatabaseConfig::class);
$dsn = "mysql:host={$config->host};port={$config->port};dbname={$config->database};charset=utf8mb4";
return new PDO($dsn, $config->username, $config->password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
});
$pdo = $container->make(PDO::class);
echo "数据库连接已创建\n";
?>
```
服务提供者模式把相关的绑定和初始化逻辑组织在一起:
```php
interface ServiceProvider
{
public function register(Container $container): void;
public function boot(Container $container): void;
}
class LogServiceProvider implements ServiceProvider
{
public function register(Container $container): void
{
$container->singleton(LoggerInterface::class, function ($c) {
return new FileLogger();
});
}
public function boot(Container $container): void
{
$logger = $container->make(LoggerInterface::class);
$logger->log('INFO', '日志服务已启动');
}
}
class DatabaseServiceProvider implements ServiceProvider
{
public function register(Container $container): void
{
$container->singleton(PDO::class, function ($c) {
$config = $c->make(DatabaseConfig::class);
$dsn = "mysql:host={$config->host};dbname={$config->database};charset=utf8mb4";
return new PDO($dsn, $config->username, $config->password);
});
}
public function boot(Container $container): void
{
$pdo = $container->make(PDO::class);
$pdo->query("SELECT 1"); // 测试连接
}
}
class Application
{
private Container $container;
private array $providers = [];
public function __construct()
{
$this->container = new Container();
}
public function registerProvider(ServiceProvider $provider): void
{
$this->providers[] = $provider;
$provider->register($this->container);
}
public function boot(): void
{
foreach ($this->providers as $provider) {
$provider->boot($this->container);
}
}
public function getContainer(): Container
{
return $this->container;
}
}
$app = new Application();
$app->registerProvider(new LogServiceProvider());
$app->registerProvider(new DatabaseServiceProvider());
$app->boot();
$service = $app->getContainer()->make(UserService::class);
$service->register('李四', 'lisi@test.com');
?>
```
依赖注入容器是框架的基石。理解它的原理后,用起框架来心里更有底。出问题的时候也能快速定位是容器配置的问题还是业务代码的问题。自己手写一个容器虽然不会用到生产环境,但对理解框架原理非常有帮助。
PHP依赖注入容器原理与实现
张小明
前端开发工程师
AI智能体规模化工程实践:七层蓝图解决服务、安全与可观测性挑战
1. 项目概述:规模化AI智能体的服务、安全与可观测性蓝图最近和几个负责AI平台架构的朋友聊天,大家不约而同地提到了同一个痛点:单个AI智能体(Agent)的Demo跑起来很酷,但一旦要把它变成公司内部可复用的服务…
别再怕数学!用Arduino和AS5600磁编码器,一步步实现FOC力矩控制
别再怕数学!用Arduino和AS5600磁编码器,一步步实现FOC力矩控制当你想用无刷电机打造一个灵活的机器人关节或稳定的云台时,FOC(磁场定向控制)算法无疑是实现精准力矩控制的最佳选择。但对于大多数创客和嵌入式爱好者来说…
别再死记硬背公式了!用Matlab Simulink手把手搭建PMSM的Clark与Park变换模型
用Simulink玩转PMSM坐标变换:从公式恐惧到可视化理解的实战指南当你在电机控制教材上第一次看到Clark和Park变换的矩阵公式时,是否感到一阵眩晕?那些看似简单的三角函数组合,背后隐藏着怎样的物理意义?作为电气工程师&…
别再只用一个Ubuntu了!WSL2下多版本Ubuntu(16.04/20.04)共存保姆级指南
WSL2多版本Ubuntu环境全栈管理实战:从基础配置到高效工作流为什么开发者需要多版本Ubuntu环境共存?在真实的开发场景中,版本依赖就像挥之不去的幽灵。上周刚接手一个遗留项目,构建脚本开头赫然写着"Requires Ubuntu 16.04 wi…
GR4CIL:基于CLIP的类增量学习框架,解决灾难性遗忘与模态间隙难题
1. 项目概述:当CLIP遇上持续学习,如何破解“学新忘旧”的困局?在人工智能的实际部署中,我们常常面临一个尴尬的局面:一个在大量数据上预训练好的强大模型,比如CLIP,一旦需要学习新类别的知识&am…
想让LQR控制器跟踪轨迹?别急着调参,先搞懂‘增广系统’这个核心概念
LQR轨迹跟踪中的增广系统:从理论到实践的深度解析当你在MATLAB中兴奋地运行完LQR控制器代码,却发现系统始终无法精确跟踪目标轨迹时,那种挫败感我深有体会。三年前我第一次实现弹簧阻尼系统的LQR控制,看着状态变量在期望值附近&qu…