PHP依存注入(制御反転)の解釈と例説明

12187 ワード

原文住所:http://phalcon.5iunix.net/reference/di.html
以下に述べるこの例は少し長いですが、なぜService ContainerやDIを使うのかよく説明できます.まず,SomeComponentというコンポーネントを開発すると仮定する.このコンポーネントには、データベース接続が注入されます.
この例では、データベース接続がcomponentで作成されます.この方法は現実的ではありません.そうすると、データベース接続パラメータやデータベースタイプなどのパラメータを変更することはできません.
<?php

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

上記の問題を解決するには、使用前に外部接続を作成し、コンテナに注入する必要があります.現在のところ、これは良い解決策のように見えます.
<?php

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

アプリケーション内の異なる場所でこのコンポーネントを使用して、データベース接続を複数回作成する問題を考えてみましょう.グローバルレジストリと同様の方法で、一度に作成するのではなく、データベース接続インスタンスを取得します.
<?php

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つの方法を実装する必要があります.まず、新しいデータベース接続を作成し、2番目は常に共有接続を取得する必要があります.
<?php

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メソッド伝達を作成するか、構造関数を作成して伝達する必要があります.また、コンポーネントを使用するたびに、コードのメンテナンスが容易ではない依存コンポーネントを作成する必要があります.私たちが作成したコードは、次のようになります.
<?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

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

現在、このコンポーネントは、リソースを節約するために初期化されていない場合、あるサービスにアクセスする場合にのみ必要です.このコンポーネントは高度にデカップリングされている.彼らの行動、あるいは彼らの他の面はコンポーネント自体に影響しません.
===============分割線、次の部分はphalconフレーム自体からコンテナとは何かを説明します==========
PhalconDIは,サービス依存注入機能を実現したコンポーネントであり,それ自体もコンテナである.
Phalconが高度にデカップリングされているため、PhalconDIはフレームワークが他のコンポーネントを統合するために必要不可欠な部分であり、開発者はこのコンポーネントを使用してアプリケーション内の異なるクラスのファイルの注入と管理に依存するインスタンスを使用することもできます.
基本的に,このコンポーネントはInversion of Controlモードを実現した.これに基づいて、オブジェクトは、コンストラクション関数でパラメータを受信したり、setterを使用して注入を実現したりするのではなく、サービスの依存注入を直接要求します.これは、必要なコンポーネントの依存関係を得るために1つの方法しかないため、全体的なプログラムの複雑さを大幅に低減する.
さらに、このモードは、コードのテスト性を向上させ、エラーが発生しにくいようにします.
コンテナにサービスを登録する.
フレームワーク自体や開発者は、サービスを登録できます.コンポーネントAがコンポーネントB(またはそのクラスのインスタンス)の呼び出しを要求する場合、コンポーネントBのインスタンスを作成するのではなく、コンテナからコンポーネントBの呼び出しを要求することができる.
この仕事のやり方は私たちに多くの利点を提供してくれました.
  • コンポーネントを交換して、それら自体またはサードパーティから簡単に作成できます.
  • コンポーネントがリリースされる前に、オブジェクトの初期化を十分に制御し、オブジェクトをさまざまに設定することができます.
  • コンポーネントから構造化されたグローバルインスタンス
  • を統一的に得ることができる.
    サービスは、以下の方法でコンテナに注入できます.
    <?php
    
    //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」名のサービスが存在するかどうかを最初に決定します.
    コンテナは、要求されたデータのインスタンスを返し、開発者は最終的に目的のコンポーネントを取得します.
    上記の例では、どの方法を使用するかは、開発中の特定のシーンによって決定されるメリットとデメリットがあります.
    1つの文字列でサービスを設定するのは簡単ですが、柔軟性に欠けています.サービスを設定する場合、配列を使用すると柔軟性が向上し、複雑なコードを使用できます.Lambda関数は両者のバランスが良いが、より多くのメンテナンス管理コストをもたらす可能性がある.
    PhalconDIは、サービスの遅延ロードを提供します.開発者がサービスを注入するときにオブジェクトを直接インスタンス化し、コンテナに保存しない限り.コンテナでは、配列、文字列などに格納されたサービスが遅延ロードされます.すなわち、オブジェクトが要求されたときにのみ初期化されます.
    <?php
    
    //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"
        ));
    });

    以上の2つのサービスの登録方式は同じ結果を生み出す.次に、配列によって定義され、後で必要に応じてサービスパラメータを変更できます.
    <?php
    
    $di->setParameter("db", 0, array(
        "host" => "localhost",
        "username" => "root",
        "password" => "secret"
    ));

    コンテナからサービスを得る最も簡単な方法は、「get」メソッドを使用して、コンテナから新しいインスタンスを返すことです.
    <?php $request = $di->get("request");

    あるいは、次のようなマジックメソッドの形式で呼び出されます.
    <?php $request = $di->getRequest();

    PhalconDIは同時にサービス再利用を許可し、インスタンス化されたサービスを得るためにgetShared()メソッドの形式でサービスを得ることができる.
    具体的なPhalconHttpRequestリクエストの例:
    <?php $request = $di->getShared("request");

    パラメータは、要求時に配列パラメータをコンストラクション関数に渡すこともできます.
    <?php $component = $di->get("MyComponent", array("some-parameter", "other"))

    心得:
    ここまで差は少なくてはっきり言って、実はビルの主人は最初は伝统的な工场の方法と依存注入の区别がいったいどこにあるのかを理解することができなくて、この文章を见てから豁然として明るくて、これは1种の视点の転换で、どのように言って、また少し进歩しました.