PHP 的简单智能依赖注入

v0.2.3 2021-03-18 07:34 UTC

README

PHP Rade DI

PHP Version Latest Version Workflow Status Code Maintainability Coverage Status Psalm Type Coverage Quality Score

divineniiquaye/rade-di 是一个用于在 PHP 7.4+ 应用程序中进行简单到复杂依赖注入的高性能智能工具,由 Divine Niiquaye 创建,参考了 Nette DIPimple。此库提供了一种高级方法来为您的应用程序解决服务,以获得最佳性能。

Rade DI 是在多次使用 Symfony DINette 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 ContainerInterfaceArrayAccess,以下是一个示例来演示

使用没有 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.101.1.6)大约每月发布一次。它仅包含错误修复,因此您可以安全地升级您的应用程序。
  • 新的次要版本(例如 1.11.2)每六个月发布一次:一次在六月,一次在十二月。它包含错误修复和新功能,但不包括任何破坏性更改,因此您可以安全地升级您的应用程序。
  • 每两年会发布一个新的主要版本(例如 1.02.03.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,并许可在Software License下使用。