当前位置: 首页 > news >正文

PHP依赖注入容器原理与实现

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');
?>
```

依赖注入容器是框架的基石。理解它的原理后,用起框架来心里更有底。出问题的时候也能快速定位是容器配置的问题还是业务代码的问题。自己手写一个容器虽然不会用到生产环境,但对理解框架原理非常有帮助。

http://www.jsqmd.com/news/926993/

相关文章:

  • 抖音素材收集革命:5分钟搞定无水印批量下载,自媒体人必备神器!
  • UE5 Niagara新手教程:用T_SmokeSubUV纹理5分钟做出动态烟雾特效
  • 别再只用DataParallel了!PyTorch DDP分布式训练保姆级配置教程(含launch与spawn启动对比)
  • AI如何重塑蓝领工作:从自动化到人机协作的转型路径
  • AI 智能体全流程实战:从 0 搭一个门店运营助手,用 API + 工具搜索 + 编码代理做出可复现闭环
  • RT-Thread传感器框架实战:以BMI088(SPI)为例,解析sensor驱动模型
  • 从网线到电源:一文读懂PoE(802.3bt)如何用4对线给大功率设备供电(含选型避坑指南)
  • SIS问题不只是理论:在抗量子签名与哈希函数中的实战应用拆解
  • SwanLab离线版远程访问全攻略:从单机到团队协作,安全共享你的实验看板
  • 别再死记硬背74LS138真值表了!用这个实验箱实战一次,彻底搞懂3-8译码器
  • DataGrip激活失败?别慌!可能是Windows Defender或杀软在搞鬼(附详细排查与解决步骤)
  • 从类图到对象图:用StarUML(或任意UML工具)画一张“有生命”的系统快照
  • Qt Creator里配置onnxruntime的坑我帮你踩了(附YOLOv8推理C++项目完整配置流程)
  • 别再为IP核仿真头疼了!手把手教你用Vivado 2018.3给ModelSim 22.04编译专属仿真库
  • 避开这些坑!深信服AC内容审计策略不生效的5个排查步骤(附SSL解密原理)
  • 混沌系统随机性好不好?手把手教你用NIST测试包和Matlab出报告
  • 别再死记硬背了!通过一个校园网项目,彻底搞懂VLAN、VRRP和OSPF是怎么协同工作的
  • 别再只盯着CTR了!硬件工程师必看:光耦选型时这5个参数才是关键(附避坑指南)
  • SQL开发者如何通过特征工程与数据库内机器学习实现技能升级
  • 远程开发实战:在AutoDL云服务器上通过VNC运行COLMAP GUI图形界面
  • 数字电路入门避坑指南:实测74LS86异或门电压,为什么我的结果和理论值对不上?
  • 香橙派Orange Pi 5 Plus保姆级教程:一键开启UART/I2C/SPI/PWM/CAN所有接口(附配置清单)
  • CTF新手必看:从一张JPG图片里挖出ZIP压缩包和隐藏Flag(附Kali工具实战)
  • 量子计算与无网格粒子法融合:Q-FPM框架解析
  • 避坑指南:Node-RED处理Modbus-RTU负温度补码与数据解析的完整流程
  • 告别死板!用Cadence Allegro 16.6的Shape Symbol,5步搞定异形焊盘(附坐标计算小技巧)
  • OPNsense安装选UFS还是ZFS?从硬件资源与稳定性角度帮你做决定
  • 代工厂和贴牌品牌方在数据上怎么分?
  • 别再折腾了!手把手教你搞定MathType 7.4.10在Office 2021/365上的安装与报错(附文件路径详解)
  • AI 智能体总是跑偏怎么办?ChatGPT/API/Agent 故障排查指南与全流程修复手册