nette/di

💎 Nette 依赖注入容器:灵活、编译过的完整功能 DIC,具有完美可用的自动装配和支持所有新的 PHP 功能。

维护者

详细信息

github.com/nette/di

主页

源代码

问题

安装: 32,250,565

依赖: 1,569

建议者: 75

安全: 0

星标: 866

关注者: 40

分支: 70

开放问题: 26


README

Nette Dependency Injection

Downloads this Month Tests Coverage Status Latest Stable Version License

 

介绍

依赖注入(DI)的目的是让类从获取其操作所需对象的责任中解放出来(这些对象称为 服务)。而是在它们实例化时传递这些服务。

Nette DI 是框架中最有趣的部分之一。它是一个编译过的 DI 容器,非常快且易于配置。

文档可以在 网站上找到

 

支持我

你喜欢 Nette DI 吗?你在期待新功能吗?

Buy me a coffee

谢谢!

 

安装

推荐安装方式是通过 Composer

composer require nette/di

它需要 PHP 版本 8.1,并支持 PHP 8.4。

 

用法

让我们有一个用于发送新闻通讯的应用程序。代码被最大限度地简化,并在 GitHub 上提供。

有一个代表电子邮件的对象

class Mail
{
	public string $subject;
	public string $message;
}

一个可以发送电子邮件的对象

interface Mailer
{
	function send(Mail $mail, string $to): void;
}

日志记录支持

interface Logger
{
	function log(string $message): void;
}

最后,一个提供发送新闻通讯的类

class NewsletterManager
{
	private Mailer $mailer;
	private Logger $logger;

	public function __construct(Mailer $mailer, Logger $logger)
	{
		$this->mailer = $mailer;
		$this->logger = $logger;
	}

	public function distribute(array $recipients): void
	{
		$mail = new Mail;
		$mail->subject = '...';
		$mail->message = '...';

		foreach ($recipients as $recipient) {
			$this->mailer->send($mail, $recipient);
		}
		$this->logger->log('...');
	}
}

代码遵循依赖注入原则,即 每个对象只使用我们传递给它的变量。

此外,我们还可以实现自己的 LoggerMailer,如下所示

class SendMailMailer implements Mailer
{
	public function send(Mail $mail, string $to): void
	{
		mail($to, $mail->subject, $mail->message);
	}
}

class FileLogger implements Logger
{
	private string $file;

	public function __construct(string $file)
	{
		$this->file = $file;
	}

	public function log(string $message): void
	{
		file_put_contents($this->file, $message . "\n", FILE_APPEND);
	}
}

DI 容器是最高建筑师,它可以创建单个对象(在 DI 术语中称为服务)并根据我们的需求组装和配置它们。

我们的应用程序的容器可能看起来像这样

class Container
{
	private ?Logger $logger;
	private ?Mailer $mailer;

	public function getLogger(): Logger
	{
		if (!isset($this->logger)) {
			$this->logger = new FileLogger('log.txt');
		}
		return $this->logger;
	}

	public function getMailer(): Mailer
	{
		if (!isset($this->mailer)) {
			$this->mailer = new SendMailMailer;
		}
		return $this->mailer;
	}

	public function createNewsletterManager(): NewsletterManager
	{
		return new NewsletterManager($this->getMailer(), $this->getLogger());
	}
}

实现看起来是这样,因为

  • 单个服务仅在需要时创建(延迟加载)
  • 双重调用 createNewsletterManager 将使用相同的日志记录器和邮件发送器实例

让我们实例化 Container,让它创建管理器,然后我们可以开始用新闻通讯轰炸用户了 :-)

$container = new Container;
$manager = $container->createNewsletterManager();
$manager->distribute(...);

对依赖注入来说重要的是,没有类依赖于容器。因此,它可以很容易地被另一个容器替换。例如,被 Nette DI 生成的容器。

 

Nette DI

Nette DI 是容器的生成器。我们通常用配置文件来指导它。这是配置文件,它导致生成与上面提到的 Container 类几乎相同的类

services:
	- FileLogger( log.txt )
	- SendMailMailer
	- NewsletterManager

优点是配置的简短性。

Nette DI 实际上生成了容器的 PHP 代码。因此,它非常快。开发者可以看到代码,因此他知道它正在做什么。他甚至可以跟踪它。

Nette DI 的用法非常简单。将(上述)配置保存到文件 config.neon,然后创建一个容器

$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp');
$class = $loader->load(function($compiler) {
    $compiler->loadConfig(__DIR__ . '/config.neon');
});
$container = new $class;

然后使用容器创建对象 NewsletterManager 并发送电子邮件

$manager = $container->getByType(NewsletterManager::class);
$manager->distribute(['[email protected]', ...]);

容器只生成一次,代码存储在缓存(在目录 __DIR__ . '/temp')中。因此,配置文件的加载放在 $loader->load() 中的闭包中,所以它只调用一次。

在开发期间,激活自动刷新模式非常有用,该模式可以在任何类或配置文件更改时自动重新生成容器。只需在 ContainerLoader 构造函数中将 true 作为第二个参数附加即可

$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', autoRebuild: true);

 

服务

服务在 DI 容器中注册,并且它们的依赖项会自动传递。

services:
	manager: NewsletterManager

此服务构造函数中声明的所有依赖项都将自动传递。构造函数传递是服务依赖注入的首选方式。

如果我们想通过setter传递依赖项,我们可以在服务定义中添加setup部分。

services:
	manager:
		factory: NewsletterManager
		setup:
			- setAnotherService

服务类

class NewsletterManager
{
	private AnotherService $anotherService;

	public function setAnotherService(AnotherService $service): void
	{
		$this->anotherService = $service;
	}

...

我们还可以添加inject: yes指令。此指令将启用自动调用inject*方法,并将依赖项传递给具有#[Inject]属性的公共变量。

services:
	foo:
		factory: FooClass
		inject: yes

依赖项Service1将通过调用inject*方法传递,依赖项Service2将分配给$service2变量。

use Nette\DI\Attributes\Inject;

class FooClass
{
	private Service1 $service1;

	// 1) inject* method:

	public function injectService1(Service1 $service): void
	{
		$this->service1 = $service1;
	}

	// 2) Assign to the variable with the #[Inject] attribute:

	#[Inject]
	public Service2 $service2;
}

然而,这种方法并不理想,因为变量必须声明为公共的,而且没有方法可以确保传递的对象是特定类型的。我们还失去了在代码中处理分配的依赖项的能力,并且违反了封装原则。

 

工厂

我们可以使用从接口生成的工厂。接口必须声明方法的返回类型。Nette将生成接口的正确实现。

接口必须恰好有一个名为create的方法。我们的工厂接口可以声明如下:

interface BarFactory
{
	function create(): Bar;
}

create方法将使用以下定义实例化一个Bar

class Bar
{
	private Logger $logger;

	public function __construct(Logger $logger)
	{
		$this->logger = $logger;
	}
}

工厂将在config.neon文件中注册。

services:
	- BarFactory

Nette将检查声明的服务是否为接口。如果是,它还将生成相应的工厂实现。定义也可以写成更详细的形式:

services:
	barFactory:
		implement: BarFactory

此完整定义允许我们使用argumentssetup部分声明对象的附加配置,类似于所有其他服务。

在我们的代码中,我们只需要获取工厂实例并调用create方法。

class Foo
{
	private BarFactory $barFactory;

	function __construct(BarFactory $barFactory)
	{
		$this->barFactory = $barFactory;
	}

	function bar(): void
	{
		$bar = $this->barFactory->create();
	}
}