Search by

fiberphp / container

fiberphp

🚀 FiberPHP IoC 服务容器 —— PSR-11 实现,支持绑定/单例/上下文绑定、延迟加载、构造参数自动装配与 #[Inject] 属性注入。

Package info

gitee.com/fiberphp/container.git

Issues

pkg:composer/fiberphp/container

Statistics

Installs: 1

Dependents: 2

Suggesters: 0

dev-master 2026-09-30 10:38 UTC

This package is not auto-updated.

Last update: 2026-10-11 11:01:25 UTC


README

FiberPHP IoC 服务容器,实现 PSR-11。支持绑定与单例、上下文绑定、延迟绑定、构造参数自动装配、#[Inject] 属性注入、服务装饰与标签,并内置部署前依赖图静态检查器。

环境要求

  • PHP >= 8.3
  • psr/container ^2.0

安装

composer require fiberphp/container

快速开始

use FiberPHP\Container\Container;

$container = new Container();

$container->singleton(LoggerInterface::class, FileLogger::class);

$userService = $container->get(UserService::class);

UserService 无需任何注册,其构造参数中的类依赖会被自动装配。

绑定与单例

bind() — 工厂绑定

// 类名:解析时自动装配 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() — 单例绑定

工厂只解析一次,后续始终返回同一实例:

$container->singleton(CacheInterface::class, fn (Container $c) => new RedisCache());

实例直接注册

把已创建的对象直接登记为已解析实例:

$container->bind(ClockInterface::class, new SystemClock());

bind() / singleton() 返回容器自身,可链式调用。重新绑定同一抽象会清除其已缓存的实例。

解析

get() — PSR-11 入口

$logger = $container->get(LoggerInterface::class);

未绑定的类执行自动构建;条目不存在或构建失败时抛出异常(见异常)。

getOrNull() — 失败返回 null

$logger = $container->getOrNull(LoggerInterface::class) ?? new NullLogger();

has() — 是否存在条目

if ($container->has(LoggerInterface::class)) {
    // 已绑定、已解析或已注册延迟绑定
}

make() — 透传构造参数 / 强制新建

// 位置参数或命名参数均可
$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],对象构建完成后自动注入。支持两种写法:

use FiberPHP\Container\Inject;

class Service
{
    // 1. 按属性类型注入(要求属性有明确类型)
    #[Inject]
    protected LoggerInterface $logger;

    // 2. 显式指定服务键(属性可不加类型)
    #[Inject(CacheInterface::class)]
    protected $cache;
}

static 属性不注入;readonly 属性、无类型且未显式指定 class 的属性会在构建期抛出异常。建议优先使用构造注入,属性注入仅用于基类层级。

上下文绑定

同一接口在不同消费类中使用不同实现:

$container->addContextual(OrderService::class, LoggerInterface::class, OrderLogger::class);
$container->addContextual(UserService::class, LoggerInterface::class, UserLogger::class);

查询某个消费类中的绑定(未定义返回 null):

$impl = $container->contextualBinding(OrderService::class, LoggerInterface::class);

服务装饰 extend()

在已有服务外层追加包装(如缓冲、缓存),装饰闭包接收原实例与容器:

$container->extend(LoggerInterface::class,
    fn ($logger, Container $c) => new BufferedLogger($logger));
  • 已解析的实例:立即装饰并替换;
  • 已绑定的服务:包装其工厂,保留原有单例/工厂语义;
  • 未绑定的类:按瞬态(bind)注册,需要共享时应先 singleton() 再 extend()。

服务标签

把一组服务归入同一标签,再按组批量解析:

// 单标签或多标签、单服务或多服务均可
$container->tag([CollectorA::class, CollectorB::class], 'collectors');
$container->tag(CollectorC::class, ['collectors', 'metrics']);

foreach ($container->tagged('collectors') as $collector) {
    $collector->run();
}

tagged() 返回解析后的实例列表,顺序与打标顺序一致;同一标签内重复打标只保留一次;标签未定义时返回空数组。

延迟绑定

服务仅在首次被请求时才执行注册逻辑,适合加载成本高的服务:

$container->setDeferredProvider('heavy.service', function (Container $c) {
    $c->singleton('heavy.service', fn () => new HeavyService());
});

// 此时才触发上面的加载闭包,且仅触发一次
$service = $container->get('heavy.service');

has() 对已注册的延迟绑定返回 true。批量移除延迟条目:

$container->removeDeferredProviders(['heavy.service', 'other.service']);

方法调用 call()

调用任意可调用对象,未显式传入的参数按自动装配规则注入:

// 闭包
$result = $container->call(fn (LoggerInterface $logger) => $logger->getName());

// [对象, 方法名],命名参数显式传入
$result = $container->call([$controller, 'show'], ['id' => 1]);

// [类名, 方法名]:接收对象自动从容器解析
$result = $container->call([ReportGenerator::class, 'run'], ['format' => 'pdf']);

全局访问点

容器可作为进程级单例访问:

$container = Container::getInstance();

Container::setInstance($customContainer);

Container::reset(); // 清除单例(测试隔离用)

依赖图静态检查

DependencyChecker 通过反射遍历入口类的完整构造依赖图,不实例化任何类,供部署流水线在运行前发现问题:

use FiberPHP\Container\DependencyChecker;

$checker = new DependencyChecker($container);

$errors = $checker->check([
    UserController::class,
    AuthMiddleware::class,
]);

返回值为错误列表,每条包含可读依赖链路与错误原因;返回空数组即检查通过:

[
    'path'    => [UserController::class, '$userService', '...'],
    'message' => "No binding found for interface 'LoggerInterface'",
]

覆盖的问题:类不存在、类不可实例化(接口/抽象类)、未绑定接口、不可解析的标量参数、圈依赖,以及非法 #[Inject] 属性。

检查边界:

  • 有默认值、变长参数的依赖不纳入检查(运行期可自行兜底);
  • 显式绑定(含上下文绑定、延迟绑定)视为可解析,不展开闭包内部。

异常

异常实现接口抛出场景
ClassNotFoundExceptionPSR NotFoundExceptionInterface类不存在;接口存在但未绑定实现时提示 No binding found for interface
ContainerExceptionPSR ContainerExceptionInterface不可实例化、圈依赖、参数无法解析、非法属性注入等一切构建错误

两者均位于 FiberPHP\Container\Exception,且 ClassNotFoundException 继承自 ContainerException,按需选择 catch 粒度。

License

MIT