# 獲取依賴關系
向控制器,組件和服務注入依賴性有幾種可能性。 本文討論:
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
- 原子操作