タイトルは不正確です。詳細は[コメント欄]へ
概要
パスは(相対的)絶対パスで表記しましょう
(e.g. __DIR__ . '../hoge.php'
)
という前提のもと、パスを指定しないrequire
で、
エラーと理解できない動作が起きている状態です。
理解できない動作は両バージョンで起き、
エラーに関しては5.4のみで起きています。
この記事はすべて推測です。
相対パスでハマる、ということはネットで良く出るのですが、イマイチ同一事象かの判別もつきません。
※マニュアル時点で作業ディレクトリとカレントディレクトリ両方が(別ページですが)使われていますので、参照元を真似する感じで両方の表記を使用しています。
何が起きるのか
エラー
require
の部分的失敗、include_path
の探索中に
PHP Warning: require(hoge.php): failed to open stream: No such file or directory
PHP Notice: blenc_compile: unable to open stream, compiling with default compiler.
が出る(読み込みは成功する)
理解できない動作
require
されたファイルからのrequire
時、大本の読み込みパスからは読み込めない相対パスによる指定が読み込めている。
検証環境
- CentOS5 PHP 5.3
- CentOS7 PHP 5.4
require
のおさらい
require(include)
がどのような動きをするかマニュアルから確認します。
[PHP: include - Manual]
以下の記述内容は require にも当てはまります。
ファイルのインクルードは、指定されたパスから行います。パスを指定しない場合は、 include_path の設定を利用します。 ファイルが include_path に見つからないときは、include は呼び出し元スクリプトのディレクトリと現在の作業ディレクトリも探します。
ここの
呼び出し元スクリプトのディレクトリ
が、最初のrequire
か直近のrequire
かは難しいです…
流れ的には直近だと思いますが。
相対パスでハマるのは作業ディレクトリが最初のファイルのディレクトリになるからですね。
また、パス無指定の読み込みは
include_path
- 読み出し元スクリプトのディレクトリ
- 作業ディレクトリ
の三段階であることは初耳でした。
なお、三段階と書きましたが、読み出し元スクリプトのディレクトリと作業ディレクトリ、どちらが先に探索されるかはここでは明言されていないと受け取ります。
(素直に読むと読み出し元スクリプトのディレクトリからですが…)
パスを指定した場合 — 絶対パス (Windows ならドライブレターあるいは \ で始まるパス、Unix/Linux 系なら / で始まるパス) あるいはカレントディレクトリからの相対パス (. あるいは .. で始まるパス) のどちらでも — は **include_path は無視されます。**たとえば ../ ではじまるファイル名を指定した場合は、 親ディレクトリからそのファイルを探します。
include_path
は無視されても
- 読み出し元スクリプトのディレクトリ
- 作業ディレクトリ
は見るのでしょうか。
[PHP: コア php.ini ディレクティブに関する説明 - Manual]
PHP は、インクルードするファイルを探す際に インクルードパスの各エントリを個別に調べます。 まず最初のパスを調べ、見つからなければ次のパスを調べ、…… というように、ファイルが見つかるか warning あるいは error が発生するまで続けます。インクルードパスを実行時に変更したり設定したりするには set_include_path() を使用します。
現在の事象はwarning
が出ても継続している様に見えるんですよね。
require
には数段階あり、warning
が出れば、そのある段階は終了して次の段階の探査、ということでしょうか。
それともエラー箇所がinclude_path
ではないのでwarning
で止まらないのか
インクルードパスに . (カレントディレクトリ) を指定すると、相対パス指定によるインクルードができるようになります。
この場合の相対パスとはカレントディレクトリからか、呼び出し元のパスからかは不明です。
しかし、インクルードのたびに毎回 PHP にカレントディレクトリをチェックさせるよりは、 明示的に include './file' を指定したほうが効率的です。
あれ、やっぱりカレントディレクトリからなのかな?
それはそうか。
コード
tree
.
|-- dir
| |-- a.php
| `-- b.php
`-- root.php
root.php
<?php
error_reporting(-1);
echo "START\n";
echo getcwd() . "\n";
echo get_include_path() . "\n";
require '/tmp/dir/a.php';
echo "END\n";
<?php
echo "a\n";
echo getcwd() . "\n";
echo get_include_path() . "\n";
require ('b.php');
<?php
echo "b\n";
echo getcwd( ) . "\n";
echo get_include_path() . "\n";
PHP5.3出力
START
/tmp
.:/usr/share/pear:/usr/share/php
a
/tmp
.:/usr/share/pear:/usr/share/php
b
/tmp
.:/usr/share/pear:/usr/share/php
END
PHP5.4出力
START
/tmp
.:/usr/share/pear:/usr/share/php
a
/tmp
.:/usr/share/pear:/usr/share/php
PHP Warning: require(b.php): failed to open stream: No such file or directory in /tmp/dir/a.php on line 8
PHP Notice: blenc_compile: unable to open stream, compiling with default compiler. in /tmp/dir/a.php on line 8
b
/tmp
.:/usr/share/pear:/usr/share/php
END
a.php
の読み込みファイルをc.php
(存在しないファイル)にした場合
PHP5.3出力
START
/tmp
.:/usr/share/pear:/usr/share/php
a
/tmp
.:/usr/share/pear:/usr/share/php
PHP Warning: require(c.php): failed to open stream: No such file or directory in /tmp/dir/a.php on line 8
PHP Fatal error: require(): Failed opening required 'c.php' (include_path='.:/usr/share/pear:/usr/share/php') in /tmp/dir/a.php on line 8
PHP5.4出力
START
/tmp
.:/usr/share/pear:/usr/share/php
a
/tmp
.:/usr/share/pear:/usr/share/php
PHP Warning: require(c.php): failed to open stream: No such file or directory in /tmp/dir/a.php on line 8
PHP Warning: require(c.php): failed to open stream: No such file or directory in /tmp/dir/a.php on line 8
PHP Notice: blenc_compile: unable to open stream, compiling with default compiler. in /tmp/dir/a.php on line 8
PHP Warning: require(c.php): failed to open stream: No such file or directory in /tmp/dir/a.php on line 8
PHP Fatal error: require(): Failed opening required 'c.php' (include_path='.:/usr/share/pear:/usr/share/php') in /tmp/dir/a.php on line 8
b.php
がなぜ読み込めるか
a.php
で書かれたrequire ('b.php');
は、パスが指定されていないことからinclude_path
を探索します。
include_path
は.:/usr/share/pear:/usr/share/php
左から順に評価するので、
-
.
=/tmp/
(root.php
がカレントになるので) にはb.php
は無い -
/usr/share/pear
,/usr/share/php
にも無い -
include_path
には無いので、呼び出し元スクリプトのディレクトリと現在の作業ディレクトリからも探索します。 - 呼び出し元スクリプトが
a.php
と仮定すると、ディレクトリは/tmp/dir/
なので、b.php
が存在します - 作業ディレクトリは
/tmp/
なので無い
となり、b.php
が読み込めると推測します。
require
の動的な読み込み方法が、相対パス指定とinclude_path
しかないと思っていると、この動作は不思議に思えました。
特に
「.
はroot.php
のカレントディレクトリになるんだろ。俺知ってるんだぜ」
状態だと謎が深まります…
なぜエラーが出るのか
5.4でエラーが出るようになった。
これは本当にわかりません…
failed to open streamと出ているにもかかわらず読み込めていますしね。
include_path
- 読み出し元スクリプトのディレクトリ
- 作業ディレクトリ
のどれかが失敗するとこのエラーが出るのでは?
と推測して、ひとまずすべてが失敗するrequire(c.php)
にしてみてところ、更に謎が増え…
最後の
PHP Warning: require(c.php): failed to open stream: No such file or directory in /tmp/dir/a.php on line 8
PHP Fatal error: require(): Failed opening required 'c.php' (include_path='.:/usr/share/pear:/usr/share/php') in /tmp/dir/a.php on line 8
は、探索パスがすべて失敗したことのWarning
と、require
が失敗したことのFatal
と捉えています。5.3でも出ているので。
すると、b.php
の時との差分は
PHP Warning: require(c.php): failed to open stream: No such file or directory in /tmp/dir/a.php on line 8
一個分になるかと思います。
(増えたのは出力最初のWarning
と考えていますが、メッセージが同一のため、もしかしたらrequire
失敗時のWarning
とFatal
のセットは最初と最後のエラーの組み合わせもありうるわけですが…)
探索先は3種類、すべて探索して失敗した場合のエラーは2種類。
切り分け1 include_path
から.
を除去
a.php
からの読み込み時にinclude_path
から.:
を取り除きます。
元々.
は/tmp/
を指しているので、影響は無いと考えます
<?php
echo "a\n";
echo getcwd() . "\n";
set_include_path('/usr/share/pear:/usr/share/php');
echo get_include_path() . "\n";
require ('a.php');
START
/tmp
.:/usr/share/pear:/usr/share/php
a
/tmp
/usr/share/pear:/usr/share/php
PHP Warning: require(b.php): failed to open stream: No such file or directory in /tmp/dir/a.php on line 9
PHP Notice: blenc_compile: unable to open stream, compiling with default compiler. in /tmp/dir/a.php on line 9
b
/tmp
/usr/share/pear:/usr/share/php
END
変わりません。
切り分け2 カレントディレクトリを/tmp/dir
にする
include_path
探索後の探索が
- 作業ディレクトリ
- 読み出し元スクリプトのディレクトリ
の場合、作業ディレクトリの探索でエラーになり、読み出し元スクリプトのディレクトリの探索で成功、となっている可能性があります。
なお、include_path
の'.'があるとinclude_path
時点で成功するため切り分け1のまま取り除いておきます
<?php
echo "a\n";
chdir('/tmp/dir');
echo getcwd() . "\n";
set_include_path('/usr/share/pear:/usr/share/php');
echo get_include_path() . "\n";
require ('b.php');
START
/tmp
.:/usr/share/pear:/usr/share/php
a
/tmp/dir
/usr/share/pear:/usr/share/php
b
/tmp/dir
/usr/share/pear:/usr/share/php
END
エラーが出なくなりました!
PHP Warning: require(hoge.php): failed to open stream: No such file or directory
PHP Notice: blenc_compile: unable to open stream, compiling with default compiler.
は、include_path
探索後、カレントディレクトリの探索を行い、失敗時に出力し、読み出し元スクリプトのディレクトリからの探索へ続く、と見ていいかもしれません
切り分け3 本当にカレントディレクトリの探索が先なのか
現在の構成ではb.php
がa.php
と同じ場所にあり、カレントディレクトリか読み出し元スクリプトのディレクトリかの識別がつきにくいため、
最後に本当にカレントディレクトリからの探索が先かを調査します。
別ディレクトリに別のb.php
を作成し、カレントディレクトリをそこにすることで、
a.php
から呼び出されるb.php
はカレントか呼び出し元かを分けます
<?php
echo "a\n";
chdir('/tmp/another');
echo getcwd() . "\n";
set_include_path('/usr/share/pear:/usr/share/php');
echo get_include_path() . "\n";
require ('b.php');
```php:もう一つのb.php
(`/tmp/another/b.php`)