こんにちは。よっしーです(^^)
今日は、PHP-DIにおけるコンテナの使用方法についてご紹介します。
背景
PHP-DIに触れる機会がありましたので、PHP-DIにおけるコンテナの使用方法について備忘として残しました。
詳細は下記の公式サイトをご覧ください。
get() および has()
コンテナはPSR-11標準を実装しています。これは、Psr\Container\ContainerInterfaceを実装していることを意味します。
namespace Psr\Container;
interface ContainerInterface
{
public function get($id);
public function has($id);
}
可能な限り、実装(DI\Container)ではなく、このインターフェースに対して型ヒントを使用することをお勧めします。これにより、コードがPHP-DIから切り離され、別のコンテナにいつでも切り替えることができます。
set()
コンテナにエントリを直接設定できます。
$container->set('foo', 'bar');
$container->set('MyInterface', \DI\create('MyClass'));
// クロージャを生の値として設定する必要がある場合は、\DI\valueを使用します。
// クロージャはデフォルトでファクトリとして解釈されるためです。
$container->set('myClosure', \DI\value(function() { /* ... */ }));
ただし、定義ファイルを使用することを推奨します。定義ファイルについては後日、ご紹介します。
make()
コンテナは、make() メソッドも提供しています。このメソッドは DI\FactoryInterface で定義されています。
class GithubProfile
{
public function __construct(ApiClient $client, $user)
...
}
$container->make('GithubProfile', [
'user' => 'torvalds',
]);
make() メソッドは、get() メソッドと同様に機能しますが、呼び出されるたびにエントリを解決します。エントリの種類に応じて、次のように動作します:
- エントリがオブジェクトである場合、新しいインスタンスが毎回作成されます。
- エントリがファクトリである場合、ファクトリが毎回呼び出されます。
- エントリがエイリアスである場合、エイリアスが毎回解決されます。
ただし、毎回解決されるのは要求したエントリだけであり、エントリの依存関係全体ではありません。つまり、エントリがエイリアスである場合、エイリアスが指すエントリは一度だけ解決されます。
Container::make() の第2引数にパラメータを提供し、解決するエントリがオブジェクトを作成する場合、提供されたパラメータはオブジェクトのコンストラクタに使用され、不足しているパラメータはコンテナから解決されます。
Container::make() は、コンテナ内に格納されるべきでないオブジェクト(つまり、サービスではないか、ステートレスでないもの)を作成するために便利です。また、オブジェクトのコンストラクタの一部のパラメータを上書きしたい場合にも役立ちます。
もしサービスやコントローラ内で make() メソッドを使用する必要がある場合、FactoryInterface に対して型ヒントをすることをおすすめします。これにより、コードがコンテナに結合されることを避けることができます。DI\FactoryInterface は DI\Container に自動的にバインドされているため、設定なしで注入することができます。
call()
コンテナは、どんなPHP callable(呼び出し可能なもの)でも呼び出すことができる call() メソッドを公開しています。
call_user_func() を使用する場合に比べて、次の追加機能を提供します:
- 名前付きパラメータ(位置ではなく名前でパラメータを渡すことができます)
$container->call(function ($foo, $bar) {
// ...
}, [
'foo' => 'Hello',
'bar' => 'World',
]);
// Can also be useful in a micro-framework for example
$container->call($controller, $_GET + $_POST);
- 型ヒントに基づく依存性の注入
$container->call(function (Logger $logger, EntityManager $em) {
// ...
});
- 明示的な定義に基づく依存性の注入
$container->call(function ($dbHost) {
// ...
}, [
// パラメータ名でインデックス付けされる
'dbHost' => \DI\get('db.host'),
]);
$container->call(function ($dbHost) {
// ...
}, [
// インデックスなしでも
\DI\get('db.host'),
]);
これらをすべて組み合わせることができます。
$container->call(function (Logger $logger, $dbHost, $operation) {
// ...
}, [
'operation' => 'delete',
'dbHost' => \DI\get('db.host'),
]);
call() メソッドは、特にコントローラを呼び出すために便利です。
$controller = function ($name, EntityManager $em) {
// ...
}
$container->call($controller, $_GET); // $_GETには ['name' => 'John'] が含まれています
これにより、コントローラを書く開発者がリクエストパラメータと依存性の注入を使用してリクエストパラメータとサービスを取得する自由が与えられます。
make() と同様に、call() も Invoker\InvokerInterface(PHP-DI/Invoker パッケージ内)で定義されています。したがって、このインターフェースに型ヒントを指定し、コンテナに結合しないようにすることができます。Invoker\InvokerInterface は自動的に DI\Container にバインドされているため、設定なしで注入することができます。
namespace Invoker;
interface InvokerInterface
{
public function call($callable, array $parameters = []);
}
Container::call() は、次のような呼び出し可能なものを呼び出すことができます。
- クロージャ
- 関数
- オブジェクトのメソッドおよび静的メソッド
- __invoke() を実装した呼び出し可能オブジェクト(Invokable Objects)
さらに、次のような呼び出しも行えます。
- 呼び出し可能クラスの名前:
$container->call('My\CallableClass')
- オブジェクトのメソッド(オブジェクトではなくクラス名を指定します):
$container->call(['MyClass', 'someMethod'])
どちらの場合も、’My\CallableClass’ および ‘MyClass’ は、$container->get() を使用してコンテナによって解決されます。
これにより、より冗長な形式を避けることができます。例えば:
$object = $container->get('My\CallableClass');
$container->call($object);
// 以下のように書けます
$container->call('My\CallableClass');
injectOn()
時には、既に作成されたオブジェクトに依存関係を注入する必要があります。
例えば、古いフレームワークの中でコントローラの作成方法を制御できない場合があります。injectOn() を使用することで、オブジェクトが作成された後にコンテナに依存関係の解決を依頼することができます。
通常は、injectOn() の代わりに get() または make() を使用する方が良いです。本当に必要な場所でのみ使用してください。
class UserController extends BaseController
{
#[Inject]
private SomeService $someService;
public function __construct()
{
// フレームワークはコントローラの作成方法を制御できないため、
// コンテナを使用してコントローラを作成することはできません。
// したがって、コンテナに依存関係の注入を依頼します。
$container->injectOn($this);
// これで依存関係が注入されました
$this->someService->doSomething();
}
}
おそらく推測している通り、このメソッドではコンストラクタインジェクションを使用することはできません。ただし、属性または設定ファイルでオブジェクトを設定した場合、他の種類のインジェクション(プロパティまたはセッター)は機能します。
コンテナの拡張
コンテナの内部を知りたい場合は、@api アノテーションのついたクラスやインターフェイスのみを PHP-DI で使用するようにしましょう。
それ以外のクラスやインターフェイスは内部クラスであり、 それを使用することは可能ですが、マイナーバージョン間であっても 後方互換性は保証されません。
おわりに
今日は、PHP-DIにおけるコンテナの使用方法についてご紹介しました。
何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)
コメント