コードの事前ロード

PHP 7.4.0 以降では、エンジンの起動時に opcache に事前ロードするスクリプトを指定できるようになりました。 指定されたファイルに存在するあらゆる 関数、クラス、 インターフェイス や トレイト (定数は除く) は、 明示的にインクルードすることなく全てのリクエストからグローバルに利用できるようになります。 これにより(コードが常に利用できるようになるため)、メモリ使用量と、 パフォーマンスおよび便利さのトレードオフが発生します。 事前ロードされたスクリプトをクリアするには PHP プロセスの再起動が必要です。 つまり、この機能は本番環境でのみ役に立ちます。開発環境では役に立ちません。

パフォーマンスとメモリ使用量の最適なトレードオフは、アプリケーションによって異なることに注意して下さい。 "全てをあらかじめ読み込む" ことはもっとも簡単な戦略かもしれませんが、 必ずしも最適とは限りません。 さらに、コードの事前ロードは、リクエストが終了しても継続して生き残るプロセスの場合にだけ役に立ちます。 つまり、opcache が有効になった CLI スクリプトは動作はしますが、 一般的にコードの事前ロードは役に立ちません。 但し、FFI 経由でコードの事前ロードを使う場合は例外です。

注意:

コードの事前ロードは、Windows ではサポートされていません。

コードの事前ロードを設定するには、2つのステップが必要です。 まず、opcache を有効にしなければなりません。 その上で、opcache.preload の値を php.ini に設定します。

opcache.preload=preload.php

preload.php は、サーバーの起動時(PHP-FPM, mod_php, など) に一度だけ実行され、リクエストを越えて生き残るメモリ領域に読み込まれる任意のファイルです。 特権のないシステムユーザに切り替える前に root で起動するサーバーの場合や、 PHP が root 権限で実行(推奨されません)される場合のために、 opcache.preload_user を使って事前ロードを実行するためのシステムユーザーを指定することが出来ます。 デフォルトでは、事前ロードを root で行うことは禁止されていますが、 明示的に opcache.preload_user=root と指定した場合は許可されます。

preload.php スクリプトでは、 include, include_once, require, require_once, opcache_compile_file で参照されるあらゆるファイルが評価され、 リクエストを越えて生き残るメモリ領域に読み込まれます。 次の例では、src ディレクトリにある全ての .php ファイルが事前ロードされます。 但し、Test ファイルの場合を除きます。

<?php
$directory = new RecursiveDirectoryIterator(__DIR__ . '/src');
$fullTree = new RecursiveIteratorIterator($directory);
$phpFiles = new RegexIterator($fullTree, '/.+((?<!Test)+\.php$)/i', RecursiveRegexIterator::GET_MATCH);

foreach ($phpFiles as $key => $file) {
    require_once $file[0];
}
?>

includeopcache_compile_file 両方が動作しますが、コードをどう扱うかが異なります。

  • include は、ファイル内でコードを実行しますが、 opcache_compile_file は実行しません。 これは、前者のみが条件付きの宣言 (if ブロック内の関数宣言)をサポートしているということです。
  • include はコードを実行するので、 ネストして include されたファイルも評価され、 それに含まれる宣言も事前ロードされます。
  • opcache_compile_file は任意の順番でファイルを読み込むことが出来ます。 つまり、a.php が クラス A を定義しており、 b.phpA を継承したクラス B を定義している場合、 opcache_compile_file はそれらふたつのファイルを任意の順番で読み込めます。 しかし、include を使う場合、 a.php必ず 最初にインクルードしなければなりません。
  • どちらの場合も、後に読み込まれるスクリプトが、 既に事前ロードされているファイルをインクルードしていれば、 スクリプトの内容は実行されます。しかし、そこで定義されているシンボルは再定義されません。 include_once を使っても、 ファイルが二度インクルードされることを妨げません。 ファイルで定義されたグローバルな定数をインクルードするために、 ファイルを再読み込みする必要があるかもしれません。 なぜなら、定数は事前ロードでは処理されないからです。
どちらのアプローチが優れているかは、あなたがどのような挙動を望むか次第です。 オートローディング と一緒に使うことで、 opcache_compile_file は大きな柔軟性を得られます。 一方で、手動でコードをロードする場合、 include を使うほうが堅牢になるでしょう。