7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PHP5.3と5.4のパスを指定しないrequire(include)の違い

Last updated at Posted at 2016-03-28

タイトルは不正確です。詳細は[コメント欄]へ

概要

パスは(相対的)絶対パスで表記しましょう
(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かは難しいです…
流れ的には直近だと思いますが。
相対パスでハマるのは作業ディレクトリが最初のファイルのディレクトリになるからですね。

また、パス無指定の読み込みは

  1. include_path
  2. 読み出し元スクリプトのディレクトリ
  3. 作業ディレクトリ

の三段階であることは初耳でした。
なお、三段階と書きましたが、読み出し元スクリプトのディレクトリと作業ディレクトリ、どちらが先に探索されるかはここでは明言されていないと受け取ります。
(素直に読むと読み出し元スクリプトのディレクトリからですが…)


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

include_pathは無視されても

  1. 読み出し元スクリプトのディレクトリ
  2. 作業ディレクトリ

は見るのでしょうか。


[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

/tmp/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";

a.php
<?php

echo "a\n";

echo getcwd() . "\n";
echo get_include_path() . "\n";

require ('b.php');

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
左から順に評価するので、

  1. . = /tmp/(root.phpがカレントになるので) にはb.phpは無い
  2. /usr/share/pear,/usr/share/phpにも無い
  3. include_pathには無いので、呼び出し元スクリプトのディレクトリと現在の作業ディレクトリからも探索します。
  4. 呼び出し元スクリプトがa.phpと仮定すると、ディレクトリは/tmp/dir/なので、b.phpが存在します
  5. 作業ディレクトリは/tmp/なので無い

となり、b.phpが読み込めると推測します。

requireの動的な読み込み方法が、相対パス指定とinclude_pathしかないと思っていると、この動作は不思議に思えました。
特に
.root.phpのカレントディレクトリになるんだろ。俺知ってるんだぜ」
状態だと謎が深まります…

なぜエラーが出るのか

5.4でエラーが出るようになった。
これは本当にわかりません…
failed to open streamと出ているにもかかわらず読み込めていますしね。

  1. include_path
  2. 読み出し元スクリプトのディレクトリ
  3. 作業ディレクトリ

のどれかが失敗するとこのエラーが出るのでは?
と推測して、ひとまずすべてが失敗する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失敗時のWarningFatalのセットは最初と最後のエラーの組み合わせもありうるわけですが…)

探索先は3種類、すべて探索して失敗した場合のエラーは2種類。

切り分け1 include_pathから.を除去

a.phpからの読み込み時にinclude_pathから.:を取り除きます。
元々./tmp/を指しているので、影響は無いと考えます

a.php
<?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探索後の探索が

  1. 作業ディレクトリ
  2. 読み出し元スクリプトのディレクトリ

の場合、作業ディレクトリの探索でエラーになり、読み出し元スクリプトのディレクトリの探索で成功、となっている可能性があります。
なお、include_pathの'.'があるとinclude_path時点で成功するため切り分け1のまま取り除いておきます

a.php
<?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.phpa.phpと同じ場所にあり、カレントディレクトリか読み出し元スクリプトのディレクトリかの識別がつきにくいため、
最後に本当にカレントディレクトリからの探索が先かを調査します。
別ディレクトリに別のb.phpを作成し、カレントディレクトリをそこにすることで、
a.phpから呼び出されるb.phpはカレントか呼び出し元かを分けます

a.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`)

7
4
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?