必须实现自动解析构造函数参数、接口绑定、单例/瞬时区分及控制器协同;通过container类的bind()和make()方法支持服务注册与递归解析,controller基类强制依赖容器注入,最终完成依赖解耦与可替换性验证。

要在PHP框架中拼接一个可用的依赖注入容器,必须让容器能自动解析构造函数参数、支持接口绑定、区分单例与瞬时实例,并与控制器基类协同工作;否则每次 new 对象都会导致硬编码依赖,无法替换日志、数据库等服务实现。
定义基础容器类并支持服务绑定
在 src/Container.php 中创建空类 Container,声明私有属性 $bindings 用于存储服务注册信息,$instances 用于缓存单例实例。
添加 bind() 方法:接收服务标识符(如字符串 'Logger' 或接口名 LoggerInterface)和解析逻辑(类名字符串或闭包),存入 $bindings 数组。注意:若传入的是闭包,后续 make() 必须执行它;若传入的是字符串类名,则需用反射创建实例。
这一步不能省略类型判断——传入闭包时不做处理直接存,传入字符串时要验证类是否存在,否则后续反射会抛出 Fatal Error。
实现自动解析构造函数依赖的核心逻辑
在 Container 类中添加 make() 方法,接收服务标识符作为参数。
第一步:检查 $instances 中是否已存在该标识符对应的单例实例,有则直接返回;
第二步:若未注册到 $bindings,且标识符是合法类名,则尝试用反射获取其构造函数;
第三步:遍历构造函数参数,对每个参数类型提示(如 Request、Response、LoggerInterface)递归调用 make() 获取对应实例;
【关键点】反射过程中若遇到未绑定的接口类型(如 UserRepository),必须立即抛出异常,不能静默跳过,否则运行时才报错更难定位。
第四步:用 ReflectionClass::newInstanceArgs() 将解析出的依赖数组传入构造函数完成实例化;
第五步:若该服务被标记为单例(例如通过 singleton() 方法注册),则将实例写入 $instances 并返回。
在控制器基类中集成容器实例
打开 src/Controller.php,定义抽象类 Controller,声明 protected $container 属性。
在构造函数中接收 Container 实例并赋值给 $container,不设默认值——【强制前提】所有控制器必须由容器创建,禁止 new Controller()。
添加 render() 方法,内部调用 $this->container->make(Response::class)->send(),把响应输出逻辑交给容器管理的服务。
方法一:在路由分发时,用 $container->make($controllerClass) 替代 new $controllerClass();
方法二:若框架使用中间件机制,在进入控制器前拦截,自动注入 $container 到控制器实例。
绑定接口与具体实现并验证流程
在框架启动文件(如 index.php 或 bootstrap.php)中,获取全局容器实例后执行:
$container->bind(LoggerInterface::class, FileLogger::class);
$container->singleton(Database::class, function ($c) { return new PDO('sqlite:app.db'); });
然后手动触发一次控制器调用:$controller = $container->make(HomeController::class); $controller->index();
如果 HomeController 的构造函数声明了 LoggerInterface $logger 和 Database $db,且 index() 方法中调用了 $logger->log() 和 $db->query(),则说明容器已正确解析并注入全部依赖。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











