# 获取依赖关系
向控制器,组件和服务注入依赖性有几种可能性。 本文讨论:
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();
}
}
~~~
构造器依赖项将自动传递到创建的控件。
- Nette简介
- 快速开始
- 入门
- 主页
- 显示文章详细页
- 文章评论
- 创建和编辑帖子
- 权限验证
- 程序员指南
- MVC应用程序和控制器
- URL路由
- Tracy - PHP调试器
- 调试器扩展
- 增强PHP语言
- HTTP请求和响应
- 数据库
- 数据库:ActiveRow
- 数据库和表
- Sessions
- 用户授权和权限
- 配置
- 依赖注入
- 获取依赖关系
- DI容器扩展
- 组件
- 字符串处理
- 数组处理
- HTML元素
- 使用URL
- 表单
- 验证器
- 模板
- AJAX & Snippets
- 发送电子邮件
- 图像操作
- 缓存
- 本土化
- Nette Tester - 单元测试
- 与Travis CI的持续集成
- 分页
- 自动加载
- 文件搜索:Finder
- 原子操作