フェムトおよび依存性注入パターン。 パート1

PHPのDependency Injectionパターンの良い説明を見ていません。

最近、SymfonyのメンバーがDIコンテナをリリースし、このパターンの使用方法に関する詳細で優れた本を提供しました。

DI用の別のライブラリPhemtoを思い出しました 。 その著者は、 SimpleTestの作成者であるMarcus Bakerです。 残念ながら、このサイトには簡潔で不明瞭なヘルプが含まれています。 それにもかかわらず、プロジェクトは開発中であり、ディストリビューションの内部には、DI、そしてもちろんマニュアルに関する非常に良い説明の記事があります。 Phemtoは、それほど大きくない3つのファイルで構成される非常に小さなプロジェクトです。

この記事をロシア語に翻訳して、ここに掲載しておくと便利だと思いました。 この記事はそれほど大きくはありませんが、充実しています。 オリジナルへのリンクを提供できません。オリジナルはディストリビューション内にあります:)


プログラミング用語では、 Phemtoは軽量で自動化された依存関係注入コンテナーです。 簡単に言えば、 Phemtoのタスクは、オブジェクトをインスタンス化し、最小限の情報を取得することです。したがって、アプリケーションまたはフレームワーク内の依存関係を大幅に弱めます。

なぜこれが必要ですか?

DIパターンを理解する最も簡単な方法は、一端に「DIを使用」、他端に「ハードコーディング(つまり、ハードコーディングされた接続)を使用する」スケールを想像することです。 ここで、ハードコーディングから、 FactoryRegistryService Locatorの各パターンを介してDIに至るまでの短い旅行を手配します。 DIが何であるかをすでに知っている場合は、
Phemtoのインストール

new演算子を使用したオブジェクトの通常の作成は単純で簡単に見えますが、後で何かを変更したい場合は困難に直面する可能性があります。 コードを見てみましょう...


クラス MyController {
関数 __construct {
...
$ connection = new MysqlConnection ;
}
}


ここで、 MyControllerはMysqlConnectionに依存しています

新しい演算子は明確で理解しやすいものですが、 MyControllerはMySQLデータベースのみを使用できます。 継承できるようにクラスを少し作り直してもほとんど助けにはなりません。 その後、子コントローラーのロジックおよびデータベースドライバーを取得するためのロジックと共に、相続人がいます。 いずれにせよ、複数の依存関係は継承によって解決されないため、クラスが乱雑になります。 一般的に、継承カードは1回しかプレイできません。

次のステップは、 ファクトリを使用することです...


クラス MyController {
関数 __construct $ connection_pool {
...
$ connection = $ connection_pool- > getConnection ;
}
}


非常に効果的なソリューション。 ファクトリーは、構成ファイルを使用して、または明示的に、目的のドライバータイプに構成できます。 多くの場合、ファクトリーはさまざまなオブジェクトファミリーからオブジェクトを作成でき、それらは抽象ファクトリー (Abstract Fabroika)またはリポジトリー (リポジトリー)と呼ばれます。 ただし、制限があります。

工場は多くの余分なコードをもたらします。 モックオブジェクトを使用してクラスをテストする必要がある場合は、ファクトリーによって返されるオブジェクトだけでなく、ファクトリー自体もシミュレートする必要があります。 少し大騒ぎしてください。

はい。ライブコードでは、ファクトリの作成者が考えていなかったオブジェクトを返す必要がある場合、ファクトリ自体を継承または書き換える必要があります。これは、フレームワークにとって顕著な問題になる可能性があります。

中毒に対する私たちの戦いの次のステップは、外部のメインオブジェクトからレジストリオブジェクトの作成を取り出すことです...


クラス MyController {
関数 __construct $レジストリ {
...
$ connection = $ registry- > connection ;
}
}
...
$レジストリ = 新しいレジストリ ;
$ registry- > connection = new MysqlConnection ;
...
$ controller = new MyController $レジストリ ;


レジストリは完全に受動的ですが、メインコードでは多くのオブジェクトを作成してオーバーロードします。 必要のないオブジェクトの予約を誤って作成し、この場所をそのままにしておくこともできます。

さらに、このアプローチでは、遅延オブジェクトの読み込み(遅延読み込み)を使用できません。 失敗が待っています。アダプターの同じオブジェクトをデータベースに返さずに、異なるオブジェクトを返したい場合。

この例に考慮する必要がある依存関係がまだある場合、人生はすぐに悪化します。 つまり たとえば、アダプタオブジェクトを作成する場合、 newを作成するだけでは不十分ですが、コンストラクタに他のオブジェクトを追加する必要があります。 一般に、事前設定は非常に混乱する恐れがあります。

Registryオブジェクトが目的のオブジェクトのインスタンスを個別に作成できるようにすれば、 Registryパターンをより洗練させることができます。 私たちのオブジェクトはサービスロケーターになりました...


クラス MyController {
function __construct $ services {
...
$ connection = $ services- > connection ;
}
}
...
$ services = 新しい ServiceLocator ;
$ services- > connection 'MysqlConnection' ;
...
$コントローラー = 新しい MyController $サービス ;


現在、設定は任意の順序で実行できますが、 ServiceLocatorMysqlConnectionの作成方法を知っている必要があります。 パラメーターを渡すことは非常に骨の折れる作業ですが、問題は工場の助けまたはリフレクションのトリックの助けによって解決されます。 オブジェクトのライフサイクル(たとえば、同じオブジェクトを返す、または異なるオブジェクトを作成する)は、ファクトリメソッドですべてをプログラムするか、設定またはプラグインにすべてを入れることができるプログラマーの制御下にあります。

残念ながら、このほぼ銀の弾丸にはRegistryと同じ問題があります。 このようなインターフェイスを使用するクラスは、必然的にServiceロケーターに依存します 。 2つのシステムを異なるサービスロケーターと混在させようとすると、そのような「不運」を感じるでしょう。

依存性注入の方法は少し異なります。 最初の例を見てみましょう...


クラス MyController {
関数 __construct {
...
$ connection = new MysqlConnection ;
}
}


...そして、外部依存関係を作ります...

クラス MyController {
function __construct Connection $ connection {
...
}
}


一見、これはひどいです。 さて、結局のところ、スクリプトでは毎回、これらすべての依存関係を手で触らなければなりません。 データベースへのアダプターの変更は、100箇所で変更を行う必要があります。 だから、 新しいものを使用すると...


$インジェクター = new Phemto ;
$コントローラー = $インジェクター -> create 'MyController' ;


信じられないかもしれませんが、必要なのはそれだけです。

Phemtoのタスクは、オブジェクトの作成方法を特定することです。これにより、開発の自動化が驚くほどクールになります。 インターフェースのパラメーターのタイプによってのみ、 MysqlConnectionが目的の接続タイプを満たす唯一の候補であると推測されます。

より複雑な状況では、通常「チェーン」ファイルに含まれる追加情報が必要になる場合があります。 パターンの力を感じることができるように、実際の生活からそのようなファイルの例を示します...


require_once 'phemto / phemto.php' ;

$インジェクター = new Phemto ;
$インジェクター -> whenCreating 'Page' -> forVariable 'session' -> willUse new Reused 'Session' ;
$インジェクター -> whenCreating 'Page' -> forVariable 'continuation' -> willUse 'Continuation' ;
$インジェクター -> whenCreating 'Page' -> forVariable 'alerts' -> willUse 'Alert' ;
$インジェクター -> whenCreating 'Page' -> forVariable 'accounts' -> willUse 'Accounts' ;
$インジェクター -> whenCreating 'Page' -> forVariable 'mailer' -> willUse 'Mailer' ;
$インジェクター -> whenCreating 'Page' -> forVariable 'clock' -> willUse 'Clock' ;
$インジェクター -> whenCreating 'Page' -> forVariable 'request' -> willUse 'Request' ;
$インジェクタを 返し ます。


このような数の設定は、中規模のプロジェクトでは一般的です。

これで、コントローラーはインターフェイスのみを設定し、オブジェクトの作成作業は仲介者によって実行されます。
MyControllerは、 MysqlConnectionを認識しなくなります
しかし、 $インジェクターは両方について知っています。 これはInversion of Controlと呼ばれます。


パート2に続く

Source: https://habr.com/ru/post/J64061/


All Articles