ホームページ  >  記事  >  バックエンド開発  >  PHP 依存関係インジェクションを理解する Laravel IoC コンテナー

PHP 依存関係インジェクションを理解する Laravel IoC コンテナー

WBOY
WBOYオリジナル
2016-06-23 13:19:13915ブラウズ

Laravel フレームワークの依存関係注入は確かに非常に強力で、コンテナーを介して実装された依存関係注入は必要なサービスを選択的にロードし、フレームワークの初期化のコストを増加させる可能性があります。 以下はインターネットで見た投稿です。この記事は、従来のクラスに従ったデータベース接続の設計から、コンテナを介してサービスを読み込む高度に分離された設計に至るまで、依存関係の挿入の力を示しています。これは参考になるものです。学ぶ。

------------------------------------------------ ----------分割線の下はダニエルの原文です---------------------------- -- ------------------------

原文リンク (http://www.yuansir-web.com/2014/03/) 20)

まず、SomeComponent という名前のコンポーネントを開発するとします。データベース接続がこのコンポーネントに挿入されます。この例では、データベース接続はコンポーネント内で作成されますが、これを行うと、データベース接続パラメータやデータベース タイプなどの一部のパラメータを変更できなくなります。

 1 <?php 2  3 class SomeComponent 4 { 5  6     /** 7      * The instantiation of the connection is hardcoded inside 8      * the component so is difficult to replace it externally 9      * or change its behavior10      */11     public function someDbTask()12     {13         $connection = new Connection(array(14             "host" => "localhost",15             "username" => "root",16             "password" => "secret",17             "dbname" => "invo"18         ));19 20         // ...21     }22 23 }24 25 $some = new SomeComponent();26 $some->someDbTask();

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

 1 <?php 2  3 class SomeComponent 4 { 5  6     protected $_connection; 7  8     /** 9      * Sets the connection externally10      */11     public function setConnection($connection)12     {13         $this->_connection = $connection;14     }15 16     public function someDbTask()17     {18         $connection = $this->_connection;19 20         // ...21     }22 23 }24 25 $some = new SomeComponent();26 27 //Create the connection28 $connection = new Connection(array(29     "host" => "localhost",30     "username" => "root",31     "password" => "secret",32     "dbname" => "invo"33 ));34 35 //Inject the connection in the component36 $some->setConnection($connection);37 38 $some->someDbTask();

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

 1 <?php 2  3 class Registry 4 { 5  6     /** 7      * Returns the connection 8      */ 9     public static function getConnection()10     {11        return new Connection(array(12             "host" => "localhost",13             "username" => "root",14             "password" => "secret",15             "dbname" => "invo"16         ));17     }18 19 }20 21 class SomeComponent22 {23 24     protected $_connection;25 26     /**27      * Sets the connection externally28      */29     public function setConnection($connection){30         $this->_connection = $connection;31     }32 33     public function someDbTask()34     {35         $connection = $this->_connection;36 37         // ...38     }39 40 }41 42 $some = new SomeComponent();43 44 //Pass the connection defined in the registry45 $some->setConnection(Registry::getConnection());46 47 $some->someDbTask();

ここで、コンポーネントに 2 つのメソッドを実装する必要があると想像してみましょう。1 つ目は新しいデータベース接続を作成する必要があり、2 つ目は常に共有接続を取得します。

 1 <?php 2  3 class Registry 4 { 5  6     protected static $_connection; 7  8     /** 9      * Creates a connection10      */11     protected static function _createConnection()12     {13         return new Connection(array(14             "host" => "localhost",15             "username" => "root",16             "password" => "secret",17             "dbname" => "invo"18         ));19     }20 21     /**22      * Creates a connection only once and returns it23      */24     public static function getSharedConnection()25     {26         if (self::$_connection===null){27             $connection = self::_createConnection();28             self::$_connection = $connection;29         }30         return self::$_connection;31     }32 33     /**34      * Always returns a new connection35      */36     public static function getNewConnection()37     {38         return self::_createConnection();39     }40 41 }42 43 class SomeComponent44 {45 46     protected $_connection;47 48     /**49      * Sets the connection externally50      */51     public function setConnection($connection){52         $this->_connection = $connection;53     }54 55     /**56      * This method always needs the shared connection57      */58     public function someDbTask()59     {60         $connection = $this->_connection;61 62         // ...63     }64 65     /**66      * This method always needs a new connection67      */68     public function someOtherDbTask($connection)69     {70 71     }72 73 }74 75 $some = new SomeComponent();76 77 //This injects the shared connection78 $some->setConnection(Registry::getSharedConnection());79 80 $some->someDbTask();81 82 //Here, we always pass a new connection as parameter83 $some->someOtherDbTask(Registry::getConnection());

それだけです 、依存関係注入を使用して問題を解決する方法を見てきました。コード内に依存関係を作成する代わりに、依存関係をパラメーターとして渡します。これにより、プログラムの保守が容易になり、プログラム コードの結合が減少し、一種の疎結合が実現します。しかし、長期的には、この形式の依存性注入にはいくつかの欠点もあります。

例えばコンポーネント内に依存関係が多い場合、渡すsetterメソッドを複数作成したり、渡すコンストラクタを作成したりする必要があります。さらに、コンポーネントを使用するたびに依存コンポーネントを作成する必要があるため、コードのメンテナンスが簡単ではありません。

 1 <?php 2  3 //Create the dependencies or retrieve them from the registry 4 $connection = new Connection(); 5 $session = new Session(); 6 $fileSystem = new FileSystem(); 7 $filter = new Filter(); 8 $selector = new Selector(); 9 10 //Pass them as constructor parameters11 $some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);12 13 // ... or using setters14 15 $some->setConnection($connection);16 $some->setSession($session);17 $some->setFileSystem($fileSystem);18 $some->setFilter($filter);19 $some->setSelector($selector);

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

 1 <?php 2  3 class SomeComponent 4 { 5  6     // ... 7  8     /** 9      * Define a factory method to create SomeComponent instances injecting its dependencies10      */11     public static function factory()12     {13 14         $connection = new Connection();15         $session = new Session();16         $fileSystem = new FileSystem();17         $filter = new Filter();18         $selector = new Selector();19 20         return new self($connection, $session, $fileSystem, $filter, $selector);21     }22 23 }

現時点では、問題の始まりに戻ったようです。コンポーネント内に依存関係を作成し、コンポーネントを修正して検索しています。問題の解決策は毎回ありますが、どれも良い方法ではありません。

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

 1 <?php 2  3 class SomeComponent 4 { 5  6     protected $_di; 7  8     public function __construct($di) 9     {10         $this->_di = $di;11     }12 13     public function someDbTask()14     {15 16         // Get the connection service17         // Always returns a new connection18         $connection = $this->_di->get('db');19 20     }21 22     public function someOtherDbTask()23     {24 25         // Get a shared connection service,26         // this will return the same connection everytime27         $connection = $this->_di->getShared('db');28 29         //This method also requires a input filtering service30         $filter = $this->_db->get('filter');31 32     }33 34 }35 36 $di = new Phalcon\DI();37 38 //Register a "db" service in the container39 $di->set('db', function(){40     return new Connection(array(41         "host" => "localhost",42         "username" => "root",43         "password" => "secret",44         "dbname" => "invo"45     ));46 });47 48 //Register a "filter" service in the container49 $di->set('filter', function(){50     return new Filter();51 });52 53 //Register a "session" service in the container54 $di->set('session', function(){55     return new Session();56 });57 58 //Pass the service container as unique parameter59 $some = new SomeComponent($di);60 61 $some->someTask();

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

PhalconDIはサービスの依存性注入機能を実装するコンポーネントであり、それ自体がコンテナでもあります。

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

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

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

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

この働き方には多くの利点があります:

我们可以更换一个组件,从他们本身或者第三方轻松创建。
在组件发布之前,我们可以充分的控制对象的初始化,并对对象进行各种设置。
我们可以使用统一的方式从组件得到一个结构化的全局实例

服务可以通过以下几种方式注入到容器:

 1 <?php 2  3 //Create the Dependency Injector Container 4 $di = new Phalcon\DI(); 5  6 //By its class name 7 $di->set("request", 'Phalcon\Http\Request'); 8  9 //Using an anonymous function, the instance will lazy loaded10 $di->set("request", function(){11     return new Phalcon\Http\Request();12 });13 14 //Registering directly an instance15 $di->set("request", new Phalcon\Http\Request());16 17 //Using an array definition18 $di->set("request", array(19     "className" => 'Phalcon\Http\Request'20 ));

在上面的例子中,当向框架请求访问一个请求数据时,它将首先确定容器中是否存在这个”reqeust”名称的服务。

容器会反回一个请求数据的实例,开发人员最终得到他们想要的组件。

在上面示例中的每一种方法都有优缺点,具体使用哪一种,由开发过程中的特定场景来决定的。

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

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

 1 <?php 2  3 //Register a service "db" with a class name and its parameters 4 $di->set("db", array( 5     "className" => "Phalcon\Db\Adapter\Pdo\Mysql", 6     "parameters" => array( 7           "parameter" => array( 8                "host" => "localhost", 9                "username" => "root",10                "password" => "secret",11                "dbname" => "blog"12           )13     )14 ));15 16 //Using an anonymous function17 $di->set("db", function(){18     return new Phalcon\Db\Adapter\Pdo\Mysql(array(19          "host" => "localhost",20          "username" => "root",21          "password" => "secret",22          "dbname" => "blog"23     ));24 });

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

1 <?php2 3 $di->setParameter("db", 0, array(4     "host" => "localhost",5     "username" => "root",6     "password" => "secret"7 ));

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

1 <?php2      $request = $di->get("request");

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

1 <?php2 3 $request = $di->getRequest();4 5 Phalcon\DI 同时允许服务重用,为了得到一个已经实例化过的服务,可以使用 getShared() 方法的形式来获得服务。

具体的 Phalcon\Http\Request 请求示例:

1 <?php2 3 $request = $di->getShared("request");

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

1 <?php2 3 $component = $di->get("MyComponent", array("some-parameter", "other"))

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
前の記事:yii2開発後記次の記事:yii2開発後記