them / container
基于 Autowiring 的 DI 容器,是 pimple/pimple 的替代品
Requires
- php: >=8.2
- pimple/pimple: ^3.5
- psr/container: ^2.0
- them/attributes: ^1.1
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.52
- jetbrains/phpstorm-attributes: ^1.0
- phpstan/extension-installer: ^1.3
- phpstan/phpstan: ^1.10
- phpstan/phpstan-strict-rules: ^1.5
- phpunit/phpunit: ^11.0
README
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 获取一个未知的服务时,它会尝试实例化它。
当然,这只能在以下情况下工作:
- 提供的 ID 是一个现有类的名称
- 该类可以实例化(即不是抽象的,不是接口)
- 构造函数可以被调用(即不是私有的/受保护的)
- 每个构造函数参数都有一个类型分配(不是
__construct($value)
) - 每个参数类型都可以通过其名称由容器解决。
构造函数注入
给定一个类
<?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
实例,还可以提供上述类中的一个类名。容器将尝试自动装配和注册它们。