注: opsの質問に対する直接的な答えは、「すべての要求ではなく、必要なときにのみデータベースを作成/接続できるのはいつですか」は、必要なときに注入することですが、単に役に立たないと言っています。この点に関して、非特定のフレームワークのコンテキストでは実際に役立つ情報があまりないため、実際にどのようにそれを正しく行うかを説明しています。
更新:この質問に対する「古い」回答は以下を参照してください。これにより、非常に物議を醸し、多くの人にとって「アンチパターン」であるサービスロケーターパターンが助長されました。調査から学んだことで新しい回答が追加されました。最初に古い回答を読んで、これがどのように進行したかを確認してください。
新しい答え
にきびをしばらく使用した後、私はそれがどのように機能するか、そして実際にはそれほど驚くべきことではないことについて多くのことを学びました. それでもかなりクールですが、コードが 80 行しかないのは、基本的にクロージャーの配列を作成できるためです。Pimple はサービス ロケータとしてよく使用されます (実際にできることは非常に限られているため)。これは「アンチパターン」です。
まず、サービスロケーターとは何ですか?
サービス ロケーター パターンは、ソフトウェア開発で使用される設計パターンであり、強力な抽象化レイヤーを使用してサービスの取得に関連するプロセスをカプセル化します。このパターンは、「サービス ロケータ」と呼ばれる中央レジストリを使用します。このレジストリは、要求に応じて、特定のタスクを実行するために必要な情報を返します。
ブートストラップでピンプルを作成し、依存関係を定義してから、インスタンス化したすべてのクラスにこのコンテナを渡しました。
サービスロケーターが悪いのはなぜですか?
あなたが言うこの問題は何ですか?主な問題は、このアプローチが依存関係をクラスから隠していることです。したがって、開発者がこのクラスを更新しようとしていて、これまでに見たことがない場合、不明な量のオブジェクトを含むコンテナ オブジェクトが表示されることになります。また、このクラスをテストするのは少し悪夢のようです。
なぜ私はもともとこれをしたのですか?コントローラーの後に依存性注入を開始すると思ったからです。これは間違っています。コントローラーレベルですぐに開始します。
これが私のアプリケーションでの動作方法である場合:
フロントコントローラー-->ブートストラップ-->ルーター-->コントローラー/メソッド-->モデル [サービス|ドメインオブジェクト|マッパー] -->コントローラー-->ビュー-->テンプレート
...その後、依存性注入コンテナーは、最初のコントローラー レベルですぐに動作を開始する必要があります。
つまり、まだ pimple を使用しているとしたら、作成するコントローラーと必要なものを定義することになります。したがって、ビューとモデル レイヤーからコントローラーに何かを挿入して、それを使用できるようにします。これは制御の反転であり、テストがはるかに簡単になります。Aurn wiki から (これについてはすぐに説明します):
実生活では、必要な部品にアクセスできるように、金物店全体を (できれば) 建設現場に運ぶことで家を建てることはありません。代わりに、職長 (__construct()) は、必要になる特定の部品 (ドアと窓) を要求し、それらの調達に取り掛かります。オブジェクトは同じように機能する必要があります。ジョブを実行するために必要な特定の依存関係のみを要求する必要があります。House にハードウェア ストア全体へのアクセスを許可することは、せいぜい貧弱な OOP スタイルであり、最悪の場合、保守性の悪夢です。- Auryn Wiki より
オーリンに入る
その点で、週末に紹介された Rdlowrey によって書かれた Auryn という素晴らしいものを紹介したいと思います。
Auryn は、クラス コンストラクターのシグネチャに基づいて、クラスの依存関係を「自動配線」します。これが意味することは、要求されたクラスごとに、Auryn がそれを見つけ、コンストラクターで必要なものを把握し、最初に必要なものを作成してから、最初に要求したクラスのインスタンスを作成するということです。仕組みは次のとおりです。
プロバイダーは、コンストラクター メソッドのシグネチャで指定されたパラメーターの型ヒントに基づいて、クラスの依存関係を再帰的にインスタンス化します。
...そして、 PHP のリフレクションについて少しでも知っていれば、それを「遅い」と呼ぶ人がいることを知っているでしょう。そこで、Auryn はそれについて次のように説明します。
「反射が遅い」と聞いたことがあるかもしれません。何かを明確にしましょう。間違ったやり方をすると、何でも「遅すぎる」可能性があります。リフレクションは、ディスク アクセスよりも 1 桁速く、(たとえば) リモート データベースから情報を取得するよりも数桁高速です。さらに、速度が心配な場合は、リフレクションごとに結果をキャッシュする機会が提供されます。Auryn は、潜在的なパフォーマンスへの影響を最小限に抑えるために、生成したリフレクションをキャッシュします。
「反射が遅い」という議論は飛ばしてしまいました。
オーリンの使い方
Auryn をオートローダーの一部にしています。これは、クラスが要求されたときに、Auryn が離れてクラスとその依存関係、およびその依存関係の依存関係 (など) を読み取り、それらすべてをインスタンス化のためにクラスに返すことができるようにするためです。Auyrn オブジェクトを作成します。
$injector = new \Auryn\Provider(new \Auryn\ReflectionPool);
データベース クラスのコンストラクターの要件としてデータベースインターフェイスを使用します。そこで、どの具体的な実装を使用するかを Auryn に伝えます (これは、別の種類のデータベースをインスタンス化したい場合に変更する部分であり、コード内の 1 つのポイントで、すべて機能します)。
$injector->alias('Library\Database\DatabaseInterface', 'Library\Database\MySQL');
MongoDB に変更する必要があり、そのためのクラスを作成した場合は、単純に に変更Library\Database\MySQL
しLibrary\Database\MongoDB
ます。
次に、をルーターに渡し$injector
ます。コントローラー/メソッドを作成するときに、依存関係が自動的に解決されます。
public function dispatch($injector)
{
// Make sure file / controller exists
// Make sure method called exists
// etc...
// Create the controller with it's required dependencies
$class = $injector->make($controller);
// Call the method (action) in the controller
$class->$action();
}
最後に、OPの質問に答えます
さて、この手法を使用して、データベース アクセスを必要とするユーザー サービス (ユーザー モデルとしましょう) を必要とするユーザー コントローラーがあるとします。
class UserController
{
protected $userModel;
public function __construct(Model\UserModel $userModel)
{
$this->userModel = $userModel;
}
}
class UserModel
{
protected $db;
public function __construct(Library\DatabaseInterface $db)
{
$this->db = $db;
}
}
ルーターでコードを使用すると、Auryn は次のことを行います。
- MySQL を具象クラス (ブーストラップでエイリアス) として使用して、Library\DatabaseInterface を作成します。
- 以前に作成したデータベースを挿入して「UserModel」を作成します
- 以前に作成した UserModel を注入して UserController を作成します。
それが再帰であり、これが先ほど話した「自動配線」です。クラス階層にコンストラクター要件としてデータベースオブジェクトが含まれている場合にのみ、オブジェクトがインスタンス化されるため、これにより OP の問題が解決されます。
また、各クラスには、コンストラクターで機能するために必要な要件が正確に含まれているため、サービス ロケーター パターンの場合のような隠れた依存関係はありません。
RE: 必要に応じて connect メソッドが呼び出されるようにする方法。これは本当に簡単です。
- Database クラスのコンストラクターで、オブジェクトをインスタンス化せず、その設定 (ホスト、データベース名、ユーザー、パスワード) を渡すだけであることを確認してください。
new PDO()
クラスの設定を使用して、実際にオブジェクトを実行する connect メソッドを用意します。
class MySQL implements DatabaseInterface
{
private $host;
// ...
public function __construct($host, $db, $user, $pass)
{
$this->host = $host;
// etc
}
public function connect()
{
// Return new PDO object with $this->host, $this->db etc
}
}
したがって、データベースを渡すすべてのクラスにはこのオブジェクトがありますが、connect() が呼び出されていないため、まだ接続されていません。
- Database クラスにアクセスできる関連モデルで、呼び出し
$this->db->connect();
てから、やりたいことを続けます。
本質的には、以前に説明したメソッドを使用して、データベース オブジェクトをそれを必要とするクラスに渡しますが、メソッドごとに接続をいつ実行するかを決定するには、必要なメソッドで connect メソッドを実行するだけです。 1。いいえ、シングルトンは必要ありません。必要なときにいつ接続するかを指示するだけで、接続するように指示しないと指示しません。
古い回答
依存性注入コンテナーについてもう少し詳しく説明し、それらがどのように状況に役立つかを説明します。注: 「MVC」の原則を理解すると、ここで非常に役立ちます。
問題
いくつかのオブジェクトを作成したいが、データベースにアクセスする必要があるのは特定のオブジェクトだけです。現在行っているのは、各 requestでデータベース オブジェクトを作成することです。これはまったく不要であり、DiC コンテナーなどを使用する前にはまったく一般的です。
2 つのサンプル オブジェクト
作成する可能性のある 2 つのオブジェクトの例を次に示します。1 つはデータベース アクセスを必要とし、もう 1 つはデータベース アクセスを必要としません。
/**
* @note: This class requires database access
*/
class User
{
private $database;
// Note you require the *interface* here, so that the database type
// can be switched in the container and this will still work :)
public function __construct(DatabaseInterface $database)
{
$this->database = $database;
}
}
/**
* @note This class doesn't require database access
*/
class Logger
{
// It doesn't matter what this one does, it just doesn't need DB access
public function __construct() { }
}
では、これらのオブジェクトを作成し、関連する依存関係を処理し、関連するクラスにのみデータベース オブジェクトを渡す最善の方法は何でしょうか? 幸運なことに、依存性注入コンテナーを使用すると、これら 2 つが調和して機能します。
にきびに入る
Pimpleは、 PHP 5.3+ のクロージャーを利用する (Symfony2 フレームワークのメーカーによる) 非常にクールな依存性注入コンテナーです。
pimple のやり方は実にクールです。必要なオブジェクトは、直接要求するまでインスタンス化されません。したがって、大量の新しいオブジェクトをセットアップできますが、要求するまでそれらは作成されません!
これは、 boostrapで作成する非常に単純なにきびの例です。
// Create the container
$container = new Pimple();
// Create the database - note this isn't *actually* created until you call for it
$container['datastore'] = function() {
return new Database('host','db','user','pass');
};
次に、User オブジェクトと Logger オブジェクトをここに追加します。
// Create user object with database requirement
// See how we're passing on the container, so we can use $container['datastore']?
$container['User'] = function($container) {
return new User($container['datastore']);
};
// And your logger that doesn't need anything
$container['Logger'] = function() {
return new Logger();
};
素晴らしい!では、実際に $container オブジェクトをどのように使用すればよいのでしょうか?
良い質問!したがって、ブートストラップ$container
でオブジェクトを作成し、オブジェクトとその必要な依存関係を設定しました。ルーティング メカニズムでは、コンテナーをコントローラーに渡します。
注: 初歩的なコードの例
router->route('controller', 'method', $container);
コントローラーで、$container
渡されたパラメーターにアクセスし、そこからユーザー オブジェクトを要求すると、データベース オブジェクトが既に挿入された新しいユーザー オブジェクト (ファクトリー スタイル) が返されます!
class HomeController extends Controller
{
/**
* I'm guessing 'index' is your default action called
*
* @route /home/index
* @note Dependant on .htaccess / routing mechanism
*/
public function index($container)
{
// So, I want a new User object with database access
$user = $container['User'];
// Say whaaat?! That's it? .. Yep. That's it.
}
}
解決したこと
つまり、1 つの石で (2 羽だけでなく) 複数の鳥を殺したということです。
- リクエストごとに DB オブジェクトを作成する- もう必要ありません! Pimple が使用するクロージャのため、要求したときにのみ作成されます
- コントローラーから「新しい」キーワードを削除する- そうです。この責任をコンテナに引き渡しました。
注:先に進む前に、箇条書き 2 がいかに重要かを指摘したいと思います。このコンテナーがない場合、アプリケーション全体で 50 個のユーザー オブジェクトを作成したとします。そしてある日、新しいパラメータを追加したいとします。OMG - アプリケーション全体を調べて、このパラメータを every に追加する必要がありますnew User()
。ただし、DiC を使用すると$container['user']
、どこでも使用している場合は、この 3 番目のパラメーターをコンテナーに一度追加するだけで済みます。はい、それはまったく素晴らしいです。
- データベースを切り替える機能- お聞きしましたが、これの要点は、MySQL から PostgreSQL に変更したい場合、コンテナ内のコードを変更して、コーディングした新しい異なるタイプのデータベースを返すということです。すべてが同じ種類のものを返す限り、それだけです! 誰もが常に口論する具体的な実装を交換する機能。
重要な部分
これはコンテナーの使用方法の1 つであり、ほんの始まりにすぎません。これを改善するには多くの方法があります。たとえば、コンテナをすべてのメソッドに渡す代わりに、リフレクションや何らかのマッピングを使用して、コンテナのどの部分が必要かを判断できます。これを自動化すれば、あなたはゴールデンです。
これがお役に立てば幸いです。ここで行った方法により、少なくともかなりの量の開発時間が短縮されました。起動するのはとても楽しいです!