こんにちは。よっしーです(^^)
今日は、PHP-DIにおける遅延インジェクションについてご紹介します。
背景
PHP-DIに触れる機会がありましたので、PHP-DIにおける遅延インジェクションについて備忘として残しました。
詳細は下記の公式サイトをご覧ください。
はじめに
この機能は、オブジェクトの遅延初期化(lazy initialization)と混同しないように注意してください。PHP-DIは常にオブジェクトを、それがリクエストされるかどこかにインジェクトされる時にのみ作成します。
遅延インジェクションはこれ以上のことを行います。それはオブジェクトの依存関係の作成を、実際に使用される瞬間まで遅延させることを可能にし、それ以前には作成しません。
注意: この機能は例外的にのみ使用すべきであり、最後のセクションの「使用するタイミング」セクションを読んでください。
class ProductExporter
{
private $pdfWriter;
private $csvWriter;
public function __construct(PdfWriter $pdfWriter, CsvWriter $csvWriter)
{
$this->pdfWriter = $pdfWriter;
$this->csvWriter = $csvWriter;
}
public function exportToPdf()
{
$this->pdfWriter->write(...);
}
public function exportToCsv()
{
$this->csvWriter->write(...);
}
}
$productExporter = $container->get(ProductExporter::class);
$productExporter->exportToCsv();
この例では、exportToPdf() は呼び出されていません。PdfWriter はクラス内で初期化され、インジェクトされていますが、使用されていません。
PdfWriter の初期化にコストがかかる場合(たとえば多くの依存関係がある場合や、コンストラクタで高コストの処理を行う場合など)、遅延インジェクションは、オブジェクトが使用されるまでインスタンス化を避けるのに役立ちます。
動作原理
遅延(lazy)オブジェクトとしてオブジェクトを定義すると、PHP-DIは次のようにインジェクションを行います:
- オブジェクトが既に作成されている場合、そのオブジェクトをインジェクトします。
- そうでない場合、まだ作成されていないオブジェクトへのプロキシ(代理オブジェクト)をインジェクトします。
プロキシは、元のオブジェクトと見た目や振る舞いがまったく同じ特別な種類のオブジェクトです。したがって、違いはわかりません。プロキシは、元のオブジェクトが必要な時にのみインスタンス化されます。
プロキシの作成は複雑です。これには、Doctrine、Symfony、Zendなどでも使用されている(素晴らしい)ProxyManagerライブラリをPHP-DIが利用しています。
例を通じて説明します。簡単にするため、遅延オブジェクトをインジェクトするのではなく、コンテナに対して遅延オブジェクトを返すように求める例を示します:
class Foo
{
public function doSomething()
{
}
}
$container->set('Foo', \DI\create()->lazy());
// $proxyはProxyオブジェクトで、初期化されていません
// メモリ内で非常に軽量です
$proxy = $container->get('Foo');
var_dump($proxy instanceof Foo); // true
// プロキシ上のメソッドを呼び出すと、初期化されます
$proxy->doSomething();
// これでプロキシが初期化され、実際のFooのインスタンスが作成されて呼び出されました
この例では、まず遅延で定義されたオブジェクトのプロキシが取得され、そのプロキシがメソッドを呼び出されたときに初めて本物のオブジェクトが作成されます。プロキシの使用によって、必要な場合にのみオブジェクトがインスタンス化され、パフォーマンスの向上が期待されます。
使用方法
遅延インジェクションを使用する方法について説明します。
インストール
遅延インジェクションにはOcramius/ProxyManagerライブラリが必要です。このライブラリはPHP-DIとデフォルトでインストールされていないため、以下のコマンドでインストールする必要があります:
composer require ocramius/proxy-manager
PHPの設定ファイル
return [
'foo' => DI\create('MyClass')
->lazy(),
];
属性(Attributes)を使用する場合
use DI\Attribute\Injectable;
#[Injectable(lazy: true)]
class MyClass
{
}
PHPコード内で直接使用する場合
$container->set('foo', \DI\create('MyClass')->lazy());
遅延インジェクションを使用することで、オブジェクトが必要になるまでインスタンス化を遅延できるため、効率的なリソース使用を実現できます。ただし、遅延インジェクションは必要な場面にのみ使用するべきであり、過度に使用するとコードの可読性が低下する可能性があるため、注意が必要です。
いつ使用するすべきか
遅延インジェクションは、特定のオブジェクトに対してプロキシオブジェクトを生成するため、アプリケーション全体で頻繁に使用することは推奨されません。遅延インジェクションをいくつかの特定のケースで使用することが適切です。
プロキシは非常に最適化されていますが、遅延化するオブジェクトがコンストラクタに時間がかかる場合(例:データベースへの接続、ファイルへの書き込みなど)、そのオブジェクトを遅延インジェクションに指定することで効果があります。
パフォーマンスの最適化
遅延インジェクションを使用すると、指定したクラスのプロキシを生成する必要があります。
デフォルトでは、これらのプロキシはHTTPリクエストごとに生成されます。これは開発中には適していますが、本番環境では適切ではありません。
本番環境では、プロキシをファイルに書き込むようにすることをお勧めします:
// プロキシを tmp/proxies ディレクトリに書き込むように設定
$containerBuilder->writeProxiesToFile(true, __DIR__ . '/tmp/proxies');
プロキシディレクトリ内のファイルをデプロイごとにクリアすることで、古いプロキシが残らないようにする必要があります。
コンテナをコンパイルする際にプロキシクラスを生成する方法# デフォルトでは、プロキシは初めて使用される際にディスクに書き込まれます。
コンテナをコンパイルする際にプロキシクラスを事前に生成するには、コンテナのコンパイルを有効にする必要があります:
// コンパイル時にプロキシを var/cache ディレクトリに書き込むように設定
$containerBuilder->enableCompilation(__DIR__ . '/var/cache');
$containerBuilder->writeProxiesToFile(true, __DIR__ . '/var/cache');
この機能を使用するには、上記の両方の設定オプションを設定する必要があります。
おわりに
今日は、PHP-DIにおける遅延インジェクションについてご紹介しました。
何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)
コメント