首頁  >  文章  >  後端開發  >  對PHP依賴注入的理解實例分析

對PHP依賴注入的理解實例分析

高洛峰
高洛峰原創
2016-12-20 16:13:15941瀏覽

本文實例講述了對PHP依賴注入的理解。分享給大家供大家參考,具體如下:

看Laravel的IoC容器文檔只是介紹實例,但是沒有說原理,之前用MVC框架都沒有在意這個概念,無意中在phalcon的文檔中看到這個詳細的介紹,感覺豁然開朗,複製貼上過來,主要是好久沒寫東西了,現在確實很懶變得!

首先,我們假設,我們要開發一個元件命名為SomeComponent。這個元件中現在將要注入一個資料庫連線。

在這個例子中,資料庫連接在component中被創建,這種方法是不切實際的,這樣做的話,我們將不能改變資料庫連接參數及資料庫類型等一些參數。

class SomeComponent {
  /**
   * The instantiation of the connection is hardcoded inside
   * the component so is difficult to replace it externally
   * or change its behavior
   */
  public function someDbTask()
  {
    $connection = new Connection(array(
      "host" => "localhost",
      "username" => "root",
      "password" => "secret",
      "dbname" => "invo"
    ));
    // ...
  }
}
$some = new SomeComponent();
$some->someDbTask();

為了解決上面所說的問題,我們需要在使用前創建一個外部連接,並注入到容器中。就目前而言,這看起來是一個很好的解決方案:

class SomeComponent {
  protected $_connection;
  /**
   * Sets the connection externally
   */
  public function setConnection($connection)
  {
    $this->_connection = $connection;
  }
  public function someDbTask()
  {
    $connection = $this->_connection;
    // ...
  }
}
$some = new SomeComponent();
//Create the connection
$connection = new Connection(array(
  "host" => "localhost",
  "username" => "root",
  "password" => "secret",
  "dbname" => "invo"
));
//Inject the connection in the component
$some->setConnection($connection);
$some->someDbTask();

現在我們來考慮一個問題,我們在應用程式中的不同地方使用此元件,將多次建立資料庫連接。使用一種類似全域註冊表的方式,從這獲得一個資料庫連線實例,而不是使用一次就建立一次。

class Registry
{
  /**
   * Returns the connection
   */
  public static function getConnection()
  {
    return new Connection(array(
      "host" => "localhost",
      "username" => "root",
      "password" => "secret",
      "dbname" => "invo"
    ));
  }
}
class SomeComponent
{
  protected $_connection;
  /**
   * Sets the connection externally
   */
  public function setConnection($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();

現在,讓我們來想像一下,我們必須在組件中實現兩個方法,首先需要創建一個新的數據庫連接,第二個總是獲得一個共享連接:

class Registry
{
  protected static $_connection;
  /**
   * Creates a connection
   */
  protected static function _createConnection()
  {
    return new Connection(array(
      "host" => "localhost",
      "username" => "root",
      "password" => "secret",
      "dbname" => "invo"
    ));
  }
  /**
   * Creates a connection only once and returns it
   */
  public static function getSharedConnection()
  {
    if (self::$_connection===null){
      $connection = self::_createConnection();
      self::$_connection = $connection;
    }
    return self::$_connection;
  }
  /**
   * Always returns a new connection
   */
  public static function getNewConnection()
  {
    return self::_createConnection();
  }
}
class SomeComponent
{
  protected $_connection;
  /**
   * Sets the connection externally
   */
  public function setConnection($connection){
    $this->_connection = $connection;
  }
  /**
   * This method always needs the shared connection
   */
  public function someDbTask()
  {
    $connection = $this->_connection;
    // ...
  }
  /**
   * This method always needs a new connection
   */
  public function someOtherDbTask($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::getConnection());

到此為止,我們已經看到瞭如何使用依賴注入來解決我們的問題。不是在程式碼內部創建依賴關係,而是讓其作為一個參數傳遞,這使得我們的程式更容易維護,降低程式碼的耦合度,實現一種鬆散耦合。但從長遠來看,這種形式的依賴注入也有一些缺點。

例如,如果元件中有較多的依賴關係,我們需要建立多個setter方法傳遞,或建立建構函式進行傳遞。另外,每次使用元件時,都需要創建依賴元件,使程式碼維護不太易,我們編寫的程式碼可能像這樣:

//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方法。為了解決這個問題,我們再次回到使用一個全域註冊表來建立元件。但是,在創建物件之前,它增加了一個新的抽象層:

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);
  }
}

這一刻,我們好像回到了問題的開始,我們正在創建組件內部的依賴,我們每次都在修改以及尋找一種解決問題的辦法,但這都不是很好的做法。

一種實用和優雅的來解決這些問題,是使用容器的依賴注入,像我們在前面看到的,容器作為全局註冊表,使用容器的依賴注入做為一種橋樑來解決依賴可以使我們的程式碼耦合度更低,很好的降低了元件的複雜性:

class SomeComponent
{
  protected $_di;
  public function __construct($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 everytime
    $connection = $this->_di->getShared('db');
    //This method also requires a input filtering service
    $filter = $this->_db->get('filter');
  }
}
$di = new Phalcon\DI();
//Register a "db" service in the container
$di->set('db', function(){
  return new Connection(array(
    "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->someTask();

現在,該元件只有存取某種service的時候才需要它,如果它不需要,它甚至不初始化,以節約資源。該組件是高度解耦。他們的行為,或者說他們的任何其他方面都不會影響到組件本身。我們的實作辦法

PhalconDI 是一個實現了服務的依賴注入功能的元件,它本身也是一個容器。

由於Phalcon高度解耦,PhalconDI 是框架用來整合其他元件的必不可少的部分,開發人員也可以使用這個元件依賴注入和管理應用程式中不同類別檔案的實例。

基本上,這個元件實作了 Inversion of Control 模式。基於此,物件不再以建構函式接收參數或使用setter的方式來實現注入,而是直接請求服務的依賴注入。這就大大降低了整體程序的複雜性,因為只有一個方法可以用以獲得所需的一個元件的依賴關係。
此外,這種模式增強了程式碼的可測試性,從而使它不容易出錯。
在容器中註冊服務

框架本身或開發人員都可以註冊服務。當一個元件A要求呼叫元件B(或它的類別的一個實例),可以從容器中請求呼叫元件B,而不是建立元件B的一個實例。

這種工作方式為我們提供了許多優點:

我們可以更換一個組件,從他們本身或第三方輕鬆創建。
在元件發布之前,我們可以充分的控制物件的初始化,並對物件進行各種設定。

我們可以使用統一的方式從組件​​得到一個結構化的全局實例
服務可以通過以下幾種方式註入到容器:

//Create the Dependency Injector Container
$di = new Phalcon\DI();
//By its class name
$di->set("request", 'Phalcon\Http\Request');
//Using an anonymous function, the instance will lazy loaded
$di->set("request", function(){
  return new Phalcon\Http\Request();
});
//Registering directly an instance
$di->set("request", new Phalcon\Http\Request());
//Using an array definition
$di->set("request", array(
  "className" => 'Phalcon\Http\Request'
));

在上面的例子中,當向框架請求訪問一個請求數據時,它將首先確定容器中是否存在這個”reqeust」名稱的服務。

容器會反回一個請求資料的實例,開發人員最後得到他們想要的元件。
在上面範例中的每一種方法都有優缺點,具體使用哪一種,由開發過程中的特定場景來決定的。

用一个字符串来设定一个服务非常简单,但缺少灵活性。设置服务时,使用数组则提供了更多的灵活性,而且可以使用较复杂的代码。lambda函数是两者之间一个很好的平衡,但也可能导致更多的维护管理成本。

Phalcon\DI 提供服务的延迟加载。除非开发人员在注入服务的时候直接实例化一个对象,然后存存储到容器中。在容器中,通过数组,字符串等方式存储的服务都将被延迟加载,即只有在请求对象的时候才被初始化。

//Register a service "db" with a class name and its parameters
$di->set("db", array(
  "className" => "Phalcon\Db\Adapter\Pdo\Mysql",
  "parameters" => array(
     "parameter" => array(
        "host" => "localhost",
        "username" => "root",
        "password" => "secret",
        "dbname" => "blog"
     )
  )
));
//Using an anonymous function
$di->set("db", function(){
  return new Phalcon\Db\Adapter\Pdo\Mysql(array(
     "host" => "localhost",
     "username" => "root",
     "password" => "secret",
     "dbname" => "blog"
  ));
});

以上这两种服务的注册方式产生相同的结果。然后,通过数组定义的,在后面需要的时候,你可以修改服务参数:

$di->setParameter("db", 0, array(
  "host" => "localhost",
  "username" => "root",
  "password" => "secret"
));

从容器中获得服务的最简单方式就是使用”get”方法,它将从容器中返回一个新的实例:

$request = $di->get("request");

   

或者通过下面这种魔术方法的形式调用:

$request = $di->getRequest();

   

Phalcon\DI 同时允许服务重用,为了得到一个已经实例化过的服务,可以使用 getShared() 方法的形式来获得服务。
具体的 Phalcon\Http\Request 请求示例:

$request = $di->getShared("request");

   

参数还可以在请求的时候通过将一个数组参数传递给构造函数的方式:

$component = $di->get("MyComponent", array("some-parameter", "other"));

  希望本文所述对大家PHP程序设计有所帮助。

   更多对PHP依赖注入的理解实例分析相关文章请关注PHP中文网!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn