PHP の include_once は何をもって同じファイルと判断するのか
PHP の include_once と require_once は、実行プロセス内で同じファイルの 2 回目以降の取り込みをスキップするための仕組みですが、何をもって「同じファイル」と判定するのかふと気になりました。この点はドキュメントにははっきりと記載はありません。
結論から言うと、ローカルファイルでは、基本的に正規化された絶対パスが判定基準になっています。
判定に使われるのはパス文字列
PHP の実装では、読み込んだファイルの opened_path を included_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 の文字列単位で判定されます。



