Phalcon\Di 是一個實現依賴注入和定位服務的組件,而且它本身就是一個裝載它們的容器。
Dependency Injection,簡稱DI.
技術描述
Class A中用到了Class B的對象x,一般情況下,需要在A的代碼中顯式的new一個x的對象。
採用依賴注入技術之後,A的代碼只需要定義一個私有的x對象,不需要直接new來獲得這個對象,而是通過相關的容器控制程序來將x對象在外部new出來並注入到A類裏的引用中。而具體獲取的方法、對象被獲取時的狀態由配置文件(如XML)來指定。
依賴注入
依賴注入有如下實現方式:
- 基於接口。實現特定接口以供外部容器注入所依賴類型的對象。
- 基於 set 方法。實現特定屬性的public set方法,來讓外部容器調用傳入所依賴類型的對象。
- 基於構造函數。實現特定參數的構造函數,在新建對象時傳入所依賴類型的對象。
- 基於註解。基於Java的註解功能,在私有變量前加“@Autowired”等註解,不需要顯式的定義以上三種代碼,便可以讓外部容器傳入對應的對象。該方案相當於定義了public的set方法,但是因爲沒有真正的set方法,從而不會爲了實現依賴注入導致暴露了不該暴露的接口(因爲set方法只想讓容器訪問來注入而並不希望其他依賴此類的對象訪問)。
Phalcon\Di 解釋:
以下實例解釋爲什麼Phalcon使用依賴注入。首先,假設我們正在開發一個名爲的組件SomeComponent
。這執行一些任務。我們的組件具有依賴關係,即與數據庫的連接。
在第一個示例中(就是下面這個),是在組件內部創建連接(數據庫),儘管這是有效的實現,但他不好使( it is impractical),因爲我們無法更改鏈接參數或數據庫系統的類型,因爲組件只能按照已經創建的方式工作。
<?php
class SomeComponent
{
/**
* The instantiation of the connection is hardcoded inside the component,
*鏈接的實例化是在組件內硬編碼的,
*therefore it's difficult replace it externally or change its behavior
*因此它 難以在外部替換或者修改
*/
public function someDbTask()
{
$connection = new Connection(
[
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'dbname' => 'invo',
]
);
// ...
}
}
$some = new SomeComponent();
$some->someDbTask();
爲了解決這個缺點,我們創建了一個在使用它之前在外部注入依賴項的setter(就是setConnection)。這也是一個有效的實現,但也有其缺點:
<?php
class SomeComponent
{
private $connection;
/**
* Sets the connection externally 在外部設置鏈接
*
* @param Connection $connection
*/
public function setConnection(Connection $connection)
{
$this->connection = $connection;
}
public function someDbTask()
{
$connection = $this->connection;
// ...
}
}
$some = new SomeComponent();
// Create the connection
$connection = new Connection(
[
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'dbname' => 'invo',
]
);
// Inject the connection in the component
$some->setConnection($connection);
$some->someDbTask();
現在考慮我們在應用程序的不同部分使用此組件,然後在將其傳遞給組件之前需要多次創建連接。使用全局註冊表模式,我們可以在那裏存儲連接對象,並在需要時重用它。
<?php
class Registry
{
/**
* Returns the connection
*/
public static function getConnection()
{
return new Connection(
[
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'dbname' => 'invo',
]
);
}
}
class SomeComponent
{
protected $connection;
/**
* Sets the connection externally
*
* @param Connection $connection
*/
public function setConnection(Connection $connection)
{
$this->connection = $connection;
}
public function someDbTask()
{
$connection = $this->connection;
// ...
}
}
$some = new SomeComponent();
// Pass the connection defined in the registry
$some->setConnection(Registry::getConnection());
$some->someDbTask();
現在,讓我們假設我們必須在組件中實現兩個方法,第一個總是需要創建一個新連接,第二個總是需要使用共享連接:
<?php
class Registry
{
protected static $connection;
/**
* Creates a connection
*
* @return Connection
*/
protected static function createConnection(): Connection
{
return new Connection(
[
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'dbname' => 'invo',
]
);
}
/**
* Creates a connection only once and returns it
*
* @return Connection
*/
public static function getSharedConnection(): Connection
{
if (self::$connection === null) {
self::$connection = self::createConnection();
}
return self::$connection;
}
/**
* Always returns a new connection
*
* @return Connection
*/
public static function getNewConnection(): Connection
{
return self::createConnection();
}
}
class SomeComponent
{
protected $connection;
/**
* Sets the connection externally
*
* @param Connection $connection
*/
public function setConnection(Connection $connection)
{
$this->connection = $connection;
}
/**
* This method always needs the shared connection
*/
public function someDbTask()
{
$connection = $this->connection;
// ...
}
/**
* This method always needs a new connection
*
* @param Connection $connection
*/
public function someOtherDbTask(Connection $connection)
{
}
}
$some = new SomeComponent();
// This injects the shared connection
$some->setConnection(
Registry::getSharedConnection()
);
$some->someDbTask();
// Here, we always pass a new connection as parameter
$some->someOtherDbTask(
Registry::getNewConnection()
);
到目前爲止,我們已經看到依賴注入如何解決我們的問問題,將依賴項作爲參數傳遞而不是在代碼內部創建它們使我們的應用程序更易於維護和解耦。
然而,從長遠來看,這種形式的依賴注入有一些缺點。例如,如果組件有很多依賴項,我們需要創建多個setter參數來傳遞依賴項或創建一個構造函數,用多個參數傳遞它們,另外在使用組件之前創建依賴項,每次都使我們的代碼不能維護我們希望:
<?php
// Create the dependencies or retrieve them from the registry
$connection = new Connection();
$session = new Session();
$fileSystem = new FileSystem();
$filter = new Filter();
$selector = new Selector();
// Pass them as constructor parameters
$some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);
// ... Or using setters
$some->setConnection($connection);
$some->setSession($session);
$some->setFileSystem($fileSystem);
$some->setFilter($filter);
$some->setSelector($selector);
想想我們是否必須在應用程序的許多部分中創建此對象。將來,如果我們不需要任何依賴項,我們需要遍歷整個代碼庫,以在我們注入代碼的任何構造函數或setter中刪除參數。爲了解決這個問題,我們再次返回全局註冊表來創建組件。但是,它在創建對象之前添加了一個新的抽象層:
<?php
class SomeComponent
{
// ...
/**
* Define a factory method to create SomeComponent instances injecting its dependencies
*/
public static function factory()
{
$connection = new Connection();
$session = new Session();
$fileSystem = new FileSystem();
$filter = new Filter();
$selector = new Selector();
return new self($connection, $session, $fileSystem, $filter, $selector);
}
}
現在我們發現自己回到了我們開始的地方,我們再次構建組件內部的依賴關係!我們必須找到一種解決方案,使我們不再反覆陷入不良行爲。
解決這些問題的一種實用而優雅的方法是使用容器進行依賴。容器充當我們之前看到的全局註冊表。使用依賴容器作爲橋接來獲取依賴關係允許我們降低組件的複雜性:
<?php
use Phalcon\Di;
use Phalcon\DiInterface;
class SomeComponent
{
protected $di;
public function __construct(DiInterface $di)
{
$this->di = $di;
}
public function someDbTask()
{
// Get the connection service
// Always returns a new connection
$connection = $this->di->get('db');
}
public function someOtherDbTask()
{
// Get a shared connection service,
// this will return the same connection every time
$connection = $this->di->getShared('db');
// This method also requires an input filtering service
$filter = $this->di->get('filter');
}
}
$di = new Di();
// Register a 'db' service in the container
$di->set(
'db',
function () {
return new Connection(
[
'host' => 'localhost',
'username' => 'root',
'password' => 'secret',
'dbname' => 'invo',
]
);
}
);
// Register a 'filter' service in the container
$di->set(
'filter',
function () {
return new Filter();
}
);
// Register a 'session' service in the container
$di->set(
'session',
function () {
return new Session();
}
);
// Pass the service container as unique parameter
$some = new SomeComponent($di);
$some->someDbTask();
現在,組件可以在需要時簡單地訪問所需的服務,如果它不需要服務,甚至不會初始化,從而節省資源。該組件現在高度分離。例如,我們可以替換創建連接的方式,它們的行爲或它們的任何其他方面以及不會影響組件的方式。
Phalcon \ Di是實現依賴注入和服務定位的組件,它本身就是它們的容器。
由於Phalcon高度去耦,Phalcon \ Di對於整合框架的不同組件至關重要。開發人員還可以使用此組件注入依賴項並管理應用程序中使用的不同類的全局實例。
基本上,該組件實現了控制反轉模式。應用此方法,對象不會使用setter或構造函數接收它們的依賴項,而是請求服務依賴項注入器。這降低了整體複雜性,因爲只有一種方法可以在組件中獲得所需的依賴關係。
此外,這種模式增加了代碼的可測試性,從而使其不易出錯。