#依賴注入
依賴注入(DI)的目的是使類免除獲取其操作所需的對象的責任(這些對象被稱為服務)。 要在他們的實例化上傳遞這些服務。 我們在這章學到以下幾點:
* 依賴注入的原理是什么。
* 如何創建動態和靜態DI容器。
* 如何延遲加載服務。
什么是依賴注入?
依賴注入(DI)是沒有什么神秘或令人費解的。 它可以被理解為一個自私的句子:“不要尋求任何東西,讓別人做它”。現在讓我們把它翻譯成程序員的演講。 我們有一個博客里面有一個Article類:
~~~
class Article
{
public $id;
public $title;
public $content;
function save()
{
// 我們將數據保存到數據庫中
}
}
~~~
我們可以這樣使用這個類
~~~
$article = new Article;
$article->title = '10 Things You Need to Know About Losing Weight';
$article->content = 'Every year millions of people in ...';
$article->save();
~~~
save()方法是將要把文章保存到articles數據表中,它使用Nette\Database很容易實現。但是怎樣Article得到數據庫連接。 即使用Connection類。
嗯,我們可以將它放入一些全局變量中,如$ GLOBALS ['connection']或類的一些靜態成員。 但是你沒有聽說過使用全局變量是錯誤的嗎? 這是真的,全局變量是邪惡的,靜態成員也是一樣的。
那么在我們怎樣得到數據庫連接? DI有答案:“不要尋找任何東西,讓別人做它。”換句話說,如果我需要一個數據庫,有人給我,這不是我的工作。 哈,它狡猾,親愛的DI! 我們開始做吧:
~~~
class Article
{
public $id;
public $title;
public $content;
private $connection;
function __construct(Nette\Database\Connection $connection)
{
$this->connection = $connection;
}
function save()
{
$this->connection->query('INSERT INTO articles', [
'title' => $this->title,
'content' => $this->content,
]);
}
}
~~~
使用Article類將稍有改變:
~~~
$article = new Article($connection);
$article->title = ...
$article->content = ...
$article->save();
~~~
你在問,這個代碼需要$connection? DI給出一個直接的答案:“讓別人做它。”數據庫連接將由調用代碼的人提供。 等等,等等。 當然你說,不可能使責任的外交。 必須有一個零點。 你是對的。 開始有一個創造者,他沒有委托任何東西,他創造了對象。 我們稱之為系統容器。 他有一個單獨的章節。
以前學過對象編程的人不難看懂,但是沒有學過的話有點難了。實際上上面意思是做一個 Article對象,他有一個保存數據方法,但是在保存數據之前要連接數據,但是要怎樣連接數據呢,所以我們做一個$connection默認對象,如果有人要使用Article類,那他就要實例化它,程序就會自動注入$connection默認對象。有些象做一個電腦配置單,然后按配置單采購配件,再組裝起來,系統就是依賴注入,這樣才能成為一臺完整電腦出來,但可以按配置單組很多臺電腦,每臺電腦都有系統。
# 為什么全局變量是錯誤的呢?
好問題。 Article類無論如何都需要數據庫連接。 但從第一個例子,沒有顯示出來,從哪里和如何得到它。 這樣的代碼的用戶可能會感到驚訝,文章真的節省了,他問:“它在哪里保存?”第二個例子使用DI,代碼是自我解釋。
想象一下,你正在探索一些支付網關,你寫一個例子:
~~~
$cc = new CreditCard('4461510140804839', 12, 2013);
$cc->pay(1000, CreditCard::DOLLARS);
~~~
您運行的代碼,與您的卡號,后來你會發現,它真的從您的帳戶扣了錢! 震驚你盯著列表和哀嘆:“我的錢在哪里,怎么會發生,我沒有配對它任何支付網關!”類CreditCard自己做了,發現一個在一些全局變量中詞,神秘,如Article得到了數據庫連接。 這樣的事情你不是從代碼中推導出來的,你甚至不知道如何將網關改為另一個,比如測試。
# DI意味著更多的寫作
你可以實例化對象,使用DI意味著更多的寫作,那就是創建一個Article的實例,你必須處理數據庫連接等等。 這是真的,但不要忘記最后一次,當“少寫作”花費你$ 1000! 不,我們不想放松。 異常是正確的,我們將添加一個更大:當我們發現需要緩存一些數據,與DI協調,它將需要一個參數與緩存存儲庫。 這意味著在許多地方調整應用程序:至少在任何地方,其中Article要被實例化。
現在怎么辦? 事情有一個解決方案:而不是手動創建Article對象,我們做一個工廠,即。 函數創建這些文章對象。 當Article更改構造函數時,只有工廠必須更新,沒有更多。 和在哪里得到工廠在我們的代碼? 你知道...讓別人做。 :-)
# DI容器和服務
術語“DI容器”是指工廠。 更確切地說,它是一個對象,包含任何數量的工廠,每個服務一個。 什么是服務? 普通對象,比如說Connection實例。 只是用DI容器我們稱之為服務。 也許一些顧問發明它使DI看起來很復雜,所以他們可以咨詢。
示例可以是創建Article對象的容器,也可以是所需的數據庫連接::
~~~
class Container
{
function createConnection()
{
return new Nette\Database\Connection('mysql:', 'root', '***');
}
function createArticle()
{
return new Article($this->createConnection());
}
}
~~~
用法如下:
~~~
$container = new Container;
$article = $container->createArticle();
~~~
優點是顯而易見的,我們不需要關心這篇文章究竟是如何實例化的,這是工廠的工作。 無論如何,解決方案仍有兩個缺點。 首先,有條目數據連接到代碼,所以我們將它們分離成一個變量:
~~~
class Container
{
private $parameters;
function __construct(array $parameters)
{
$this->parameters = $parameters;
}
function createConnection()
{
return new Nette\Database\Connection(
$this->parameters['dsn'],
$this->parameters['user'],
$this->parameters['password']
);
}
function createArticle()
{
return new Article($this->createConnection());
}
}
$container = new Container([
'dsn' => 'mysql:',
'user' => 'root',
'password' => '***',
]);
$article = $container->createArticle();
~~~
更重要的缺點是,總是,當我們要求一個Article創建時,將要開始新的數據庫連接。 這需要避免。 我們將添加方法getConnection,它將保留一次創建的服務以供下次使用:
~~~
class Container
{
private $parameters;
private $services = [];
function __construct(array $parameters)
{
$this->parameters = $parameters;
}
function createConnection()
{
return new Nette\Database\Connection(
$this->parameters['dsn'],
$this->parameters['user'],
$this->parameters['password']
);
}
function getConnection()
{
if (!isset($this->services['connection'])) {
$this->services['connection'] = $this->createConnection();
}
return $this->services['connection'];
}
function createArticle()
{
return new Article($this->getConnection());
}
}
~~~
現在我們有全功能DI容器。 如你所見,編寫它并不復雜。 值得注意的是,服務單獨不知道,這是由一些容器創建的,因此可以創建任何PHP對象的方式。 不用觸摸自己的源代碼。
# Nette\DI\Container
Nette \ DI \ Container類是通用DI容器的靈活實現。 它自動確保該服務實例只創建一次。
我們可以使我們自己的容器靜態,即。 繼承這個類,或者當我們添加工廠作為閉包或回調時,它是動態的。
## 靜態容器
工廠方法的名稱遵循統一的約定,它們包括前綴createService +從首字母大寫開始的服務的名稱。 如果他們不應該從外部訪問,可以降低其受保護的可見性。 請注意,容器已經為用戶參數定義了$parameters字段。
~~~
class MyContainer extends Nette\DI\Container
{
protected function createServiceConnection()
{
return new Nette\Database\Connection(
$this->parameters['dsn'],
$this->parameters['user'],
$this->parameters['password']
);
}
protected function createServiceArticle()
{
return new Article($this->getService('connection'));
}
}
~~~
現在我們創建一個容器的實例并傳遞參數:
~~~
$container = new MyContainer([
'dsn' => 'mysql:',
'user' => 'root',
'password' => '***',
]);
~~~
我們通過調用getService方法獲取服務:
~~~
$article = $container->getService('article');
~~~
如前所述,所有服務都只在一個容器中創建一次,但是如果容器總是創建一個Article的新實例,這將更有用。 它可以很容易地實現:而不是服務文章的工廠,我們將創建一個普通的方法createArticle:
~~~
class MyContainer extends Nette\DI\Container
{
function createServiceConnection()
{
return new Nette\Database\Connection(
$this->parameters['dsn'],
$this->parameters['user'],
$this->parameters['password']
);
}
function createArticle()
{
return new Article($this->getService('connection'));
}
}
$container = new MyContainer(...);
$article = $container->createArticle();
~~~
從$ container-> createArticle()的調用很明顯,一個新的對象總是被創建。 這是一個程序員的約定。
## 動態容器
我們可以使用addService方法將服務添加到Nette \ DI \ Container甚至運行時。 工廠可以寫成PHP回調或閉包。 請注意,容器本身作為參數傳遞給它們,以便容易地訪問參數和其他服務。
~~~
$container = new Nette\DI\Container;
$container->addService('connection', function($container) {
return new Nette\Database\Connection(
$container->parameters['dsn'],
$container->parameters['user'],
$container->parameters['password']
);
});
~~~
當服務創建只包括簡單的實例化時,可以直接傳遞類名作為addService方法的第二個參數。 當我們已經創建了對象時,我們甚至可以傳遞它。
除了addService,我們還有hasService用于服務存在檢查,removeService用于刪除它
- Nette簡介
- 快速開始
- 入門
- 主頁
- 顯示文章詳細頁
- 文章評論
- 創建和編輯帖子
- 權限驗證
- 程序員指南
- MVC應用程序和控制器
- URL路由
- Tracy - PHP調試器
- 調試器擴展
- 增強PHP語言
- HTTP請求和響應
- 數據庫
- 數據庫:ActiveRow
- 數據庫和表
- Sessions
- 用戶授權和權限
- 配置
- 依賴注入
- 獲取依賴關系
- DI容器擴展
- 組件
- 字符串處理
- 數組處理
- HTML元素
- 使用URL
- 表單
- 驗證器
- 模板
- AJAX & Snippets
- 發送電子郵件
- 圖像操作
- 緩存
- 本土化
- Nette Tester - 單元測試
- 與Travis CI的持續集成
- 分頁
- 自動加載
- 文件搜索:Finder
- 原子操作