PHP 依存関係の注入について理解する | LaravelIoC コンテナ

黄舟
リリース: 2023-03-03 14:20:02
オリジナル
1012 人が閲覧しました

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

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

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

1 <?php 
2  
3 class SomeComponent 
4 { 
5  
6     PRotected $_connection; 
7  
8     /** 
9      * Sets the connection externally
10      */
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 connection
28 $connection = new Connection(array(
29     "host" => "localhost",
30     "username" => "root",
31     "password" => "secret",
32     "dbname" => "invo"
33 ));
34 
35 //Inject the connection in the component
36 $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 SomeComponent
22 {
23 
24     protected $_connection;
25 
26     /**
27      * Sets the connection externally
28      */
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 registry
45 $some->setConnection(Registry::getConnection());
46 
47 $some->someDbTask();
ログイン後にコピー

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

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

1 <?php 
2  
3 class Registry 
4 { 
5  
6     protected static $_connection; 
7  
8     /** 
9      * Creates a connection
10      */
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 it
23      */
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 connection
35      */
36     public static function getNewConnection()
37     {
38         return self::_createConnection();
39     }
40 
41 }
42 
43 class SomeComponent
44 {
45 
46     protected $_connection;
47 
48     /**
49      * Sets the connection externally
50      */
51     public function setConnection($connection){
52         $this->_connection = $connection;
53     }
54 
55     /**
56      * This method always needs the shared connection
57      */
58     public function someDbTask()
59     {
60         $connection = $this->_connection;
61 
62         // ...
63     }
64
65     /**
66      * This method always needs a new connection
67      */
68     public function someOtherDbTask($connection)
69     {
70 
71     }
72 
73 }
74 
75 $some = new SomeComponent();
76 
77 //This injects the shared connection
78 $some->setConnection(Registry::getSharedConnection());
79 
80 $some->someDbTask();
81 
82 //Here, we always pass a new connection as parameter
83 $some->someOtherDbTask(Registry::getConnection());
ログイン後にコピー

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

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 parameters
11 $some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);
12 
13 // ... or using setters
14 
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 dependencies
 10      */
 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 }
ログイン後にコピー

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

実装方法¶


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

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

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

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

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


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

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

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

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

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

 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 service
 17         // Always returns a new connection
 18         $connection = $this->_di->get(&#39;db&#39;);
 19 
 20     }
 21 
 22     public function someOtherDbTask()
 23     {
 24 
 25         // Get a shared connection service,
 26         // this will return the same connection everytime
 27         $connection = $this->_di->getShared(&#39;db&#39;);
 28 
 29         //This method also requires a input filtering service
 30         $filter = $this->_db->get(&#39;filter&#39;);
 31 
 32     }
 33 
 34 }
 35 
 36 $di = new Phalcon\DI();
 37 
 38 //Register a "db" service in the container
 39 $di->set(&#39;db&#39;, 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 container
 49 $di->set(&#39;filter&#39;, function(){
 50     return new Filter();
 51 });
 52 
 53 //Register a "session" service in the container
 54 $di->set(&#39;session&#39;, function(){
 55     return new Session();
 56 });
 57 
 58 //Pass the service container as unique parameter59 $some = new SomeComponent($di);
 60 
 61 $some->someTask();
ログイン後にコピー

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

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

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

用一个字符串来设定一个服务非常简单,但缺少灵活性。设置服务时,使用数组则提供了更多的灵活性,而且可以使用较复杂的代码。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 function
 17 $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 <?php
2 
3 $di->setParameter("db", 0, array(
4     "host" => "localhost",
5     "username" => "root",
6     "password" => "secret"
7 ));
ログイン後にコピー

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

1 <?php
2      $request = $di->get("request")
ログイン後にコピー

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

1 <?php
2 
3 $request = $di->getRequest();
4 
5 Phalcon\DI 同时允许服务重用,为了得到一个已经实例化过的服务,可以使用 getShared() 方法的形式;
ログイン後にコピー

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

1 <?php
2 
3 $request = $di->getShared("request");
ログイン後にコピー

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

1 <?php
2 
3 $component = $di->get("MyComponent", array("some-parameter", "other"))
ログイン後にコピー

理解PHP依赖注入|LaravelIoC容器

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート