kabuto/yii-di

dev-master / 3.0.x-dev 2019-06-19 22:07 UTC

This package is auto-updated.

Last update: 2024-09-20 09:57:10 UTC


README

Yii 依赖注入


该库是一个与 PSR-11 兼容的 依赖注入 容器,能够实例化和配置类以解析依赖。

Latest Stable Version Total Downloads Build Status Scrutinizer Code Quality Code Coverage

功能

  • PSR-11 兼容。
  • 支持属性注入、构造函数注入和方法注入。
  • 检测循环引用。
  • 接受数组定义,因此可用于合并配置。

使用容器

使用 DI 容器相当简单。首先,您将其设置为对象定义,然后它们可以直接在应用程序中使用或用于解析其他定义的依赖项。

通常整个应用程序中只有一个容器,因此它通常配置在入口脚本(如 index.php)或配置文件中。

$container = new Container($config);

配置可以存储在返回数组的 .php 文件中。

return [
    EngineInterface::class => EngineMarkOne::class,
    'full_definition' => [
        '__class' => EngineMarkOne::class,
        '__construct()' => [42], 
        'argName' => 'value',
        'setX()' => [42],
    ],
    'closure' => function($container) {
        return new MyClass($container->get('db'));
    },
    'static_call' => [MyFactory::class, 'create'],
    'object' => new MyClass(),
];

接口定义简单地将一个 ID(通常是一个接口)映射到特定的类。

完整定义详细描述了如何实例化一个类。

  • __class 包含要实例化的类的名称。
  • __construct() 包含构造函数参数的数组。
  • 配置的其余部分和属性值以及方法调用。它们按照数组中的顺序设置/调用。

闭包在实例化很复杂且应在代码中描述时很有用。如果非常复杂,将此类代码移动到工厂中并将其作为静态调用引用是一个好主意。

尽管通常不是一个好主意,但您可以设置已实例化的对象到容器中。

此外,定义可以通过调用 set() 来添加。

$container->set($id, Example::class);

$container->set($id, [
    '__class' => Example::class,
    '__construct()' => ['a', 'b'],
    'property1' => 'val1',
    'setMethod()' => 'val2',
    'property2' => 'val3',
]);

配置容器后,可以通过 get() 获取依赖项。

$object = $container->get('interface_name');

请注意,直接使用容器是一种不良实践,而通过通过 yiisoft/injector 包提供的 Injector 实现自动注入则更好。

使用别名

容器通过 Reference 类支持别名。能够以接口和命名的方式检索对象可能很有用。

$container = new Container([
    EngineInterface::class => EngineMarkOne::class,
    'engine_one' => EngineInterface::class,
]);

委托查找和组合容器

Container 类支持委托查找。当使用委托查找时,所有依赖项始终从给定的根容器中获取。这允许我们将多个容器组合成组合容器,然后我们可以将其用于查找。使用这种方法时,应仅使用组合容器。

$composite = new CompositeContainer();
$container = new Container([], [], $composite);

上下文容器

在应用程序中,我们可能希望为 DI 容器设置多个配置级别。例如,在 Yii 应用程序中,这些可能是

  • 提供默认配置的扩展
  • 具有配置的应用程序
  • 使用与主应用程序不同配置的应用程序中的模块

虽然通常不希望将 DI 容器注入到您的对象中,但有一些例外,例如需要访问容器的 Yii 应用程序模块。

为了在模块级别支持自定义配置的同时支持此用例,我们实现了上下文容器。主要类是CompositeContextContainer;在它不包含任何定义的意义上,它与CompositeContainer相似。上下文容器的attach()函数有一个额外的字符串参数,用于定义容器的上下文。

使用上下文,我们可以创建一个简单的范围系统

$root = new CompositeContextContainer();
$coreContainer = new Container([], [], $root);
$extensionContainer = new Container([], [], $root);

$appContainer = new Container([
    LoggerInterface::class => MainLogger::class
], [], $root);
$moduleAContainer = new Container([
    LoggerInterface::class => LoggerA::class
], [], $root);
$moduleBContainer = new Container([
    LoggerInterface::class => LoggerB::class
], [], $root);

$composite->attach($moduleContainer, '/moduleB');
$composite->attach($moduleContainer, '/moduleA');
$composite->attach($appContainer);
$composite->attach($extensionContainer);
$composite->attach($coreContainer);

// The composite context container will allow us to create contextual containers with virtually no overhead.
$moduleAContainer = $composite->getContextContainer('/moduleA');
$moduleBContainer = $composite->getContextContainer('/moduleB');

$composite->get(LoggerInterface::class); // MainLogger

$composite->get(LoggerInterface::class); // MainLogger
$moduleAContainer->get(LoggerInterface::class // LoggerA
$moduleBContainer->get(LoggerInterface::class // LoggerB

搜索是按照最长前缀首先进行,然后按照它们被添加的顺序检查容器。对于Yii的上下文容器,模块的容器是自动创建的。

使用服务提供者

服务提供者是一种特殊类,负责将复杂服务或依赖组绑定到容器中,包括注册服务及其引用、事件监听器、中间件等。

所有服务提供者都扩展了yii\di\support\ServiceProvider类,并包含一个register方法。在register方法内部,您应该仅将东西绑定到容器中。您永远不应该在服务提供者中实现任何业务逻辑、与环境引导相关的功能、更改数据库或与绑定东西无关的任何其他功能。要在服务提供者中访问容器,您应该使用container字段。容器通过构造函数传递给服务提供者,并保存到container字段。

典型的服务提供者可能看起来像

use yii\di\contracts\ServiceProvider;

class CarFactoryProvider implements ServiceProvider
{
    public function register(Container $container): void
    {
        $container->registerDependencies($container);
        $container->registerService($container);
    }

    protected function registerDependencies(Container $container): void
    {
        $container->set(EngineInterface::class, SolarEngine::class);
        $container->set(WheelInterface::class, [
            '__class' => Wheel::class,
            'color' => 'black',
        ]);
        $container->set(CarInterface::class, [
            '__class' => BMW::class,
            'model' => 'X5',
        ]);
    }

    protected function registerService(Container $container): void
    {
        $container->set(CarFactory::class, [
              '__class' => CarFactory::class,
              'color' => 'red',
        ]);
    }
}

要将服务提供者添加到容器中,您需要将服务提供者类(或配置数组)传递给容器的addProvider方法

$container->addProvider(CarFactoryProvider::class);

或通过使用providers键通过配置数组传递它

$container = new Container([
    'providers' => [
        CarFactoryProvider::class,
    ],
]);

在上面的代码中,我们创建了一个负责启动具有所有依赖项的汽车工厂的服务提供者。一旦通过addProvider方法或通过配置数组添加了服务提供者,服务提供者的register方法就会立即调用,并将服务注册到容器中。

注意,如果您在register方法中执行重操作,服务提供者可能会降低您的应用程序的性能。

使用延迟服务提供者

如前所述,服务提供者在注册重服务时可能会降低您的应用程序的性能。因此,为了避免性能下降,您可以使用所谓的延迟服务提供者。

延迟服务提供者扩展了yii\di\support\DeferredServiceProvider,除了register方法外,还包含一个provides方法,该方法返回服务提供者绑定到容器中的服务名称和标识符数组。延迟服务提供者被添加到容器的相同方式,但延迟服务提供者的register方法仅在请求provides方法中列出的服务之一时才会调用。示例

use yii\di\support\DeferredServiceProvider;

class CarFactoryProvider extends DeferredServiceProvider
{
    public function provides(): array
    {
        return [
            
            CarFactory::class,
            CarInterface::class,
            EngineInterface::class,
            WheelInterface::class,
        ];
    }
    
    public function register(Container $container): void
    {
        $this->registerDependencies($container);
        $this->registerService($container);
    }

    protected function registerDependencies(Container $container): void
    {
        $container->set(EngineInterface::class, SolarEngine::class);
        $container->set(WheelInterface::class, [
            '__class' => Wheel::class,
            'color' => 'black',
        ]);
        $container->set(CarInterface::class, [
            '__class' => BMW::class,
            'model' => 'X5',
        ]);
    }

    protected function registerService(Container $container): void
    {
        $container->set(CarFactory::class, [
              '__class' => CarFactory::class,
              'color' => 'red',
        ]);
    }
}

$container->addProvider(CarFactoryProvider::class);

// returns false as provider wasn't registered
$container->has(EngineInterface::class); 

// returns SolarEngine, registered in the provider
$engine = $container->get(EngineInterface::class); 

// returns true as provider was registered when EngineInterface was requested from the container
$container->has(EngineInterface::class); 

在上面的代码中,我们将CarFactoryProvider添加到容器中,但CarFactoryProviderregister方法直到从容器请求EngineInterface时才执行。当我们请求EngineInterface时,容器查看CarFactoryProviderprovides列表,并且由于EngineInterface列在provides中,容器调用了CarFactoryProviderregister方法。

注意,您可以使用延迟服务提供者不仅来延迟重服务的引导,而且当它们实际需要时才将服务注册到容器中。

进一步阅读