如何从零手写一个DI容器:基于gh_mirrors/co/container实现PSR-11合规容器的完整教程
【免费下载链接】container项目地址: https://gitcode.com/gh_mirrors/co/container
本教程基于 gh_mirrors/co/container(PSR-11 标准接口仓库),带你从零手写一个 PHP 依赖注入容器(DI 容器),掌握 PSR-11 合规容器的全部 3 个接口与 2 个核心方法。📦
💡 为什么值得学?DI 容器是现代 PHP 框架(如 Laravel、Symfony)的基石。读懂 PSR-11 这 3 个几十行的接口文件,你就掌握了所有合规容器的"公共语言"。
什么是DI容器?1分钟看懂核心思想
DI 容器(Dependency Injection Container,依赖注入容器)本质是一个"服务工厂":
- 传统写法:类 A 内部自己
new类 B,两者强耦合,难以测试和替换; - DI 容器写法:把"创建对象"的职责交给容器,类 A 只声明"我需要 B",由容器统一组装和交付。
好处一目了然:✅ 依赖集中管理、✅ 便于单元测试(可注入 Mock 对象)、✅ 替换实现时业务代码零改动。
而PSR-11是 PHP-FIG 制定的"容器接口标准"——它规定任何容器都至少暴露统一的get()和has()方法。gh_mirrors/co/container 正是这份标准的接口定义仓库,只有 3 个接口文件、没有一行实现代码,这正是我们"从零手写"的完美起点。
PSR-11核心剖析:3个接口文件定乾坤
仓库结构极其精简,src/目录下仅 3 个文件:
| 文件 | 作用 | 一句话理解 |
|---|---|---|
| ContainerInterface.php | 容器主体接口 | 容器的"脸面":get()+has() |
| ContainerExceptionInterface.php | 容器异常基接口 | 容器出错的"统一身份证" |
| NotFoundExceptionInterface.php | "找不到"异常接口 | 专门标记"查无此服务" |
第一步:读懂 ContainerInterface 的两个方法
整个 PSR-11 标准的核心就在这两个方法声明里:
- get(string $id):按标识符取对象,是容器的"主入口";
- has(string $id): bool:判断容器是否认识某个标识符。
其中有一段非常关键的官方约定(src/ContainerInterface.php):
has($id)返回 true不保证get($id)一定不抛异常,但保证不会抛"未找到异常"。
换句话说:has()管"有没有这个 key",get()还负责"构造这个对象"——构造过程中仍可能因其他原因失败。写实现时务必遵守这条语义。
第二步:理解两级异常体系
异常接口只有寥寥数行,却设计了清晰的继承链:
Throwable └── ContainerExceptionInterface(容器通用异常) └── NotFoundExceptionInterface(未找到异常)- 基接口定义在 ContainerExceptionInterface.php,要求"所有容器异常都继承它";
- 未找到接口定义在 NotFoundExceptionInterface.php,专用于"key 不存在"场景。
这样设计的好处:调用方可以分级捕获——catch NotFoundExceptionInterface只处理"没找到",catch ContainerExceptionInterface兜底其他构建失败。错误语义从此清晰可控。🎯
3步写出你的PSR-11合规容器
一键获取标准接口包
标准库通过 Composer 包psr/container分发(元数据见 composer.json,要求 PHP >= 7.4,命名空间Psr\Container映射到src/目录)。获取仓库:
git clone https://gitcode.com/gh_mirrors/co/container composer require psr/container实现两个方法:最简容器诞生
手写实现其实非常短——一个数组存服务、两个方法做存取即可:
<?php use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; final class MyContainer implements ContainerInterface { private array $services = []; public function set(string $id, $service): void { $this->services[$id] = $service; } public function get(string $id) { if (!array_key_exists($id, $this->services)) { throw new class("Service [$id] not found.") extends \RuntimeException implements NotFoundExceptionInterface {}; } return $this->services[$id]; } public function has(string $id): bool { return array_key_exists($id, $this->services); } }对照标准检查一遍:get()未找到时抛出的异常实现了 NotFoundExceptionInterface ✅;has()返回 bool ✅。两个方法齐全,恭喜——你的容器已通过 PSR-11 合规检验!
注册与获取:3行代码感受依赖注入
$container = new MyContainer(); $container->set('logger', new MonologLogger()); $logger = $container->get('logger'); // 从容器取,而非 new业务类只需声明__construct(Logger $logger),由容器注入实例——依赖倒置就此完成。🚀
常见踩坑与最佳实践清单
- ⚠️key 命名要稳定:
$id是全局契约,建议用"类型名或语义化名称",如logger、app.config; - ⚠️区分"没找到"和"构造失败":前者抛
NotFoundExceptionInterface,后者抛ContainerExceptionInterface的其它实现,别混为一谈; - ⚠️
has()为 true 不代表get()无异常:这是官方明文约定(见 src/ContainerInterface.php),不要据此省略异常处理; - ✅面向接口编程:业务代码依赖
ContainerInterface而非你的MyContainer,未来无缝切换任何 PSR-11 容器; - ✅进阶方向:把
set()升级为"工厂闭包"(传入Closure延迟实例化),即可实现自动依赖解析,这也是各大框架容器的核心机制。
总结与延伸资源
回顾一下这条"从零到合规"的路线:
- 理解标准——PSR-11 只用 3 个接口定义容器的行为契约;
- 实现
get()/has()——两行判断、一个数组,最简容器即可成立; - 规范异常——用两级异常体系表达"没找到"与"构建失败"。
📁 核心资料速查:
- 容器主体接口:ContainerInterface.php
- 异常基接口:ContainerExceptionInterface.php
- 未找到异常接口:NotFoundExceptionInterface.php
- 包元数据与自动加载配置:composer.json
- 项目说明:README.md
标准只有薄薄一层,真正让 DI 容器变强大的,是你的工厂闭包、自动解析和缓存策略。动手把上面的MyContainer扩展成支持工厂的版本吧——你会离手写一个"迷你版框架容器"只差一小步!✨
【免费下载链接】container项目地址: https://gitcode.com/gh_mirrors/co/container
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考