# container **Repository Path**: fiberphp/container ## Basic Information - **Project Name**: container - **Description**: 🚀 FiberPHP IoC 服务容器 —— PSR-11 实现,支持绑定/单例/上下文绑定、延迟加载、构造参数自动装配与 #[Inject] 属性注入。 - **Primary Language**: PHP - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-25 - **Last Updated**: 2026-09-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # FiberPHP Container FiberPHP IoC 服务容器,实现 PSR-11。支持绑定与单例、上下文绑定、延迟绑定、构造参数自动装配、`#[Inject]` 属性注入、服务装饰与标签,并内置部署前依赖图静态检查器。 ## 环境要求 - PHP >= 8.3 - psr/container ^2.0 ## 安装 ```bash composer require fiberphp/container ``` ## 快速开始 ```php use FiberPHP\Container\Container; $container = new Container(); $container->singleton(LoggerInterface::class, FileLogger::class); $userService = $container->get(UserService::class); ``` `UserService` 无需任何注册,其构造参数中的类依赖会被自动装配。 ## 绑定与单例 ### bind() — 工厂绑定 ```php // 类名:解析时自动装配 Impl 的构造参数 $container->bind(LoggerInterface::class, FileLogger::class); // 闭包:自定义构造过程,闭包接收容器与透传参数 $container->bind(Redis::class, fn (Container $c, array $params = []) => new Redis($params['host'] ?? '127.0.0.1')); // 自绑定(省略 concrete):仅为该类注册一个绑定条目 $container->bind(Service::class); // 共享实例请用 singleton() $container->singleton(Service::class); ``` ### singleton() — 单例绑定 工厂只解析一次,后续始终返回同一实例: ```php $container->singleton(CacheInterface::class, fn (Container $c) => new RedisCache()); ``` ### 实例直接注册 把已创建的对象直接登记为已解析实例: ```php $container->bind(ClockInterface::class, new SystemClock()); ``` `bind()` / `singleton()` 返回容器自身,可链式调用。重新绑定同一抽象会清除其已缓存的实例。 ## 解析 ### get() — PSR-11 入口 ```php $logger = $container->get(LoggerInterface::class); ``` 未绑定的类执行自动构建;条目不存在或构建失败时抛出异常(见[异常](#异常))。 ### getOrNull() — 失败返回 null ```php $logger = $container->getOrNull(LoggerInterface::class) ?? new NullLogger(); ``` ### has() — 是否存在条目 ```php if ($container->has(LoggerInterface::class)) { // 已绑定、已解析或已注册延迟绑定 } ``` ### make() — 透传构造参数 / 强制新建 ```php // 位置参数或命名参数均可 $service = $container->make(Service::class, ['host' => 'redis://x', 1]); // fresh=true:跳过单例缓存,强制创建新实例 $another = $container->make(Service::class, fresh: true); ``` > 传入构造参数(即使是 singleton 绑定)会绕过单例缓存,本次解析返回独立实例;无参解析才命中共享缓存。 ## 自动装配规则 未显式传入的构造参数,按参数声明处理: | 参数声明 | 处理方式 | |---|---| | 单一类类型 `Foo`、可空类类型 `?Foo` | 从容器解析该类型 | | 多类联合 `Foo\|Bar` | 需有默认值(回退)或显式绑定,否则报错 | | 交叉类型 `Foo&Bar` | 需有默认值或显式绑定,否则报错 | | 标量、无类型参数 | 需有默认值,否则报错 | | 变长参数 `...$items` | 不自动解析,仅接受 `make()` 显式传入(数组自动展开) | 有默认值的类依赖还承担两个兜底场景:依赖类型正在构建链上(自引用/互相引用,如 `?Node $parent = null`),以及未绑定的接口/抽象类——此时回退默认值而非报错。 ## 属性注入 实例属性标注 `#[Inject]`,对象构建完成后自动注入。支持两种写法: ```php use FiberPHP\Container\Inject; class Service { // 1. 按属性类型注入(要求属性有明确类型) #[Inject] protected LoggerInterface $logger; // 2. 显式指定服务键(属性可不加类型) #[Inject(CacheInterface::class)] protected $cache; } ``` `static` 属性不注入;`readonly` 属性、无类型且未显式指定 class 的属性会在构建期抛出异常。建议优先使用构造注入,属性注入仅用于基类层级。 ## 上下文绑定 同一接口在不同消费类中使用不同实现: ```php $container->addContextual(OrderService::class, LoggerInterface::class, OrderLogger::class); $container->addContextual(UserService::class, LoggerInterface::class, UserLogger::class); ``` 查询某个消费类中的绑定(未定义返回 null): ```php $impl = $container->contextualBinding(OrderService::class, LoggerInterface::class); ``` ## 服务装饰 extend() 在已有服务外层追加包装(如缓冲、缓存),装饰闭包接收原实例与容器: ```php $container->extend(LoggerInterface::class, fn ($logger, Container $c) => new BufferedLogger($logger)); ``` - 已解析的实例:立即装饰并替换; - 已绑定的服务:包装其工厂,**保留原有单例/工厂语义**; - 未绑定的类:按瞬态(bind)注册,需要共享时应先 `singleton()` 再 `extend()`。 ## 服务标签 把一组服务归入同一标签,再按组批量解析: ```php // 单标签或多标签、单服务或多服务均可 $container->tag([CollectorA::class, CollectorB::class], 'collectors'); $container->tag(CollectorC::class, ['collectors', 'metrics']); foreach ($container->tagged('collectors') as $collector) { $collector->run(); } ``` `tagged()` 返回解析后的实例列表,顺序与打标顺序一致;同一标签内重复打标只保留一次;标签未定义时返回空数组。 ## 延迟绑定 服务仅在首次被请求时才执行注册逻辑,适合加载成本高的服务: ```php $container->setDeferredProvider('heavy.service', function (Container $c) { $c->singleton('heavy.service', fn () => new HeavyService()); }); // 此时才触发上面的加载闭包,且仅触发一次 $service = $container->get('heavy.service'); ``` `has()` 对已注册的延迟绑定返回 true。批量移除延迟条目: ```php $container->removeDeferredProviders(['heavy.service', 'other.service']); ``` ## 方法调用 call() 调用任意可调用对象,未显式传入的参数按自动装配规则注入: ```php // 闭包 $result = $container->call(fn (LoggerInterface $logger) => $logger->getName()); // [对象, 方法名],命名参数显式传入 $result = $container->call([$controller, 'show'], ['id' => 1]); // [类名, 方法名]:接收对象自动从容器解析 $result = $container->call([ReportGenerator::class, 'run'], ['format' => 'pdf']); ``` ## 全局访问点 容器可作为进程级单例访问: ```php $container = Container::getInstance(); Container::setInstance($customContainer); Container::reset(); // 清除单例(测试隔离用) ``` ## 依赖图静态检查 `DependencyChecker` 通过反射遍历入口类的完整构造依赖图,**不实例化任何类**,供部署流水线在运行前发现问题: ```php use FiberPHP\Container\DependencyChecker; $checker = new DependencyChecker($container); $errors = $checker->check([ UserController::class, AuthMiddleware::class, ]); ``` 返回值为错误列表,每条包含可读依赖链路与错误原因;返回空数组即检查通过: ```php [ 'path' => [UserController::class, '$userService', '...'], 'message' => "No binding found for interface 'LoggerInterface'", ] ``` 覆盖的问题:类不存在、类不可实例化(接口/抽象类)、未绑定接口、不可解析的标量参数、圈依赖,以及非法 `#[Inject]` 属性。 检查边界: - 有默认值、变长参数的依赖不纳入检查(运行期可自行兜底); - 显式绑定(含上下文绑定、延迟绑定)视为可解析,不展开闭包内部。 ## 异常 | 异常 | 实现接口 | 抛出场景 | |---|---|---| | `ClassNotFoundException` | PSR `NotFoundExceptionInterface` | 类不存在;**接口存在但未绑定实现**时提示 `No binding found for interface` | | `ContainerException` | PSR `ContainerExceptionInterface` | 不可实例化、圈依赖、参数无法解析、非法属性注入等一切构建错误 | 两者均位于 `FiberPHP\Container\Exception`,且 `ClassNotFoundException` 继承自 `ContainerException`,按需选择 catch 粒度。 ## License [MIT](LICENSE)