divineniiquaye / rade-di
PHP 的简单智能依赖注入
Requires
- php: ^7.4 || ^8.0
- nette/utils: ^3.2
- psr/container: ^1.1|^2.0
- symfony/service-contracts: ^1.1.6|^2
Requires (Dev)
- composer/package-versions-deprecated: ^1.11
- divineniiquaye/php-invoker: ^0.9
- phpstan/phpstan: ^0.12
- phpstan/phpstan-strict-rules: ^0.12
- phpunit/phpunit: ^8.5 || ^9.5
- squizlabs/php_codesniffer: ^3.5
- symfony/config: ^5.2
- vimeo/psalm: ^3.11 || 4.4.*
Suggests
- nikic/php-parser: For using container builder class with service builders
- symfony/config: For using configurations in service providers and service builders
README
PHP Rade DI
divineniiquaye/rade-di 是一个用于在 PHP 7.4+ 应用程序中进行简单到复杂依赖注入的高性能智能工具,由 Divine Niiquaye 创建,参考了 Nette DI 和 Pimple。此库提供了一种高级方法来为您的应用程序解决服务,以获得最佳性能。
Rade DI 是在多次使用 Symfony DI 和 Nette DI 在多个项目后产生的。Nette 的自动注入功能比 symfony 的要简单得多。毫无疑问,它们都很好用,但我想有一个 DI,让我能专注于写代码而不是配置 DI。最初只是一个简单的容器,但后来成功地包含了大多数基本功能。
本项目遵守 行为准则。通过参与本项目及其社区,您应遵守此准则。
📦 安装 & 基本用法
本项目需要 PHP 7.4 或更高版本。推荐的安装方式是通过 Composer。只需运行
$ composer require divineniiquaye/rade-di
创建一个容器只需创建一个 Container
实例
use Rade\DI\Container; $container = new Container();
要将服务注册到容器中,服务必须是一个真实的有效 PHP 对象类型。容器实现了 PSR-11 ContainerInterface
和 ArrayAccess
,以下是一个示例来演示
使用没有
ArrayAccess
的容器
use function Rade\DI\Loader\{service, wrap}; // define some services $container->set('session_storage', new SessionStorage('SESSION_ID')); // or this for default autowiring typed support $container->autowire('session_storage', new SessionStorage('SESSION_ID')); $container->set('session', static fn(): Session => new Session($container['session_storage'])); // or $container->set('session', wrap(Session::class)); // or further for autowiring $container->set('session', service(Session::class))->autowire();
使用带有
ArrayAccess
的容器
use Rade\DI\Definition; // define some services $container['session_storage'] = new SessionStorage('SESSION_ID'); $container['session'] = fn(): Session => new Session($container['session_storage']); // or $container['session'] = new Definition(Session::class); // or $container['session'] = $container->call(Session::class); // or further $container['session'] = new Session($container['session_storage']);
使用定义的服务也非常简单
// get the session object $session = $container->get('session'); // or using ArrayAccess $session = $container['session']; // or use it's service class name, parent classes or interfaces $session = $container->get(Session::class); // the above call is roughly equivalent to the following code: $storage = new SessionStorage('SESSION_ID'); $session = new Session($storage);
容器支持可重用服务实例。这意味着,已解析的已注册服务被冻结,对象 ID 在整个应用程序使用 Rade DI 期间不会更改。
Rade DI 还支持自动注入,除非可调用项的返回类型没有定义,或者更好的是,如果您根本不想进行自动注入,请使用容器的 set 方法。默认情况下,使用 ArrayAccess
实现注册的服务都会自动注入。
use function Rade\DI\Loader\{service, reference}; $container['session'] = service(Session::class, [reference('session_storage')])->shared(false);
在上面的示例中,每次调用 $container['session']
都会返回一个新实例的会话。此外,Rade 还支持服务的别名和标签。如果您想为已注册的服务添加不同的名称,请使用 alias
方法。
$container['film'] = new Movie('S1', 'EP202'); $container->alias('movie', 'film'); // Can be access by $container['film'] or $container['movie']
对于标签,例如您正在构建一个报告聚合器,该聚合器接收一个包含许多不同的 Report
接口实现数组的报告。
$container['speed.report'] = new SpeedReport(...); $container['memory.report'] = new MemoryReport(...); $container->tags(['reports' => ['speed.report', 'memory.report']]); // or if autowired or not $container->tags(['reports' => [SpeedReport::class, MemoryReport::class]]);
一旦服务被标记,您可以通过 tagged
方法轻松解析它们
$tags = $container->tagged('reports'); $reports = []; foreach ($tags as $report => $attr) { $reports[] = $report; } $manager = new ReportAggregator($reports); // For the $attr var, this is useful if you need tag to have extra values. eg: $container->tags(['process' => [BackupProcessor::class, MonitorProcessor::class, CacheProcessor::class => false]]); foreach ($container->tagged('process') as $process => $enabled) { if ($enabled) { $manager->addProcessor($container->get($process)); } }
自PHP 8发布以来,容器支持通过名为#[Inject]
的属性向公开类属性和公开类方法注入服务。如果没有为属性提供值,它将使用公开类属性或公开类方法的类型声明。
出于性能考虑,此功能仅限于实现Rade\DI\Injector\InjectableInterface
的类,并且可以使用容器的调用方法或容器解析器的resolveClass方法进行解析。
例如:通过调用injectService1
方法传递依赖项Service1
,依赖项Service2
将被分配给$service2
属性。
use Rade\DI\Attribute\Inject; use Rade\DI\Injector\InjectableInterface; class FooClass implements InjectableInterface { #[Inject] public Service2 $service2; private Service1 $service1; #[Inject] public function injectService1(Service1 $service) { $this->service1 = $service1; } public function getService1(): Service1 { return $this->service1; } }
在PHP的8 #[Inject]属性之前,rade di支持使用phpdoc类型自动装配,并且计划在PHP 8.2发布后继续支持。#[Inject]属性是一种高级自动装配,只要值可以被容器解析,它就不管。
Rade Di支持扩展,这使得容器可扩展和可重用。使用Rade DI,您的项目不需要过度依赖PSR-11容器,使用服务提供者在项目中可以节省很多。
use Rade\DI\Container; class FooProvider implements Rade\DI\Extensions\ExtensionInterface { /** * {@inheritdoc} */ public function register(AbstractContainer $container, array $configs = []): void { // register some services and parameters // on $container } }
然后,在容器上注册提供者。
$container->register(new FooProvider());
服务提供者支持Symfony的配置组件来编写服务定义的配置。实现服务提供者类以实现Symfony\Component\Config\Definition\ConfigurationInterface
。
默认情况下,编写服务提供者的配置,服务提供者类的名称成为指向所需配置数据的键。如果要使用自定义键名,请添加一个静态的getId
方法返回您的自定义键名。
强烈建议使用Symfony的配置组件 +
Rade\DI\ContainerBuilder
类。
$ composer require symfony/config
此外,Rade\DI\ServiceLocator
类旨在设置预定义服务,同时仅在需要时才实例化它们。
对于服务定位器,Rade使用symfony的服务契约。
它还允许您在不同的命名下提供服务。例如,您可能希望使用一个期望EventDispatcherInterface
实例的名称为event_dispatcher
的对象,而您的事件调度器已注册为名称dispatcher
。
use Monolog\Logger; use Rade\DI\ServiceLocator; use Psr\Container\ContainerInterface; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Contracts\Service\ServiceSubscriberInterface; class MyService implements ServiceSubscriberInterface { /** * "logger" must be an instance of Psr\Log\LoggerInterface * "event_dispatcher" must be an instance of Symfony\Component\EventDispatcher\EventDispatcherInterface */ public function __construct(private ServiceProviderInterface $container = null) { } /** * {@inheritdoc} */ public static function getSubscribedServices(): array { return ['logger', 'event_dispatcher' => 'dispatcher']; } } $container['logger'] = new Monolog\Logger(); $container['dispatcher'] = new EventDispatcher(); $container['service'] = MyService::class;
📓 文档
在开始使用此库之前,请参阅详细的文档。有关高级使用、配置和定制的完整文档,请访问docs.divinenii.com。
⏫ 升级
有关如何升级到此库的新版本的信息,请参阅UPGRADE。
🏷️ 更新日志
严格遵循SemVer。次要和补丁版本不应引入代码库的破坏性更改;有关最近更改的更多信息,请参阅CHANGELOG。
任何标记为@internal
的类或方法都不建议在库外部使用,并且可能在任何时间进行破坏性更改,因此请避免使用它们。
🛠️ 维护与支持
(此政策可能在未来发生变化,并且可能根据情况做出例外。)
- 新的
补丁版本
(例如1.0.10
,1.1.6
)大约每月发布一次。它仅包含错误修复,因此您可以安全地升级您的应用程序。 - 新的
次要版本
(例如1.1
,1.2
)每六个月发布一次:一次在六月,一次在十二月。它包含错误修复和新功能,但不包括任何破坏性更改,因此您可以安全地升级您的应用程序。 - 每两年会发布一个新的主要版本(例如
1.0
,2.0
,3.0
)。它可能包含破坏性更改,因此您在升级之前可能需要在您的应用程序中进行一些更改。
当一个主要版本发布时,每个分支(X.0,X.1,X.2,X.3 和 X.4)的次要版本数量限制为五个。分支的最后一个次要版本(例如 1.4,2.4)被视为具有超过 2 年生命周期的长期支持(LTS)版本,其他版本可维持长达 8 个月。
在已发布版本的活跃维护结束后,从Biurad Lap获取专业支持。.
🧪 测试
$ ./vendor/bin/phpunit
这将测试 divineniiquaye/rade-di 是否可以运行在 PHP 7.4 版本或更高版本上。
🏛️ 管理机构
此项目主要由Divine Niiquaye Ibok维护。欢迎贡献 👷♀️!要贡献,请熟悉我们的贡献指南。
要报告安全漏洞,请使用Biurad 安全。我们将协调修复,并最终将解决方案提交到本项目。
🙌 赞助商
您有兴趣赞助此项目的开发吗?请与我们联系并在Patreon上支持我们,或查看https://biurad.com/sponsor以了解贡献的方式。
👥 致谢与认可
📄 许可证
divineniiquaye/rade-di 库版权所有 © Divine Niiquaye Ibok,并许可在下使用。