名古屋出身ソフトウェアエンジニアのブログ

PHP の include_once は何をもって同じファイルと判断するのか

公開:
更新:

PHP の include_oncerequire_once は、実行プロセス内で同じファイルの 2 回目以降の取り込みをスキップするための仕組みですが、何をもって「同じファイル」と判定するのかふと気になりました。この点はドキュメントにははっきりと記載はありません。

結論から言うと、ローカルファイルでは、基本的に正規化された絶対パスが判定基準になっています。

判定に使われるのはパス文字列

PHP の実装では、読み込んだファイルの opened_pathincluded_files ハッシュに登録します。include_once されたときは、解決したパスがすでに登録されているかを調べ、登録済みならファイルを再評価しません1

つまり、ファイルの内容や inode 等の情報を比較しているわけではありません。ローカルファイルの場合はパス解決時に realpath 相当の処理が行われるため、相対パス、., .., シンボリックリンクなどが正規化されます2

シンボリックリンクや相対パスは同一扱い

たとえば、/app/current/app/src を指すシンボリックリンクであるとします。

include_once "/app/src/config.php";
include_once "/app/current/config.php"; // current -> src の symlink

どちらも最終的に /app/src/config.php へ解決されるため、通常は 1 行目の方だけが実行されます。 2 回目の include_once はファイルを再評価せず、true を返します3

次の例も、解決後のパスが同じになるため同一扱いです。

include_once "/app/src/config.php";
include_once "/app/src/./config.php";
include_once "/app/tmp/../src/config.php";

ハードリンクは別扱い

一方、同じ inode を指す異なるハードリンクはパスが異なるので別扱いです。

/app/a.php  ─┐
             ├─ 同じ inode
/app/b.php  ─┘
include_once "/app/a.php";
include_once "/app/b.php"; // 再度実行され得る

ハードリンクは realpath() で解決しても別々のパス名のままです。そのため、included_files には別のキーとして登録され、両方のファイルが実行され得ます。

実際に登録されたパスを確認する

どのパスが登録されたかは、get_included_files() で確認できます。

var_dump(get_included_files());

パスの指定方法による挙動が気になったときは、推測するよりもこの関数で確認するのが手っ取り早いです。

なお、phar:// やその他のストリームラッパーでは、ローカルファイルと同じ realpath 処理が使われるとは限りません。一般のストリームラッパーでは URL の意味論はラッパーの実装に依存するため、URL の文字列単位で判定されます。