ob-ivan/sd-dependency-injection

该包最新版本(v1.6)没有提供许可证信息。

v1.6 2018-02-14 15:03 UTC

This package is auto-updated.

Last update: 2024-09-29 05:20:47 UTC


README

注册服务并将它们注入消费者。

安装

composer require ob-ivan/sd-dependency-injection

用法

术语

在大型应用程序中,某些类实例需要被许多其他类共享的情况很常见。例如,一个数据库连接实例在各个控制器类之间共享。

这些共享的类被称为 服务(它们为其他类提供一些有用的功能),而共享它们的类被称为 消费者(它们消费提供的服务)。

让消费者知道它需要访问的所有服务的整个过程称为 注入

消费者可以正式声明它需要的服务的列表。这被称为列出其 依赖项。将消费者的依赖项列表取出来并将相应的服务注入其中的过程称为,不出所料,依赖注入

依赖注入容器(DIC)是实现依赖注入的一种方式。它要求每个服务都在容器中以某种 公共名称 注册,并且消费者将它们所需服务的公共名称作为正式的依赖项列表列出。

依赖项可以通过几种不同的方式注入消费者。在这个库中,支持以下三种类型的依赖注入:

  • 设置器注入(也称为 接口注入)- 对于实现 DeclarerInterface 的消费者实例,调用每个声明依赖项的设置器。
  • 构造函数注入 - 对于消费者的 ClassName,解析其构造函数参数作为依赖项名称,并使用适当的服务调用构造函数。然后在创建的实例上调用设置器注入。
  • 参数注入 - 对于消费者 Function,解析其参数作为依赖项名称,并使用适当的参数调用该 Function。

您可以使用容器来注册服务、生成消费者或将依赖项注入任意代码。

  • 消费者 = 函数 | 实例
  • 消费者初始化器 = 类名称 | 消费者
  • 服务初始化器 = 消费者初始化器 | 值

构造

容器用原始值初始化,不允许使用初始化器

$container = new SD\DependencyInjection\Container([
    'name' => 'Chewbaka', // not interpreted as a class name
]);

如果您有多个容器,您可以通过合并它们来创建一个新的容器

$mergedContainer = SD\DependencyInjection\Container::merge($container1, $container2);

注册服务

使用服务初始化器注册服务

// Setter injection - use when constructing a Service is cheap and doesn't involve resource allocating.
$container->register('helloWorld', new HelloWorldService('Anakin'));

// Constructor injection + Setter injection - use when Service does not require constructor arguments.
$container->register('helloWorld', HelloWorldService::class);

// Argument injection + Setter injection - use when Service requires a constructor argument or
// Service construction is anyhow a complicated process.
$container->register('helloWorld', function ($name) {
    if ($name === strtoupper($name)) {
        return new HelloShouterService($name);
    }
    return new HelloWorldService($name);
});

// No injection - use for setting parameters.
$container->register('name', $container->value('Skywalker'));

您不必在容器内部注册容器,因为它默认是自我感知的

// The 'container' common name is reserved to refer to the container itself.
$container === $container->get('container');

您可以扩展已注册的服务

// Initial registering:
$container->register('currency', SD\Currency\Repository::class);

// Later on:
$container->extend('currency', function ($container, $currency) {
    $store = $container->produce(SD\CurrencyStore\Wpdb::class);
    $currency->setStore($store);
    return $currency;
});

生成消费者

使用消费者初始化器生成消费者(不支持值)

$controller = $container->produce(HelloWorldController::class);
// or:
$controller = $container->produce(new HelloWorldController('Luke'));
// or:
$controller = $container->produce(function ($catchPhrase) {
    return new HelloWorldController($catchPhrase);
});

您可以将服务注入任何消费者

// Inject into an already instantiated consumer:
$serviceAwareCalculator = $container->inject($serviceUnawareCalculator);

// Inject into a callable:
$response = $container->inject(function ($helloWorldService) use ($name) {
    return $helloWorldService->greet($name);
});

尽管不鼓励这样做,但您可以将容器用作服务定位器,如果执行上下文不允许您使用依赖注入的力量

$legacyConsumer = new LegacyConsumer($container->get('brand_new_service'));

定义消费者

类消费者(与可调用消费者相对)可以在以下任何一种方式中声明它们的依赖项——或两者的组合

  • 通过实现 DeclarerInterface 并从 declareDependencies 方法返回一个公共名称列表。
  • 通过实现 AutoDeclarerInterface 并导入其相应依赖项的 AutoDeclarerTraitAwareTrait

由于在 declareDependencies 方法中列出公共名称会导致大量重复,我们建议为每个定义的服务定义一个 AwareTrait

以下是一个示例代码

trait ExampleAwareTrait
{
    // Here's the magic. Create a field named starting with $autoDeclare and containing the service's
    // common name as a value. Don't forget to import AutoDeclarerTrait!
    protected $autoDeclareExample = 'example';
    private $example;

    // Setter name must match the common name.
    public function setExample(ExampleService $example) {
        $this->example = $example;
    }

    // An example way to access an example service instance.
    protected function getExample()
    {
        return $this->example;
    }
}

这种方式定义消费者,会告诉容器将其注入 ExampleService —— 如果不可用,则会抛出异常。

use SD\DependencyInjection\AutoDeclarerInterface;
use SD\DependencyInjection\AutoDeclarerTrait;

class ExampleConsumer implements AutoDeclarerInterface
{
    use AutoDeclarerTrait;
    use ExampleAwareTrait;

    public function run()
    {
        return $this->getExample()->doSomething();
    }
}

服务提供者

您可以通过将服务名放入提供者中进一步封装服务的通用名称。

use SD\DependencyInjection\ProviderInterface;

// This provides a correspondence between the service instance and its common name.
class ExampleProvider implements ProviderInterface
{
    public function getServiceName(): string
    {
        return 'example'; // the common name
    }

    public function provide()
    {
        return new ExampleService();
    }
}

然后,将服务与其提供者的实例注册

$container->connect(new ExampleProvider());

这种方式,注册代码和消费者都不需要知道通用名称。他们只需引用提供者实例和 AwareTrait,它就正常工作了。嘿,这就是魔法!

开发

要运行测试

composer install
vendor/bin/phpunit