ホームページ >バックエンド開発 >PHPチュートリアル >PHP 依存関係インジェクションのサンプル分析を理解する

PHP 依存関係インジェクションのサンプル分析を理解する

高洛峰
高洛峰オリジナル
2016-12-20 16:13:15996ブラウズ

この記事の例では、PHP の依存関係の挿入について説明します。詳細は次のとおりです: Laravel の IoC コンテナーのドキュメントを見ると、例が紹介されているだけで、以前 MVC フレームワークを使用していたときはこの概念に注意を払いませんでした。ファルコンのドキュメントでこの詳細な紹介を偶然目にし、突然啓発された気分になり、これをコピーして貼り付けました。主に長い間何も書いていなかったので、今では本当に怠け者です。

まず、SomeComponent という名前のコンポーネントを開発するとします。データベース接続がこのコンポーネントに挿入されます。

この例では、データベース接続はコンポーネント内で作成されますが、これを行うと、データベース接続パラメーターやデータベース タイプなどの一部のパラメーターを変更できなくなります。

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

さて、コンポーネントに 2 つのメソッドを実装する必要があると想像してみましょう。1 つ目は新しいデータベース接続を作成する必要があり、2 つ目は常に共有接続を取得する必要があります。依存関係注入を使用して問題を解決します。コード内に依存関係を作成する代わりに、依存関係をパラメーターとして渡します。これにより、プログラムの保守が容易になり、プログラム コードの結合が減少し、一種の疎結合が実現します。しかし、長期的には、この形式の依存性注入にはいくつかの欠点もあります。

たとえば、コンポーネントにさらに多くの依存関係がある場合は、渡すための複数のセッター メソッドを作成するか、渡すためのコンストラクターを作成する必要があります。さらに、コンポーネントを使用するたびに依存コンポーネントを作成する必要があるため、作成するコードは次のようになります:

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

このオブジェクトを多くの場所で作成する必要があると思います。アプリケーションで。依存コンポーネントが必要ない場合は、コード挿入部分に移動して、コンストラクターまたはセッター メソッドのパラメーターを削除する必要があります。この問題を解決するには、もう一度グローバル レジストリを使用してコンポーネントを作成します。ただし、オブジェクトを作成する前に、新しい抽象化レイヤーが追加されます:

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

現時点では、問題の始まりに戻っているようです。コンポーネント内に依存関係を作成し、修正して方法を見つけています。毎回問題を解決する方法がありますが、これらは良い習慣ではありません。

これらの問題を解決する実用的で洗練された方法は、コンテナ依存関係注入を使用することです。前に見たように、コンテナ依存関係注入をブリッジとして使用して依存関係を解決すると、コード結合が低くなります。これにより、コンポーネントの複雑さが大幅に軽減されます:

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

これで、コンポーネントは特定のサービスにアクセスする場合にのみコンポーネントを必要とし、必要がない場合は、リソースを節約するための初期化も行われません。コンポーネントは高度に分離されています。コンポーネントの動作やその他の側面は、コンポーネント自体には影響しません。私たちの実装方法

PhalconDIは、サービスの依存性注入機能を実装するコンポーネントであり、コンテナそのものでもあります。 Phalcon は高度に分離されているため、PhalconDI は他のコンポーネントを統合するために使用されるフレームワークの重要な部分であり、開発者はこのコンポーネントを使用して、アプリケーション内のさまざまなクラス ファイルのインスタンスを依存関係に挿入して管理することもできます。

基本的に、このコンポーネントは制御の反転パターンを実装します。これに基づいて、オブジェクトはコンストラクターでパラメーターを受け取ったり、セッターを使用してインジェクションを実装するのではなく、サービスの依存関係インジェクションを直接要求します。コンポーネントの必要な依存関係を取得する方法が 1 つしかないため、これによりプログラム全体の複雑さが大幅に軽減されます。

さらに、このパターンによりコードのテスト容易性が向上し、エラーが発生しにくくなります。

コンテナにサービスを登録する

フレームワーク自体または開発者のいずれかがサービスを登録できます。コンポーネント A がコンポーネント B (またはそのクラスのインスタンス) への呼び出しをリクエストする場合、コンポーネント B のインスタンスを作成する代わりに、コンテナからコンポーネント B への呼び出しをリクエストできます。

この作業方法には多くの利点があります:

コンポーネントを独自に作成したり、サードパーティが簡単に作成したりすることができます。

コンポーネントがリリースされる前に、オブジェクトの初期化を完全に制御し、オブジェクトのさまざまな設定を行うことができます。

統一された方法を使用して、コンポーネントから構造化されたグローバル インスタンスを取得できます。

サービスは次の方法でコンテナに挿入できます:

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

上記の例では、フレームワークからリクエスト データへのアクセスをリクエストするときに、まず、この「リクエスト」名のサービスがコンテナ内に存在するかどうかを判断します。

コンテナは要求されたデータのインスタンスを返し、開発者は最終的に必要なコンポーネントを取得します。

上記の例の各方法には長所と短所があり、どれを使用するかは、開発プロセス中の特定のシナリオによって異なります。

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