轻量级依赖注入容器

1.2.0 2024-04-03 14:26 UTC

README

Build Status Latest Version on Packagist Software License

此包是一个轻量级的、与框架无关的依赖注入容器。

要求

PHP 8.2 或更高版本。

安装

composer require stefna/di

使用

由于我们不进行任何自动自动装配,您将始终需要使用 ContainerBuilder 来创建 Container,因此您需要定义所有您想要访问的内容。

配置容器

要配置容器,请向容器构建器添加 DefinitionSource

<?php

use Stefna\DependencyInjection\ContainerBuilder;

$builder = new ContainerBuilder();
$builder->addDefinition([
	ClockInterface::class => fn () => new Clock(),
]);

$container = $builder->build();

$clock = $container->get(ClockInterface::class);

批量配置定义

我们提供了两种可以用于批量定义内容的方法。

首先,您有 FilterDefinition,它允许您提供任何任意的过滤器,该过滤器将与请求的类进行匹配。如果匹配,它将获得定义。

示例

<?php

use Stefna\DependencyInjection\Definition\FilterDefinition;
use Stefna\DependencyInjection\Helper\Autowire;

// this will auto-wire all class that ends with Controller
$definition = new FilterDefinition(
	fn (string $className) => str_ends_with('Controller'),
	fn (string $className) => Autowires::cls($className)
);

我们还提供了一个 NamespaceFilterDefinition,它使批量定义特定命名空间中的所有内容变得更加容易。

NamespaceFilterDefinition 仅扩展 FilterDefinition 并使其更容易与命名空间一起使用。

示例

<?php

use Stefna\DependencyInjection\Definition\NamespaceFilterDefinition;

// this will auto-wire all class that are in App\Controller namespace
$controllerDefinitions = NamespaceFilterDefinition::autoWire('App\\Controller\\');
// this will just do a simple new $className() on all classes in App\RequestInput namespace
$requestInputDefinitions = NamespaceFilterDefinition::create('App\\RequestInput\\');

如果您打算使用此功能为许多类提供自动装配,我建议扩展 FilterDefintion 并在定义上实现 PriorityAware,返回 Priority::Low 以轻松覆盖“默认”定义。

与其他容器实现一起使用

<?php

use Stefna\DependencyInjection\ContainerBuilder;

$builder = new ContainerBuilder();
$builder->addContainer($externalContainer);

$container = $builder->build();

$clock = $container->get(ClockInterface::class);

自动装配

我们提供了一些轻量级的自动装配辅助工具。

自动装配助手只会在容器中查找依赖项,它不会尝试自动创建不属于容器的对象。

<?php

use Stefna\DependencyInjection\ContainerBuilder;
use Stefna\DependencyInjection\Helper\Autowire;

interface A {}

class Obj implements A
{
	public function __construct(public readonly ClockInterface $clock)
	{}
}

$builder = new ContainerBuilder();
$builder->addDefinition([
	ClockInterface::class => fn () => new Clock(),
	Obj::class => Autowire::cls(),
	A::class => Autowire::cls(Obj::class),
]);

$container = $builder->build();

$clock = $container->get(ClockInterface::class);

属性

您可以使用属性增强自动装配。

自动装配助手默认只从容器中获取对象。

我们支持两种属性接口

  • ResolverAttribute 可以用于从容器中解析复杂值
  • ConfigureAttribute 可以用于在注入到类之前重新配置对象
ResolverAttribute

当您想要从类似配置存储之类的源解析标量值时非常有用。

#[\Attribute(\Attribute::TARGET_PARAMETER)]
final class ConfigValue implements ResolverAttribute
{
	public function __construct(private readonly string $key) {}

	public function resolve(string $type, ContainerInterface $container): mixed
	{
		$config = $container->get(Config::class);
		return $config->get($this->key);
	}
}

final class Test
{
	public function __construct(
		#[ConfigValue('site.config.value')]
		private readonly string $configValue,
	) {}
}

ConfigureAttribute

当您想要重新配置将被注入的内容时非常有用,例如设置类的自定义日志通道。

#[\Attribute(\Attribute::TARGET_PARAMETER)]
final class LogChannel implements ConfigureAttribute
{
	public function __construct(private readonly string $channel) {}

	public function configure(object $object, ContainerInterface $container): object
	{
		if ($object instanceof LoggerInterface && class_exists(ChannelWrapper::class)) {
			return new ChannelWrapper($object, $this->channel);
		}
		if ($container->has(LoggerManager::class)) {
			return $container->get(LoggerManager::class)->createLogger($this->channel);
		}
		
		// don't know how to add channel just return the incoming logger
		return $object;
	}
}

final class Test
{
	public function __construct(
		#[LogChannel('test-channel')]
		private readonly LoggerInterface $logger,
	) {}
}

工厂

在定义中的所有内容实际上都是工厂。

但我们提供了一个工厂助手,可以帮助去重工厂实例和延迟实例化工厂。

<?php

use Stefna\DependencyInjection\Helper\Factory;

class ObjFactory
{
	public function __invoke(ContainerInterface $container)
	{
		return new Obj($container->get(ClockInterface::class));
	}
}

class ComplexFactory
{
	public function __invoke(ContainerInterface $container, string $className)
	{
		if ($className === A::class) {
			return new Obj($container->get(ClockInterface::class));
		}
	}
}

class AutowiredComplexFactory
{
	public function __construct(
		private ClockInterface $clock,
	) {}

	public function __invoke()
	{
		if ($className === B::class) {
			return new Obj($this->clock);
		}
	}
}

$builder->addDefinition([
	ObjFactory::class => fn () => new ObjFactory(),
	Obj::class => Factory::simple(ObjFactory::class),
	ObjFactory::class => fn () => new ComplexFactory(),
	A::class => Factory::full(ComplexFactory::class),
	B::class => Factory::autoWire(AutowiredComplexFactory::class),
]);

自动装配工厂

有两种方法可以自动装配工厂

选项 1:将其添加到容器中,就像其他任何内容一样

use Stefna\DependencyInjection\Helper\Autowire;
use Stefna\DependencyInjection\Helper\Factory;

$builder->addDefinition([
	ComplexFactory::class => Autowire::cls(),
	// or 
	ComplexFactory::class => fn () => new ComplexFactory(new Clock()),
	
	// looks up ComplexFactory from container and if it's not defined crashes
	A::class => Factory::full(ComplexFactory::class),
]);

选项 2:使用工厂自动装配助手

use Stefna\DependencyInjection\Helper\Autowire;
use Stefna\DependencyInjection\Helper\Factory;

$builder->addDefinition([
	A::class => Factory::autoWire(ComplexFactory::class),
]);

这只是对选项 1 的一个简单包装

贡献

我们始终欢迎收到错误/安全报告和错误/安全修复。

许可

MIT 许可证 (MIT)。请参阅 许可证文件 了解更多信息。