ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 获取依赖关系 向控制器,组件和服务注入依赖性有几种可能性。 本文讨论: 1、一般依赖注入,不限于Nette DI Container中的依赖关系,以及 2、控制器,组件和服务的实际示例和建议。 ## 如何获取依赖关系? 可以通过以下方式之一将依赖注入到应用程序类中: * 通过类的构造函数, * 通过setter或成员变量, * 通过inject *方法, * 在公共成员变量上使用@inject注释。 ******前两种方式可以用于所有面向对象的编程语言,最后两种可用在Nette框架中。 让我们通过所有这些方法,然后在实际的例子中展示他们的应用。****** ### 构造函数传递 所有依赖关系在创建对象时传递。 依赖项声明为构造函数参数,其类型在类型提示中给出: ~~~ class MyService { /** @var AnotherService */ private $anotherService; public function __construct(AnotherService $service) { $this->anotherService = $service; } } ~~~ MyService类声明一个AnotherService类的实例必须在对象创建期间传递。 此声明适用于所有强制依赖性,这是类的运行所必需的。 没有这些依赖关系,不能实例化类。 ### 通过Setter或公共变量传递 这些依赖关系在对象创建后传递。 看看通过setter方法传递依赖的例子。 ~~~ class MyService { /** @var AnotherService */ private $anotherService; public function setAnotherService(AnotherService $service) { $this->anotherService = $service; } } ~~~ 这些依赖关系只能在对象创建后传递。 此方法仅适用于非强制依赖项,因为不能保证依赖项将传递到对象。 传递一个公共变量的工作方式非常相似: ~~~ class MyService { /** @var AnotherService */ public $anotherService; } ~~~ 但是,不建议使用此方法,因为变量必须声明为public,并且没有办法如何确保传递的对象将是给定类型。 我们也失去了在我们的代码中处理所分配的依赖性的能力,并且我们违反了封装的原理。 ### 通过注入*方法 此方法特定于Nette框架中的DI容器。 它是setter方法的一种特殊情况,它以inject *前缀开始。 ~~~ class MyService { /** @var AnotherService */ private $anotherService; public function injectAnotherService(AnotherService $service) { $this->anotherService = $service; } } ~~~ 该类可以包含多个inject *方法,每个方法都有多个参数和一个唯一的名称。 Nette能够在控制器中找到这些方法,并自动使用适当的参数调用它们。 也可以在配置文件中的服务中启用此行为。 这将在后面说明。 ### @inject注释 第二种方法特定于Nette 框架。 它是一个通过公共变量传递的特殊情况。 变量在docblock注释中由@inject注释标记: ~~~ class MyService { /** @inject @var \App\AnotherService */ public $anotherService; } ~~~ Nette框架还可以扫描这些变量的呈现器并自动注入这些依赖项。 @var注释中的变量类型必须由其完全限定名称(包括命名空间)给出。 使用指令中定义的别名可以从版本2.2开始使用。 这种方法具有与普通传递公共变量相同的缺点 - 我们不能强制传递依赖的类型。 然而,代码非常简单和短,这在某些情况下可能是一个优势。 ### 我应该选择哪种方式? 通过setter传递和传递可选依赖项的构造方法可用于所有实例化的类。 以下两种技术,通过inject *方法传递依赖性以及由@inject注释标记的公共变量,是较不干净的技术,并且仅在具有显式配置的DI容器中的控制器和服务中可用。 我们仅在少数特定情况下使用它们。 所有这些方法有一个共同点是,自动接线仅适用于由Nette DI Container或Nette中的一个工厂创建的对象。 当我们通过调用new创建对象时,我们必须手动传递依赖。 让我们来看看依赖注入的例子和优选方法。 ### 控制器 Nette框架中的控制器是由PresenterFactory创建。 这个工厂还自动注入依赖关系声明: 1、在构造函数中, 2、使用inject *方法, 3、通过公共属性与@inject注释。 下面的控制器展示了依赖传递的所有三种方式: ~~~ class MyPresenter extends Nette\Application\UI\Presenter { // 1) 构造函数传递: private $service1; public function __construct(Service1 $service) { $this->service1 = $service; } // 2) 使用inject *方法: private $service2; public function injectService2(Service2 $service) { $this->service2 = $service; } // 3)带@inject注释的公共变量: /** @inject @var \App\Service3 */ public $service3; } ~~~ 传递控制器的依赖项的首选方法是使用构造函数,或者在父控制器使用inject *方法的情况下。 在父呈现者中使用inject *方法允许我们保留封装,并保持构造子对子呈现者是干净的。 我们也可以使用@inject注释的两种情况,但我们必须记住,这打破了封装。 不建议通过构造函数将依赖传递给父提交者,因为它在使用提交者继承时使构造函数签名变得复杂:您必须获取并传递依赖关系到所有父提交者类。 ## 组件 组件通常直接在控制器的代码中实例化,或通过应用程序特定的工厂实例化。 在这些情况下,Nette不能自动注入依赖项,并且不能使用inject *方法或变量与@inject注释。 让我们考虑我们有以下组件: ~~~ class MyControl extends Nette\Application\UI\Control { // 1) 构造函数传递: private $service1; public function __construct(Service1 $service) { parent::__construct(); $this->service1 = $service; } // 2) setter注入的可选依赖关系: private $service2; public function setService2(Service2 $service) { $this->service2 = $service; } } ~~~ 该组件可以以下列方式用于控制器: ~~~ class MyPresenter extends Nette\Application\UI\Presenter { /** @inject @var \App\Service1 */ public $service1; /** @inject @var \App\Service2 */ public $service2; protected function createComponentMyControl() { $control = new MyControl($this->service1); $control->setService2($this->service2); return $control; } } ~~~ 因为依赖关系不是由DI容器自动注入的,所以我们必须在类中获取所有需要的依赖关系,这将创建我们的组件 - 在我们的示例中的控制器。 如果我们在另一个组件中创建组件,组件依赖项将被添加到父组件的依赖项: ~~~ class MySecondControl extends Nette\Application\UI\Control { // MySecondControl的依赖关系: private $service3; // 子类的依赖MyControl: private $service1; private $service2; public function __construct(Service1 $service1, Service2 $service2, Service3 $service3) { parent::__construct(); //向成员分配依赖关系$service1, $service2, $service3 } protected function createComponentMyControl() { $control = new MyControl($this->service1); $control->setService2($this->service2); return $control; } } ~~~ 因为我们通常手动实例化组件,依赖注入的首选方式取决于依赖是强制还是可选。 构造函数应该用于强制依赖和setter为可选。如果我们使用构造函数进行依赖传递,我们不能忘记从父类中调用构造函数:parent :: __ construct()! ## 服务 服务被注册在DI容器中,因此依赖性被自动传递。 我们只能在构造函数中声明依赖关系,除非我们提供额外的配置: ~~~ services: service1: App\Service1 ~~~ 在此服务的构造函数中声明的所有依赖项都将自动传递: ~~~ namespace App; class Service1 { private $anotherService; public function __construct(AnotherService $service) { $this->anotherService = $service; } } ~~~ 构造函数传递是服务的依赖注入的首选方式。 如果我们想通过setter传递依赖,我们可以添加setup部分到服务定义: ~~~ services: service2: class: App\Service2 setup: - setAnotherService ~~~ 服务类别: ~~~ namespace App; class Service2 { private $anotherService; public function setAnotherService(AnotherService $service) { $this->anotherService = $service; } } ~~~ 我们还可以添加inject:yes指令。 这个指令将启用自动调用inject *方法和使用@inject注释将依赖项传递给公共变量: ~~~ services: service3: class: App\Service3 inject: yes ~~~ 依赖Service1将通过调用inject *方法传递,依赖Service2将被分配给$ service2变量: ~~~ namespace App; class Service3 { // 1) inject* method: private $service1; public function injectService1(Service1 $service) { $this->service1 = $service1; } // 2) Assign to the variable with the @inject annotation: /** @inject @var \App\Service2 */ public $service2; } ~~~ 其他可能性 还有一些其他的可能性,我们如何可以改变控制器和组件的配置和依赖注入。 ### 控制器即服务 从Nette 2.1开始,您可以将控制器注册为配置文件的服务。 它将作为DI容器中的任何其他服务创建。 您可以传递任何不能自动连接的参数(字符串,数字等),并添加setter调用。 所有inject *方法将被自动调用,并且所有的依赖关系将被自动分配给带有@inject注释的公共变量。 你不必添加inject:yes指令。 配置文件中的控制器定义可以如下所示: ~~~ services: - App\Presenters\ImagePresenter("%wwwDir%/media") ~~~ ~~~ class ImagePresenter extends Nette\Application\UI\Presenter { private $imageDir; private $optimizer; public function __construct($imageDir, ImageOptimizer $optimizer) { $this->imageDir = $imageDir; $this->optimizer = $optimizer; } } ~~~ 来自配置的字符串将作为构造函数的第一个参数传递,其余参数将自动连接。 但是,在设计控制器时必须小心。 所有应用程序逻辑应在服务中,而不是在控制器中。 对附加呈现者配置的需要通常是挑战的不良分解的标志。 ## 组件工厂 像控制器一样,组件也可以在配置文件中注册。 然而,控制器通常在处理单个请求期间仅创建一次,而组件可以在多个位置被实例化。 因此,我们必须注册一个组件工厂,而不是一个服务。 从Nette 2.1开始,我们可以使用从接口生成的工厂。 接口必须在方法的@return注释中声明返回类型。 Nette将生成适当的实现接口。 接口必须只有一个名为create的方法。 我们的组件工厂接口可以通过以下方式声明: ~~~ namespace App\Components; interface IUserTableFactory { /** * @return UserTable */ public function create(); } ~~~ create方法将使用以下定义实例化UserTable组件: ~~~ amespace App\Components; class UserTable extends Control { private $userManager; public function __construct(UserManager $userManager) { $this->userManager = $userManager; } } ~~~ 工厂将在config.neon文件中注册: ~~~ services: - App\Components\IUserTableFactory ~~~ Nette将检查所声明的服务是否是一个接口。 如果是,它还会生成相应的实现工厂。 定义也可以写成更详细的形式: ~~~ services: userTableFactory: implement: App\Components\IUserTableFactory ~~~ 这个完整的定义允许我们使用参数和设置部分声明组件的附加配置,类似于所有其他服务。 在控制器中,我们只需要获取工厂实例并调用create方法: ~~~ class UserPresenter extends Nette\Application\UI\Presenter { /** @var \App\Components\IUserTableFactory @inject */ public $userTableFactory; protected function createComponentUserTable() { return $this->userTableFactory->create(); } } ~~~ 构造器依赖项将自动传递到创建的控件。