# 依賴注入與服務定位器(Dependency Injection/Service Location)
> Before reading this section, it is wise to read[the section which explains why Phalcon uses service location and dependency injection](http://docs.iphalcon.cn/reference/di-explained.html).
[Phalcon\\Di](http://docs.iphalcon.cn/api/Phalcon_Di.html)是一個實現依賴注入和定位服務的組件,而且它本身就是一個裝載它們的容器。
因為Phalcon是高度解構的,整合框架的不同組件,使用[Phalcon\\Di](http://docs.iphalcon.cn/api/Phalcon_Di.html)是必不可少的。開發者也可以使用這個組件去注入依賴和管理的應用程序中來自不同類的全局實例。
基本上,這個組件實現了 \[控制反轉\]([http://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC](http://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC)) 的模式。使用這種模式,組件的對象不用再使用setter或者構造函數去接受依賴實例,而是使用請求服務的依賴注入。這減少了總的復雜性,因為在組件內,只有一個方法去獲取所需的依賴實例。
另外,該模式增加了代碼的可測試性,從而使其不易出錯。
## 使用容器注冊服務(Registering services in the Container)
框架本身或者開發者都可以注冊服務。當一個組件A需要組件B(或者它的類的實例) 去操作,它可以通過容器去請求組件B,而不是創建一個新的組件B實例。
這個工作方法給我們提供了許多優勢:
* 我們可以很容易的使用一個我們自己建立的或者是第三方的組件去替換原有的組件。
* 我們完全控制對象的初始化,這讓我們在傳遞它們的實例到組件之前,根據需要設置這些對象。
* 我們可以在一個結構化的和統一組件內獲取全局實例。
服務可以使用不同方式去定義:
### 簡單的注冊(Simple Registration)
就像你之前看到的那樣,這里有幾種方法去注冊服務。下面是簡單調用的例子:
#### 字符串(STRING)
使用字符串注冊服務需要一個有效的類名稱,它將返回指定的類對象,如果類還沒有加載的話,將使用自動加載器實例化對象。這種類型不允許向構造函數指定參數:
~~~
<?php
// 返回 new Phalcon\Http\Request(); 對象
$di->set(
"request",
"Phalcon\\Http\\Request"
);
~~~
#### 類實例(CLASS INSTANCES)
這種類型注冊服務需要一個對象。實際上,這個服務不再需要初始化,因為它已經是一個對象,可以說,這不是一個真正的依賴注入,但是如果你想強制總是返回相同的對象/值,使用這種方式還是有用的:
~~~
<?php
use Phalcon\Http\Request;
// 返回 Phalcon\Http\Request(); 對象
$di->set(
"request",
new Request()
);
~~~
#### 閉包與匿名函數(CLOSURES/ANONYMOUS FUNCTIONS)
這個方法提供了更加自由的方式去注冊依賴,但是如果你想從外部改變實例化的參數而不用改變注冊服務的代碼,這是很困難的:
~~~
<?php
use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;
$di->set(
"db",
function () {
return new PdoMysql(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "blog",
]
);
}
);
~~~
這些限制是可以克服的,通過傳遞額外的變量到閉包函數里面:
~~~
<?php
use Phalcon\Config;
use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;
$config = new Config(
[
"host" => "127.0.0.1",
"username" => "user",
"password" => "pass",
"dbname" => "my_database",
]
);
// 把當前域的$config變量傳遞給匿名函數使用
$di->set(
"db",
function () use ($config) {
return new PdoMysql(
[
"host" => $config->host,
"username" => $config->username,
"password" => $config->password,
"dbname" => $config->name,
]
);
}
);
~~~
You can also access other DI services using the`get()`method:
~~~
<?php
use Phalcon\Config;
use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;
$di->set(
"config",
function () {
return new Config(
[
"host" => "127.0.0.1",
"username" => "user",
"password" => "pass",
"dbname" => "my_database",
]
);
}
);
// Using the 'config' service from the DI
$di->set(
"db",
function () {
$config = $this->get("config");
return new PdoMysql(
[
"host" => $config->host,
"username" => $config->username,
"password" => $config->password,
"dbname" => $config->name,
]
);
}
);
~~~
### 復雜的注冊(Complex Registration)
如果要求不用實例化/解析服務,就可以改變定義服務的話,我們需要使用數組的方式去定義服務。使用數組去定義服務可以更加詳細:
~~~
<?php
use Phalcon\Logger\Adapter\File as LoggerFile;
// 通過類名和參數,注冊logger服務
$di->set(
"logger",
[
"className" => "Phalcon\\Logger\\Adapter\\File",
"arguments" => [
[
"type" => "parameter",
"value" => "../apps/logs/error.log",
]
]
]
);
// 使用匿名函數的方式
$di->set(
"logger",
function () {
return new LoggerFile("../apps/logs/error.log");
}
);
~~~
上面兩種注冊服務的方式的結果是一樣的。然而,使用數組定義的話,在需要的時候可以變更注冊服務的參數:
~~~
<?php
// 改變logger服務的類名
$di->getService("logger")->setClassName("MyCustomLogger");
// 不用實例化就可以改變第一個參數值
$di->getService("logger")->setParameter(
0,
[
"type" => "parameter",
"value" => "../apps/logs/error.log",
]
);
~~~
除了使用數組的語法注冊服務,你還可以使用以下三種類型的依賴注入:
#### 構造函數注入(CONSTRUCTOR INJECTION)
這個注入方式是通過傳遞依賴/參數到類的構造函數。讓我們假設我們有下面的組件:
~~~
<?php
namespace SomeApp;
use Phalcon\Http\Response;
class SomeComponent
{
/**
* @var Response
*/
protected $_response;
protected $_someFlag;
public function __construct(Response $response, $someFlag)
{
$this->_response = $response;
$this->_someFlag = $someFlag;
}
}
~~~
這個服務可以這樣被注入:
~~~
<?php
$di->set(
"response",
[
"className" => "Phalcon\\Http\\Response"
]
);
$di->set(
"someComponent",
[
"className" => "SomeApp\\SomeComponent",
"arguments" => [
[
"type" => "service",
"name" => "response",
],
[
"type" => "parameter",
"value" => true,
],
]
]
);
~~~
reponse服務([Phalcon\\Http\\Response](http://docs.iphalcon.cn/api/Phalcon_Http_Response.html))作為第一個參數傳遞給構造函數,與此同時,一個布爾類型的值(true)作為第二個參數傳遞。
#### 設值注入(SETTER INJECTION)
類中可能有setter去注入可選的依賴,前面那個class可以修改成通過setter來注入依賴的方式:
~~~
<?php
namespace SomeApp;
use Phalcon\Http\Response;
class SomeComponent
{
/**
* @var Response
*/
protected $_response;
protected $_someFlag;
public function setResponse(Response $response)
{
$this->_response = $response;
}
public function setFlag($someFlag)
{
$this->_someFlag = $someFlag;
}
}
~~~
用setter方式來注入的服務可以通過下面的方式來注冊:
~~~
<?php
$di->set(
"response",
[
"className" => "Phalcon\\Http\\Response",
]
);
$di->set(
"someComponent",
[
"className" => "SomeApp\\SomeComponent",
"calls" => [
[
"method" => "setResponse",
"arguments" => [
[
"type" => "service",
"name" => "response",
]
]
],
[
"method" => "setFlag",
"arguments" => [
[
"type" => "parameter",
"value" => true,
]
]
]
]
]
);
~~~
#### 屬性注入(PROPERTIES INJECTION)
這是一個不太常用的方式,這種方式的注入是通過類的public屬性來注入:
~~~
<?php
namespace SomeApp;
use Phalcon\Http\Response;
class SomeComponent
{
/**
* @var Response
*/
public $response;
public $someFlag;
}
~~~
通過屬性注入的服務,可以像下面這樣注冊:
~~~
<?php
$di->set(
"response",
[
"className" => "Phalcon\\Http\\Response",
]
);
$di->set(
"someComponent",
[
"className" => "SomeApp\\SomeComponent",
"properties" => [
[
"name" => "response",
"value" => [
"type" => "service",
"name" => "response",
],
],
[
"name" => "someFlag",
"value" => [
"type" => "parameter",
"value" => true,
],
]
]
]
);
~~~
支持包括下面的參數類型:
| Type | 描述 | 例子 |
| --- | --- | --- |
| parameter | 表示一個文本值作為參數傳遞過去 | `["type"=>"parameter","value"=>1234]` |
| service | 表示作為服務 | `["type"=>"service","name"=>"request"]` |
| instance | 表示必須動態生成的對象 | `["type"=>"instance","className"=>"DateTime","arguments"=>["now"]]` |
解析一個定義復雜的服務也許性能上稍微慢于先前看到的簡單定義。但是,這提供了一個更強大的方式來定義和注入服務。
混合不同類型的定義是可以的,每個人可以應用需要決定什么樣的注冊服務的方式是最適當的。
### Array Syntax
使用數組的方式去注冊服務也是可以的:
~~~
<?php
use Phalcon\Di;
use Phalcon\Http\Request;
// 創建一個依賴注入容器
$di = new Di();
// 通過類名稱設置服務
$di["request"] = "Phalcon\\Http\\Request";
// 使用匿名函數去設置服務,這個實例將被延遲加載
$di["request"] = function () {
return new Request();
};
// 直接注冊一個實例
$di["request"] = new Request();
// 使用數組方式定義服務
$di["request"] = [
"className" => "Phalcon\\Http\\Request",
];
~~~
在上面的例子中,當框架需要訪問request服務的內容,它會在容器里面查找名為‘request’的服務。 在容器中將返回所需要的服務的實例。當有需要時,開發者可能最終需要替換這個組件。
每個方法(在上面的例子證明)用于設置/注冊服務方面具都具有優勢和劣勢。這是由開發者和特別的要求決定具體使用哪個。
通過字符串設置一個服務是很簡單,但是缺乏靈活性。通過數組設置服務提供了更加靈活的方式,但是使代碼更復雜。匿名函數是上述兩者之間的一個很好的平衡,但是會導致比預期的更多維護。
[Phalcon\\Di](http://docs.iphalcon.cn/api/Phalcon_Di.html)對每個儲存的服務提供了延遲加載。除非開發者選擇直接實例化一個對象并將其存儲在容器中,任何儲存在里面的對象(通過數組,字符串等等設置的)都將延遲加載,即只要當使用到時才實例化。
## 服務解疑(Resolving Services)
從容器中獲取一個服務是一件簡單的事情,只要通過“get”方法就可以。這將返回一個服務的新實例:
~~~
<?php $request = $di->get("request");
~~~
或者通過魔術方法的方式獲取:
~~~
<?php
$request = $di->getRequest();
~~~
或者通過訪問數組的方式獲取:
~~~
<?php
$request = $di["request"];
~~~
參數可以傳遞到構造函數中,通過添加一個數組的參數到get方法中:
~~~
<?php
// 將返回:new MyComponent("some-parameter", "other")
$component = $di->get(
"MyComponent",
[
"some-parameter",
"other",
]
);
~~~
### Events
[Phalcon\\Di](http://docs.iphalcon.cn/api/Phalcon_Di.html)is able to send events to an[EventsManager](http://docs.iphalcon.cn/reference/events.html)if it is present. Events are triggered using the type “di”. Some events when returning boolean false could stop the active operation. The following events are supported:
| Event Name | Triggered | Can stop operation? | Triggered on |
| --- | --- | --- | --- |
| beforeServiceResolve | Triggered before resolve service. Listeners receive the service name and the parameters passed to it. | No | Listeners |
| afterServiceResolve | Triggered after resolve service. Listeners receive the service name, instance, and the parameters passed to it. | No | Listeners |
## 共享服務(Shared services)
服務可以注冊成“shared”類型的服務,這意味著這個服務將使用 \[單例模式\]([http://zh.wikipedia.org/wiki/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F](http://zh.wikipedia.org/wiki/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F)) 運行, 一旦服務被首次解析后,這個實例將被保存在容器中,之后的每次請求都在容器中查找并返回這個實例
~~~
<?php
use Phalcon\Session\Adapter\Files as SessionFiles;
// 把session服務注冊成“shared”類型
$di->setShared(
"session",
function () {
$session = new SessionFiles();
$session->start();
return $session;
}
);
// 第一次獲取session服務時,session服務將實例化
$session = $di->get("session");
// 第二次獲取時,不再實例化,直接返回第一次實例化的對象
$session = $di->getSession();
~~~
另一種方式去注冊一個“shared”類型的服務是,傳遞“set”服務的時候,把true作為第三個參數傳遞過去:
~~~
<?php
// 把session服務注冊成“shared”類型
$di->set(
"session",
function () {
// ...
},
true
);
~~~
如果一個服務不是注冊成“shared”類型,而你又想從DI中獲取服務的“shared”實例,你可以使用getShared方法:
~~~
<?php
$request = $di->getShared("request");
~~~
## 單獨操作服務(Manipulating services individually)
一旦服務被注冊到服務容器中,你可以單獨操作它:
~~~
<?php
use Phalcon\Http\Request;
// 注冊request服務
$di->set("request", "Phalcon\\Http\\Request");
// 獲取服務
$requestService = $di->getService("request");
// 改變它的定義
$requestService->setDefinition(
function () {
return new Request();
}
);
// 修改成shared類型
$requestService->setShared(true);
// 解析服務(返回Phalcon\Http\Request實例)
$request = $requestService->resolve();
~~~
## 通過服務容器實例化類(Instantiating classes via the Service Container)
當你從服務容器中請求一個服務,如果找不到具有相同名稱的服務,它將嘗試去加載以這個服務為名稱的類。利用這個的行為, 我們可以代替任意一個類,通過簡單的利用服務的名稱來注冊:
~~~
<?php
// 把一個控制器注冊為服務
$di->set(
"IndexController",
function () {
$component = new Component();
return $component;
},
true
);
// 把一個控制器注冊為服務
$di->set(
"MyOtherComponent",
function () {
// 實際上返回另外一個組件
$component = new AnotherComponent();
return $component;
}
);
// 獲取通過服務容器創建的對象
$myComponent = $di->get("MyOtherComponent");
~~~
你可以利用這種方式,通過服務容器來總是實例化你的類(即是他們沒有注冊為服務), DI會回退到一個有效的自動加載類中,去加載這個類。通過這樣做,以后你可以輕松替換任意的類通過為它實現一個定義。
## 自動注入 DI(Automatic Injecting of the DI itself)
如果一個類或者組件需要用到DI服務,你需要在你的類中實現[Phalcon\\Di\\InjectionAwareInterface](http://docs.iphalcon.cn/api/Phalcon_Di_InjectionAwareInterface.html)接口, 這樣就可以在實例化這個類的對象時自動注入DI的服務:
~~~
<?php
use Phalcon\DiInterface;
use Phalcon\Di\InjectionAwareInterface;
class MyClass implements InjectionAwareInterface
{
/**
* @var DiInterface
*/
protected $_di;
public function setDi(DiInterface $di)
{
$this->_di = $di;
}
public function getDi()
{
return $this->_di;
}
}
~~~
按照上面這樣,一旦服務被解析,`$di`對象將自動傳遞到`setDi()`方法:
~~~
<?php
// 注冊服務
$di->set("myClass", "MyClass");
// 解析服務(注意:將自動調用$myClass->setDi($di)方法)
$myClass = $di->get("myClass");
~~~
## 使用文件組織服務(Organizing services in files)
你可以更好的組織你的應用,通過移動注冊的服務到獨立的文件里面,而不是全部寫在應用的引導文件中:
~~~
<?php
$di->set(
"router",
function () {
return include "../app/config/routes.php";
}
);
~~~
這樣,在文件(”../app/config/routes.php”)中,返回已解析的對象:
~~~
<?php
$router = new MyRouter();
$router->post("/login");
return $router;
~~~
## 使用靜態的方式訪問注入器(Accessing the DI in a static way)
如果需要的話,你可以訪問最新創建的DI對象,通過下面這種靜態方法的方式:
~~~
<?php
use Phalcon\Di;
class SomeComponent
{
public static function someMethod()
{
// 獲取session服務
$session = Di::getDefault()->getSession();
}
}
~~~
## 注入器默認工廠(Factory Default DI)
盡管Phalcon的解耦性質為我們提供了很大的自由度和靈活性,也許我們只是單純的想使用它作為一個全棧框架。 為了達到這點,框架提供了變種的[Phalcon\\Di](http://docs.iphalcon.cn/api/Phalcon_Di.html)叫[Phalcon\\Di\\FactoryDefault](http://docs.iphalcon.cn/api/Phalcon_Di_FactoryDefault.html)。這個類會自動注冊相應的服務,并捆綁在一起作為一個全棧框架。
~~~
<?php
use Phalcon\Di\FactoryDefault;
$di = new FactoryDefault();
~~~
## 服務名稱約定(Service Name Conventions)
盡管你可以用你喜歡的名字來注冊服務,但是Phalcon有一些命名約定,這些約定讓你在需要的時候,可以獲得正確的(內置)服務。
| 服務名稱 | 介紹 | 默認 | 是否是shared服務 |
| --- | --- | --- | --- |
| dispatcher | 控制器調度服務 | [Phalcon\\Mvc\\Dispatcher](http://docs.iphalcon.cn/api/Phalcon_Mvc_Dispatcher.html) | 是 |
| router | 路由服務 | [Phalcon\\Mvc\\Router](http://docs.iphalcon.cn/api/Phalcon_Mvc_Router.html) | 是 |
| url | URL生成服務 | [Phalcon\\Mvc\\Url](http://docs.iphalcon.cn/api/Phalcon_Mvc_Url.html) | 是 |
| request | HTTP 請求環境服務 | [Phalcon\\Http\\Request](http://docs.iphalcon.cn/api/Phalcon_Http_Request.html) | 是 |
| response | HTTP響應環境服務 | [Phalcon\\Http\\Response](http://docs.iphalcon.cn/api/Phalcon_Http_Response.html) | 是 |
| cookies | HTTP Cookie管理服務 | [Phalcon\\Http\\Response\\Cookies](http://docs.iphalcon.cn/api/Phalcon_Http_Response_Cookies.html) | 是 |
| filter | 輸入過濾服務 | [Phalcon\\Filter](http://docs.iphalcon.cn/api/Phalcon_Filter.html) | 是 |
| flash | 閃現信息服務 | [Phalcon\\Flash\\Direct](http://docs.iphalcon.cn/api/Phalcon_Flash_Direct.html) | 是 |
| flashSession | 閃現session信息服務 | [Phalcon\\Flash\\Session](http://docs.iphalcon.cn/api/Phalcon_Flash_Session.html) | 是 |
| session | session服務 | [Phalcon\\Session\\Adapter\\Files](http://docs.iphalcon.cn/api/Phalcon_Session_Adapter_Files.html) | 是 |
| eventsManager | 事件管理服務 | [Phalcon\\Events\\Manager](http://docs.iphalcon.cn/api/Phalcon_Events_Manager.html) | 是 |
| db | 底層數據庫連接服務 | [Phalcon\\Db](http://docs.iphalcon.cn/api/Phalcon_Db.html) | 是 |
| security | 安全助手 | [Phalcon\\Security](http://docs.iphalcon.cn/api/Phalcon_Security.html) | 是 |
| crypt | 加密/解密數據 | [Phalcon\\Crypt](http://docs.iphalcon.cn/api/Phalcon_Crypt.html) | 是 |
| tag | HTML生成助手 | [Phalcon\\Tag](http://docs.iphalcon.cn/api/Phalcon_Tag.html) | 是 |
| escaper | 內容(HTML)轉義 | [Phalcon\\Escaper](http://docs.iphalcon.cn/api/Phalcon_Escaper.html) | 是 |
| annotations | 注釋分析器 | [Phalcon\\Annotations\\Adapter\\Memory](http://docs.iphalcon.cn/api/Phalcon_Annotations_Adapter_Memory.html) | 是 |
| modelsManager | model管理服務 | [Phalcon\\Mvc\\Model\\Manager](http://docs.iphalcon.cn/api/Phalcon_Mvc_Model_Manager.html) | 是 |
| modelsMetadata | model元數據服務 | [Phalcon\\Mvc\\Model\\MetaData\\Memory](http://docs.iphalcon.cn/api/Phalcon_Mvc_Model_MetaData_Memory.html) | 是 |
| transactionManager | model事務管理服務 | [Phalcon\\Mvc\\Model\\Transaction\\Manager](http://docs.iphalcon.cn/api/Phalcon_Mvc_Model_Transaction_Manager.html) | 是 |
| modelsCache | model的緩存服務 | None | No |
| viewsCache | view的緩存服務 | None | No |
## 自定義注入器(Implementing your own DI)
如果你要創建一個自定義注入器或者繼承一個已有的,接口[Phalcon\\DiInterface](http://docs.iphalcon.cn/api/Phalcon_DiInterface.html)必須被實現。
- 簡介
- 安裝
- 安裝(installlation)
- XAMPP下的安裝
- WAMP下安裝
- Nginx安裝說明
- Apache安裝說明
- Cherokee 安裝說明
- 使用 PHP 內置 web 服務器
- Phalcon 開發工具
- Linux 系統下使用 Phalcon 開發工具
- Mac OS X 系統下使用 Phalcon 開發工具
- Windows 系統下使用 Phalcon 開發工具
- 教程
- 教程 1:讓我們通過例子來學習
- 教程 2:INVO簡介
- 教程 3: 保護INVO
- 教程4: 使用CRUD
- 教程5: 定制INVO
- 教程 6: V?kuró
- 教程 7:創建簡單的 REST API
- 組件
- 依賴注入與服務定位器
- MVC架構
- 使用控制器
- 使用模型
- 模型關系
- 事件與事件管理器
- Behaviors
- 模型元數據
- 事務管理
- 驗證數據完整性
- Workingwith Models
- Phalcon查詢語言
- 緩存對象關系映射
- 對象文檔映射 ODM
- 使用視圖
- 視圖助手
- 資源文件管理
- Volt 模版引擎
- MVC 應用
- 路由
- 調度控制器
- Micro Applications
- 使用命名空間
- 事件管理器
- Request Environmen
- 返回響應
- Cookie 管理
- 生成 URL 和 路徑
- 閃存消息
- 使用 Session 存儲數據
- 過濾與清理
- 上下文編碼
- 驗證Validation
- 表單_Forms
- 讀取配置
- 分頁 Pagination
- 使用緩存提高性能
- 安全
- 加密與解密 Encryption/Decryption
- 訪問控制列表
- 多語言支持
- 類加載器 Class Autoloader
- 日志記錄_Logging
- 注釋解析器 Annotations Parser
- 命令行應用 Command Line Applications
- Images
- 隊列 Queueing
- 數據庫抽象層
- 國際化
- 數據庫遷移
- 調試應用程序
- 單元測試
- 進階技巧與延伸閱讀
- 提高性能:下一步該做什么?
- Dependency Injection Explained
- Understanding How Phalcon Applications Work
- Api
- Abstract class Phalcon\Acl