こんにちは。よっしーです(^^)
今日は、PHP-DIにおけるキャッシュについてご紹介します。
背景
PHP-DIに触れる機会がありましたので、PHP-DIにおけるキャッシュについて備忘として残しました。
詳細は下記の公式サイトをご覧ください。
はじめに
PHP-DI 4および5は、キャッシングに大いに依存していました。しかし、PHP-DI 6では、最適化の主な手段はコンテナを高度に最適化されたコードにコンパイルすることです(以下を参照)。コンテナのコンパイルは、簡単で高速です。
コンテナのコンパイル
PHP-DIは2つのタスクを実行するためにかかる負荷を軽減することができます:
- 設定、オートワイヤリング、属性からの定義の読み取り
- それらの定義を解決してサービスを作成する
これらの2つのタスクを回避するために、コンテナは特に設定とクラスに最適化されたPHPコードにコンパイルされることができます。
セットアップ(Setup)
コンテナをコンパイルするのは、コンテナビルダーのenableCompilation()メソッドを呼び出すだけです。
$containerBuilder = new \DI\ContainerBuilder();
$containerBuilder->enableCompilation(__DIR__ . '/var/cache');
// […]
$container = $containerBuilder->build();
enableCompilation()メソッドは、コンパイルされたコンテナを格納するディレクトリのパスを受け取ります。
本番環境でのデプロイ
コンテナがコンパイルされるように設定されている場合、コンテナは1回だけコンパイルされ、再生成されることはありません。これにより、本番環境での最大のパフォーマンスが可能となります。
本番環境に新しいバージョンのコードをデプロイする際には、生成されたファイル(またはそれを含むディレクトリ)を削除して、コンテナが再コンパイルされることを確認する必要があります。
もし本番環境が多くのトラフィックを処理している場合、新しいコードのバージョンがライブになる前にコンパイル済みコンテナを生成することも検討するかもしれません。この段階は「ウォームアップ」フェーズとして知られています。これを行うには、デプロイ手順の間にコンテナを作成($containerBuilder->build()を呼び出す)し、コンパイル済みコンテナが作成されます。
開発環境
開発環境ではコンテナをコンパイルしないでください。そうしないと、定義(属性、設定ファイルなど)に加えたすべての変更が考慮されない可能性があります。以下は、行うべきことの例です。
$containerBuilder = new \DI\ContainerBuilder();
if (/* 本番環境かどうかの判定 */) {
$containerBuilder->enableCompilation(__DIR__ . '/var/cache');
}
コンパイル最適化
「動作原理」セクションで説明したように、PHP-DIは利用可能なすべての定義を収集してコンパイルします。そのため、設定にリストされていないオートワイヤリングされたクラスのような定義は、PHP-DIが認識していないためにコンパイルできません。
最適なパフォーマンスを得るために、冗長性を増す代わりに、すべてのオートワイヤリングされたクラスを定義ファイルにリストアップしてPHP-DIに知らせることができます。
return [
// ... (your definitions)
UserController::class => autowire(),
BlogController::class => autowire(),
ProductController::class => autowire(),
// ...
];
これらのクラスを設定する必要はありません(オートワイヤリングが引き続き担当します)、ただし、少なくともPHP-DIはこれらのクラスについて認識し、その定義をコンパイルします。
現在、PHP-DIはオートワイヤリングやアノテーションを使用してクラスを自動的に検索することはありません。
また、コンパイルプロセス中にワイルドカード定義を解決することはありません。これらの定義は引き続き正常に機能しますが、コンパイルされたコンテナを使用してもパフォーマンスの向上は期待できません。
一方で、ファクトリ定義(クロージャまたはクラスファクトリで定義されたもの)は、コンパイルされたコンテナでサポートされています。ただし、クロージャをファクトリとして使用する場合は次の制限があります:
- クロージャ内では$thisを使用しないでください。
- useキーワードを使用してクロージャ内で変数をインポートしないでください。たとえば、function () use ($foo) { …という形式は避けてください。
これらの制限は、各クロージャのコードがコンパイルされたコンテナにコピーされるためです。コンテナをコンパイルしない場合でも、おそらくこれらのことを行うべきではないと言えるでしょう。
動作原理
PHP-DIは、設定から定義を読み取ります。コンテナがコンパイルされると、これらの定義に基づいてPHPコードが生成されます。
たとえば、オブジェクトを作成するための定義を見てみましょう。
return [
'Logger' => DI\create()
->constructor('/tmp/app.log')
->method('setLevel', 'warning'),
];
この定義は、次のようなPHPコードにコンパイルされます。
$object = new Logger('/tmp/app.log');
$object->setLevel('warning');
return $object;
すべてのコンパイルされた定義は、PHPクラス(コンパイルされたコンテナ)にダンプされ、ファイル(たとえばCompiledContainer.php)に書き込まれます。
実行時に、コンテナビルダーはファイルCompiledContainer.phpが存在することを確認し、それをロードします(定義ファイルをロードする代わりに)。そのPHPファイルには多くのコードが含まれるかもしれませんが、PHPのオペコードキャッシュはそのクラスをメモリにキャッシュします(本番環境ではopcacheを使用することを忘れないでください)。定義を解決する必要があるとき、PHP-DIは単純にコンパイルされたコードを実行して生成されたインスタンスを返します。
キャッシング
コンテナのコンパイルは最も効率的な解決策ですが、いくつかの制限があります。次の場合は最適化されません:
設定で宣言されていないオートワイヤリング(またはアノテーション)されたクラス ワイルドカード定義 Container::make()またはContainer::injectOn()の使用(コンパイルされたコードを使用していないため) これらの機能を多く使用し、アプリケーションの速度が遅くなる場合は、キャッシュシステムを有効にすることができます。キャッシュは、反射がリクエストごとに再度読み込まれないようにします。
キャッシュは、APCuを直接使用しています。これは理にかなったキャッシュシステムであり、非常に高速に書き込みと読み取りが行えます。他のキャッシュは適切な選択肢ではないため、PHP-DIはこのキャッシュに対してPSR-6またはPSR-16を使用しません。
キャッシュを有効にするには:
$containerBuilder = new \DI\ContainerBuilder();
if (/* 本番環境かどうかの判定 */) {
$containerBuilder->enableDefinitionCache();
}
また、enableDefinitionCache(‘my-namespace’)というオプションのネームスペース引数を渡すこともできます。これにより、提供されたネームスペースがすべてのPHP-DIキャッシュキーに追加されます。これにより、複数のDIコンテナ間で単一のAPCuメモリプールを共有する場合にキャッシュの衝突を防ぐのに役立ちます。以下は、ネームスペースありとなしの状態でのMyClassというクラスに対するPHP-DIキャッシュキーの例です。
ネームスペースあり:php-di.definitions.my-namespaceMyClass
ネームスペースなし:php-di.definitions.MyClass
注意
開発環境ではキャッシュを使用しないでください。そうしないと、定義(属性、設定ファイルなど)を変更しても反映されない可能性があります。 本番環境でデプロイごとにAPCuキャッシュをクリアしてください(古いキャッシュを使用しないため)。
おわりに
今日は、PHP-DIにおけるキャッシュについてご紹介しました。
何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)
コメント