Heim  >  Artikel  >  Backend-Entwicklung  >  Beispielanalyse zur PHP-Abhängigkeitsinjektion verstehen

Beispielanalyse zur PHP-Abhängigkeitsinjektion verstehen

高洛峰
高洛峰Original
2016-12-20 16:13:15939Durchsuche

Die Beispiele in diesem Artikel beschreiben das Verständnis der PHP-Abhängigkeitsinjektion. Ich teile es Ihnen als Referenz mit:

Der Blick auf das IoC-Container-Dokument stellt nur Beispiele vor, erklärt aber nicht das Prinzip, als ich es verwendet habe Ich habe es zufällig im Phalcon-Dokument gesehen, also habe ich es kopiert und eingefügt, hauptsächlich weil ich schon lange nichts mehr geschrieben habe, und jetzt bin ich wirklich faul!

Zuerst gehen wir davon aus, dass wir eine Komponente namens SomeComponent entwickeln möchten. In diese Komponente wird nun eine Datenbankverbindung eingefügt.

In diesem Beispiel wird die Datenbankverbindung in der Komponente erstellt. Diese Methode ist unpraktisch. Wenn wir dies tun, können wir einige Parameter wie Datenbankverbindungsparameter und Datenbanktyp nicht ändern.

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

Um das oben genannte Problem zu lösen, müssen wir vor der Verwendung eine externe Verbindung herstellen und diese in den Behälter injizieren. Im Moment scheint dies eine gute Lösung zu sein:

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

Betrachten wir nun ein Problem, bei dem wir diese Komponente an verschiedenen Stellen in unserer Anwendung verwenden, wo die Datenbankverbindung mehrmals erstellt wird. Verwenden Sie eine Methode ähnlich der globalen Registrierung, um von hier aus eine Datenbankverbindungsinstanz abzurufen, anstatt sie erst nach der Verwendung zu erstellen.

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

Stellen wir uns nun vor, dass wir zwei Methoden in der Komponente implementieren müssen, von denen wir zunächst eine neue Datenbankverbindung erstellen müssen und die zweite immer eine gemeinsame Verbindung erhält:

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

Bisher haben wir gesehen, wie wir die Abhängigkeitsinjektion nutzen können, um unser Problem zu lösen. Anstatt eine Abhängigkeit innerhalb des Codes zu erstellen, übergeben wir sie als Parameter, was die Wartung unseres Programms erleichtert, die Kopplung des Programmcodes verringert und eine Art lose Kopplung erreicht. Doch auf lange Sicht bringt diese Form der Abhängigkeitsinjektion auch einige Nachteile mit sich.

Wenn es beispielsweise mehr Abhängigkeiten in der Komponente gibt, müssen wir mehrere Setter-Methoden zum Übergeben erstellen oder einen Konstruktor zum Übergeben erstellen. Darüber hinaus müssen Sie jedes Mal, wenn Sie eine Komponente verwenden, abhängige Komponenten erstellen, was die Codepflege nicht einfach macht. Der Code, den wir schreiben, sieht möglicherweise so aus:

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

Ich denke, wir müssen erstellen An vielen Stellen in der Anwendung wird dieses Objekt verwendet. Wenn Sie die abhängigen Komponenten nicht benötigen, müssen wir zum Code-Injection-Teil gehen, um die Parameter im Konstruktor oder in der Setter-Methode zu entfernen. Um dieses Problem zu lösen, verwenden wir erneut eine globale Registrierung, um die Komponente zu erstellen. Bevor das Objekt erstellt wird, wird jedoch eine neue Abstraktionsebene hinzugefügt:

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

In diesem Moment scheint es, als wären wir wieder am Anfang des Problems angelangt, wir erstellen Abhängigkeiten innerhalb der Komponente, die Wir ändern jedes Mal und suchen nach einer Möglichkeit, das Problem zu lösen, aber das ist kein guter Ansatz.

Eine praktische und elegante Möglichkeit, diese Probleme zu lösen, ist die Verwendung der Container-Abhängigkeitsinjektion. Wie wir zuvor gesehen haben, fungiert der Container als globale Registrierung und nutzt die Container-Abhängigkeitsinjektion als Brücke, um das Problem zu lösen Unser Code ist weniger gekoppelt und reduziert die Komplexität der Komponente:

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

Jetzt benötigt die Komponente ihn nur, wenn sie auf einen bestimmten Dienst zugreift. Wenn dies nicht der Fall ist, wird er nicht einmal initialisiert, um Ressourcen zu sparen. Das Bauteil ist stark entkoppelt. Ihr Verhalten oder andere Aspekte davon haben keinen Einfluss auf die Komponenten selbst. Unsere Implementierungsmethode

PhalconDI ist eine Komponente, die die Abhängigkeitsinjektionsfunktion des Dienstes implementiert, und es ist auch selbst ein Container.

Aufgrund des hohen Entkopplungsgrads von Phalcon ist PhalconDI ein wesentlicher Bestandteil des Frameworks, das zur Integration anderer Komponenten verwendet wird. Entwickler können diese Komponente auch zur Abhängigkeitsinjektion und zur Verwaltung von Instanzen verschiedener Klassendateien in der Anwendung verwenden.

Grundsätzlich implementiert diese Komponente das Inversion of Control-Muster. Auf dieser Grundlage implementiert das Objekt die Injektion nicht mehr durch den Empfang von Parametern im Konstruktor oder die Verwendung von Settern, sondern fordert direkt die Abhängigkeitsinjektion des Dienstes an. Dadurch wird die Gesamtkomplexität des Programms erheblich reduziert, da es nur einen Weg gibt, die erforderlichen Abhängigkeiten einer Komponente zu erhalten.
Darüber hinaus verbessert dieses Muster die Testbarkeit Ihres Codes und macht ihn weniger fehleranfällig.
Dienste in Containern registrieren

Entweder das Framework selbst oder Entwickler können Dienste registrieren. Wenn eine Komponente A einen Aufruf von Komponente B (oder einer Instanz ihrer Klasse) anfordert, kann sie einen Aufruf von Komponente B vom Container anfordern, anstatt eine Instanz von Komponente B zu erstellen.

Diese Arbeitsweise bietet uns viele Vorteile:

Wir können eine Komponente ersetzen, entweder von uns selbst oder einfach von einem Dritten erstellt.
Bevor die Komponente freigegeben wird, können wir die Initialisierung des Objekts vollständig steuern und verschiedene Einstellungen für das Objekt vornehmen.

Wir können eine einheitliche Methode verwenden, um eine strukturierte globale Instanz von der Komponente abzurufen
Dienste können auf folgende Weise in den Container eingefügt werden:

//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'
));

Im obigen Beispiel: Wenn das Framework Zugriff auf Anforderungsdaten anfordert, ermittelt es zunächst, ob ein Dienst mit diesem „reqeust“-Namen im Container vorhanden ist.

Der Container gibt eine Instanz der angeforderten Daten zurück und Entwickler erhalten schließlich die gewünschte Komponente.
Jede Methode im obigen Beispiel hat Vor- und Nachteile, welche verwendet werden soll, hängt vom jeweiligen Szenario während des Entwicklungsprozesses ab.

用一个字符串来设定一个服务非常简单,但缺少灵活性。设置服务时,使用数组则提供了更多的灵活性,而且可以使用较复杂的代码。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中文网!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn