prinsfrank/container

严格的PSR-11实现

资助包维护!
PrinsFrank

v0.1.3 2024-09-18 11:47 UTC

This package is auto-updated.

Last update: 2024-09-21 21:23:34 UTC


README

容器

GitHub PHP Version Support codecov PHPStan Level

严格的容器实现

设置

注意 确保您正在运行PHP 8.1或更高版本以使用此包

要立即开始,请在您的composer项目中运行以下命令;

composer require prinsfrank/container

或者仅用于开发;

composer require prinsfrank/container --dev

为什么选择这个容器包?

1. 动态具体绑定

在应用的入口点 - 如控制器或命令 - 可能存在需要注入各种不同类的情况。

在大多数容器中,如果您想自动解析所有请求(例如),您必须在每个服务提供者中注册每个单独的具体类。

private const REQUEST_CLASSES = [
    // list of classes
];

public function provides(string $id): bool {
    return in_array($id, self::REQUEST_CLASSES, true);
}

public function register(mixed ...): void {
    foreach (self::REQUEST_CLASSES as $requestClass) {
        $this->container->add(
            $requestClass, 
            fn() => $objectResolver->resolve($requestClass)
        )
    }
}

这意味着,即使提供者被延迟并且所有请求都没有在容器初始化时添加到容器中,一旦一个请求被请求并且服务提供者启动,所有请求都会被注册。这增加了内存占用,但还执行了不必要的操作,因为我们很可能在每个执行周期中只使用一个请求。

相反,在这个包中,可以添加动态具体绑定。消费者请求的标识符传递给服务提供者。不仅传递给provides方法,也传递给register方法

final class RequestDataProvider implements ServiceProviderInterface {
    public function provides(string $identifier): bool {
        return is_a($identifier, RequestData::class, true);
    }

    public function register(string $identifier, DefinitionSet $resolvedSet): void {
        $resolvedSet->add(
            new Concrete(
                $identifier,
                fn (ObjectResolver $objectResolver) => $objectResolver->resolve($identifier),
            )
        );
    }
}

如您所见,只要有一个接口被所有请求实现,现在就可以解析所有请求,而无需手动保持请求类列表,也无需在容器中注册成百上千个大多不需要的类。

2. 无透明入口标识符,只有类字符串

与其他容器不同,此包完全严格地处理服务入口标识符。此包不仅允许任何字符串作为入口标识符,而且只允许现有类和接口的FQNs。因此,不允许使用简单的'db'字符串来标识一个\DatabaseConnection实例。这意味着此包非常适合静态分析,因为它不需要在静态分析时启动容器,以确定由入口标识符定位的对象的类型。

这略微偏离了PSR-11标准,1.1.1入口标识符,其中指定了以下内容

入口标识符是任何至少由一个字符组成的PHP-合法字符串,它唯一地标识容器中的项。入口标识符是一个不透明的字符串,因此调用者不应假设字符串的结构具有任何语义意义。

在**此**容器包的上下文中,以下才是正确的

入口标识符是类或接口的类字符串。调用者可以假设对于入口标识符是接口的类字符串返回的对象实现了该接口,抽象类的类字符串导致的对象扩展了该抽象类,而具体类的类字符串是该具体类或其子类的实例。

由于大多数入口标识符已经是类字符串,这不是一个大问题,但它使事情更加优雅。PSR-11的所有其他规范点仍然有效。

3. ServiceProvider闭包中的DI'ed服务

假设我们有一个服务(A),因为它有两个依赖项,其中一个可以由容器(B)解析,另一个不能(C),所以它只能部分解析。

当启用自动装配时,你可以简单地添加一个无法解析的服务(C)的定义,这样服务A也可以被解析。但如果参数不是一个服务或者不应该普遍可用,你还可以请求作为服务闭包参数的可用的服务

class FooApiServiceProvider implements ServiceProviderInterface {
    public function provides(string $identifier): bool {
        return $identifier === FooApi::class;
    }

    public function register(string $identifier, DefinitionSet $resolvedSet, Container $container): void {
        $resolvedSet->add(
            new AbstractConcrete(
                $identifier,
                static function (Environment $environment, ClientInterface $client) {
                    return new FooApi($environment->get('FOO_API_KEY'), $client)
                }
            )
        );
    }
}

与其他容器的特性比较

1 以 illuminate/container 发布
2 除了 psr/container 接口之外