them/container

基于 Autowiring 的 DI 容器,是 pimple/pimple 的替代品

2.0.0 2024-03-20 16:35 UTC

This package is auto-updated.

Last update: 2024-09-20 17:52:59 UTC


README

them/container on packagist GPLv3

them/container

them/container 是 PHP >= 8.1 的依赖注入容器。

基本上,它结合了 Pimple 的自动装配功能和 psr/container 接口。

安装

在您的项目中使用 them/container 之前,将其添加到您的 composer.json 文件中

composer require them/container

使用方法

如果您还没有使用 Pimple,请先阅读 https://github.com/silexphp/Pimple#readme

them/container 是 Pimple 的替代品。在创建容器时,使用类 \Them\Container\Container 而不是 Pimple\Container

如果您使用服务提供者 (https://github.com/silexphp/Pimple#extending-a-container),您可以选择使它们实现 \Them\Container\ServiceProviderInterface 而不是 \Pimple\ServiceProviderInterface。在这种情况下,方法 register 将接收一个 \Them\Container\Container 实例而不是 \Pimple\Container 实例。

Pimple 的更改

PSR-11

与 Pimple 相比,them/container 默认符合 PSR-11。因此,无需执行类似以下操作:

$container = new \Pimple\Container();
$psr11 = new \Pimple\PsrContainer($container);

注册服务

除了 Pimple 的注册服务方式外,\Them\Container\Container 还提供了以下方法:

set(string $id, mixed $value): self

预注册服务

在初始化时,容器实例已经注册了一个服务,该服务可以在以下两个 ID 下使用,即 \Them\Container\Container::class\Psr\Container\ContainerInterface::class,其中两者都指向容器实例本身。

服务别名

有时您需要使用多个键注册一个服务。考虑一个需要同时在 \Psr\Log\LoggerInterface\Monolog\Logger 下使用的 logger。


declare(strict_types=1);

use Psr\Log\LoggerInterface;
use Monolog\Logger;
use Them\Container\Container;

$container = new Container();

$container[Logger::class] = fn() => new Logger('logger');

$container->aka(Logger::class, LoggerInterface::class);

自动装配

当尝试从 them/container 中通过 ID 获取一个未知的服务时,它会尝试实例化它。

当然,这只能在以下情况下工作:

  1. 提供的 ID 是一个现有类的名称
  2. 该类可以实例化(即不是抽象的,不是接口)
  3. 构造函数可以被调用(即不是私有的/受保护的)
  4. 每个构造函数参数都有一个类型分配(不是 __construct($value)
  5. 每个参数类型都可以通过其名称由容器解决。

构造函数注入

给定一个类

<?php

declare(strict_types=1);

use Psr\Log\LoggerInterface;

final readonly class SomeService
{
    public function __construct(
        private LoggerInterface $logger,
    ) {}
}

如果您调用 $container->get(SomeService::class),容器将搜索 ID \Psr\LoggerInterface 来解决参数 $logger 的值 - 如果没有注册此类键,并且由于接口不能被实例化,它会失败。

要告诉容器使用哪个键,只需将属性 \Them\Container\Attribute\Constructor 添加到服务类中

<?php

declare(strict_types=1);

use Monolog\Logger;
use Psr\Log\LoggerInterface;
use Them\Container\Attribute\Constructor;

#[Constructor(['logger' => Logger::class])]
final readonly class SomeService
{
    public function __construct(
        private LoggerInterface $logger,
    ) {}
}

在这种情况下,容器将使用键 \Monolog\Monolog 来解决参数 $logger

设置器注入

有时您需要通过设置器方法注入服务,例如使用 \Psr\Log\LoggerAwareInterface 时的 logger。

要实现这一功能,您需要向服务类中添加一个或多个Them\Container\Attribute\Method属性,告诉容器调用通过参数类型解析的参数值的函数。如果您需要覆盖此设置,请使用属性的第二个参数为参数分配一个键。

<?php

declare(strict_types=1);

use Monolog\Logger;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Scn\Container\Attribute\Method;

#[Method('setLogger', ['logger' => Logger::class])]
final class SomeService implements LoggerAwareInterface
{
    protected ?LoggerInterface $logger = null;

    public function setLogger(LoggerInterface $logger): void
    {
        $this->logger = $logger;
    }
}

解析依赖到接口(或其他不可实例化的类)

如果您正在针对接口进行编码,那么每次您通过接口名称请求服务时,都必须告诉容器要使用哪个实现。这可以通过别名(见上文)来实现,也可以通过直接在接口上添加具有“真实”类作为参数的属性\Them\Container\Attribute\Specific来实现。

<?php

declare(strict_types=1);

use Them\Container\Attribute\Specific;
use Them\Container\Container;

require_once __DIR__ . '/../vendor/autoload.php';

#[Specific(Service::class)]
interface ServiceInterface {}

final class Service implements ServiceInterface {}

$c = new Container();
var_dump($c->get(ServiceInterface::class));

如果您现在通过ID ServiceInterface 向容器请求服务,它将实例化 Service 并返回该实例。

自动装配服务提供者

当将服务提供者注册到容器时,您不仅可以提供一个 \Pimple\ServiceProviderInterface\Them\Container\ServiceProviderInterface 实例,还可以提供上述类中的一个类名。容器将尝试自动装配和注册它们。