PHP: require(include)するディレクトリを検索する順序について

アキラ
PHPのrequire/includeって結構いろんなところ探しに行くよね

ワタル
うん。でもって何かよくわからないけど見つからないとか

アキラ
気が付かずに同名のファイルをrequireしてトラブってなかなか解決できなかったり

ワタル
そりゃ同じ名前のファイル作るなって話だけどPHPのマニュアル見ても少しわかりにくい所あるからまとめようか

結論

以下の順番で検索する

  1. 絶対パスが書いてある時は、そのファイル
  2. include_pathを前から検索
  3. カレントディレクトリ基準で検索
  4. requireが書いてあるphpファイル基準で検索

注意

  • requireを.または..で開始すると、カレントディレクトリしか検索しない
  • include_pathに.とか..を入れてしまうとカレントディレクトリを移動すると検索場所が変化するので結構危険
  • require先でさらにrequireするときは、あらためて最初からルール適用となる。
ワタル
検索順序なんけどマニュアルに

ファイルのインクルードは、指定されたパスから行います。パスを指定しない場合は、 include_path の設定を利用します。 ファイルが include_path に見つからないときは、include は呼び出し元スクリプトのディレクトリと現在の作業ディレクトリも探します。

ワタル
って書いてあるんだけど、呼び出し元スクリプトのディレクトリと現在の作業ディレクトリのどっちが優先か書いてないのよ。

アキラ
あら

ワタル
で、結論は上に書いたんだけど、検証した様子を載せとくね。

検証

PHP8.1.2を使用

前提

/home/ranzoに次のようにディレクトリとphpファイルを配置する。
以下、treeコマンドの出力

.
├── Main.php
├── Same.php
├── a
│   ├── Same.php
│   ├── Sub.php
│   └── a
│       └── Same.php
├── b
│   ├── Same.php
│   ├── Sub.php
│   └── a
│       └── Same.php
├── c
│   ├── Same.php
│   └── a
│       └── Same.php
└── d

Same.phpの中身は全て同じ(symlinkだと挙動が違うので実ファイル)

<?php
echo __FILE__ . "\n";

ケース1

Main.phpを↓のようにして

<?php
set_include_path('/home/ranzo/b');
require_once '/home/ranzo/c/Same.php';

これを/home/ranzoから実行すると結果は

% cd /home/ranzo
% php ./Main.php
/home/ranzo/c/Same.php

となる。絶対パスなので迷いなくそのファイルを実行

ケース2

Main.phpを↓のようにして

<?php
set_include_path('/home/ranzo/b:/home/ranzo/c');
require_once 'Same.php';

これを/home/ranzoから実行すると結果は

% cd /home/ranzo
% php ./Main.php
/home/ranzo/b/Same.php

となるので、include_pathを最優先で前から検索していることがわかる。
なお、b/Same.phpを削除すると結果は

/home/ranzo/c/Same.php

となるので、まずは他を探す前にinclude_pathを前から順に探している。

ケース2のおまけ

include_pathに.が含まれていた場合、どこ基準の.なのかを検証する。
Main.phpを↓のようにして

<?php
set_include_path('.:/home/ranzo/b');
require_once 'Same.php';

実行すると

% cd /home/ranzo/a # 一段深いa
% php ../Main.php # 親dirのscriptを実行
/home/ranzo/a/Same.php

となるので、include内の.はscriptファイルのある場所ではなく、phpコマンドを実行したときにいるカレントディレクトリであることがわかる。

ワタル
これ、chdirとかでカレントディレクトリを変えちゃうとそれ以降requireしたときに検索対象まで変化するので注意

アキラ
set_include_pathした時点じゃなくて、requireした時点でのカレントディレクトリをさがすのか

ケース3

Main.phpを↓のようにして

<?php
require_once 'Same.php';

実行すると

% cd /home/ranzo/a # 一段深いa
% php ../Main.php # 親dirのscriptを実行
/home/ranzo/a/Same.php

となるので、scriptファイルのある場所よりも、カレントディレクトリ優先がわかる。

ケース4

Main.phpを↓のようにして

<?php
require_once 'Same.php';

phpファイルのないディレクトリから実行すると

% cd /home/ranzo/d # 空っぽのフォルダ
% php ../Main.php # 親dirのscriptを実行
/home/ranzo/Same.php

ようやく、scriptファイルのあるディレクトリを探しに行く。
ここまでで
絶対パス指定 > include_path > カレントディレクトリ > scriptパスの順とわかった。

ケース5

次はrequireした先で、サブフォルダをrequireしてみる
Main.phpを↓のようにして

<?php
// あえてcを前に書いてある
set_include_path('/home/ranzo/c:/home/ranzo/b');
require_once 'Sub.php';

Sub.phpをこのようにして、aとbディレクトリに置く。cには置かない。

<?php
echo __FILE__ . " from Sub.php\n";
require 'a/Same.php';

aから実行すると

% cd /home/ranzo/a # 一段深いa
% php ../Main.php # 親dirのscriptを実行
/home/ranzo/b/Sub.php from Sub.php
/home/ranzo/c/a/Same.php
アキラ
げ!c/a が呼ばれてんじゃん。b/a じゃないの?

ワタル
requireでどこのファイルがよばれてるかにかかわらず、その中にrequireがあったら最初からルール適用だからね。ここは注意しないとハマるかも。

ケース5の派生

ケース5とMain.phpは変えずに、Sub.phpのrequireに./を追加すると

<?php
echo __FILE__ . " from Sub.php\n";
require './a/Same.php';  # ./を追加

なんと結果は

% cd /home/ranzo/a # 一段深いa
% php ../Main.php # 親dirのscriptを実行
/home/ranzo/b/Sub.php from Sub.php
/home/ranzo/a/a/Same.php

Same.phpはa/aのものが呼ばれている。
./をつけてしまうと問答無用でカレントディレクトリ基準で呼ばれてしまうようだ。

ケース6

Main.phpを↓のようにして

<?php
require_once './Same.php';

phpファイルのないディレクトリから実行すると

% cd /home/ranzo/d # 空っぽのフォルダ
% php ../Main.php # 親dirのscriptを実行
PHP Fatal error:  Uncaught Error: Failed opening required './Same.php' (include_path='/home/ranzo/c:/home/ranzo/b') in /home/ranzo/Main.php:4

エラーになる。
この場合、カレントディレクトリしか探していないことがわかる。

PHPマニュアルには

パスを指定した場合 — 絶対パス (Windows ならドライブレターあるいは \ で始まるパス、Unix/Linux 系なら / で始まるパス) あるいはカレントディレクトリからの相対パス (. あるいは .. で始まるパス) のどちらでも — は include_path は無視されます。

とありますが、include_pathだけでなくscriptフォルダも無視されています。