include/requireを使った時のパスの扱い - PHP

投稿日:

PHP で include や require を使う時、パスの扱い方が良く分からなくなること無いですか?

一応マニュアルに書かれている事が全てなんですが、毎回分からなくなるので備忘録も兼ねて一回まとめてみたいと思います。

まず以下のような構成の場合。

├─test.php
│
└─class
        └class.php

test.php

include './class/class.php';
echo $class;

class/calss.php

$class='class';

test.phpにアクセスすると、別にエラーになることも無く、「class」と出力されます。


では次、以下のような構成の場合。

├─test.php
│
└─class
    │  class.php
    │
    └─lib
            lib.php

test.php

include './class/class.php';
echo $lib;

class/class.php

include './lib/lib.php';

class/lib/lib.php

$lib = 'lib';

で、test.php にアクセスすると以下のような感じでエラーになります。

Warning: include(./lib/lib.php) [function.include]: failed to open stream: No such file or directory in /path/to/class/class.php on line 2

Warning: include() [function.include]: Failed opening './lib/lib.php' for inclusion (include_path='.:/php/include/paths') in /path/to/class/class.php on line 2

class.php から class/lib/lib.php を参照しようとした際にエラーになっています。

が、class.php の include 部分を以下のような感じで記述するとエラーは発生せずに実行され「lib」と出力されます。

include 'lib/lib.php';

'./lib/lib.php' と 'lib/lib.php' の違いでエラーが起きたり起きなかったりする訳です。

この理由は、マニュアルの以下の部分にハッキリと書いてあります

ファイルのインクルードは、指定されたパスから行います。パスを指定しない場合は、 include_path の設定を利用します。 ファイルが include_path に見つからないときは、include() は呼び出し元スクリプトのディレクトリと現在の作業ディレクトリも探します。 include() は、ファイルを見つけられない場合に warning を発行します。一方 require() の場合は、同じ場合に fatal error を発行する点が異なります。

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

'./lib/lib.php' と書いた場合は呼び出し元のスクリプト(サンプルの場合は test.php)からの相対パスとなってしまい、test.php と同じ階層に lib フォルダが存在しないためエラーになります。

一方 'lib/lib.php' と書いた場合には、パスが指定されていない状態とみなされ「作業ディレクトリ(呼び出されたファイルと同じディレクトリ)」も探してくれるため、class.php からみた lib/lib.php が見つかるために正常に実行されるということです。

そのため、このサンプルで言えば、class/class.php で './class/lib/lib.php' などと指定すれば実行されます。


ここまでなら何となく「パスを指定せずに記述すれば良い」と思ってしまいますが、実際に作っていると上記みたいな単純な場合だけではないと思います。

例えば、以下のような構成の場合。

├─test.php
│
├─class
│  │  class.php
│  │
│  └─lib
│          lib.php
│
├─inc
│    └inc.php
│
└─sub
      └sub.php

test.php

include 'inc/inc.php';
echo $lib;

sub/sub.php

include '../inc/inc.php';
echo $lib;

inc/inc.php

include 'class/class.php';

class/class.php

include 'lib/lib.php';

class/lib/lib.php

$lib = 'lib';

この場合、test.php は問題なく lib と出力されますが、sub.php はエラーになります。

Warning: include(class/class.php) [function.include]: failed to open stream: No such file or directory in /path/to/inc/inc.php on line 2

Warning: include() [function.include]: Failed opening 'class/class.php' for inclusion (include_path='.:/php/include/paths') in /path/to/inc/inc.php on line 2

class/class.php が見つからないために発生しているエラーです。

inc/inc.php は class/class.php を参照していますが、ここの部分の記述に問題があると言うか、難しいんですよね。

まず、inc/inc.php と class/class.php は別なディレクトリに存在するので、「パスを指定せずに参照する」事は無理です。
サンプルで書いている class/class.php と言うパスは test.php からのパスなので、test.php ではエラーになりませんが、sub/sub.php からだと class/class.php では参照できないためエラーになってしまいます。

書き方としては、inc/inc.php からみた場合、class/class.php は ../class/class.php になりますが、inc/inc.php で

include '../class/class.php';

とした場合、たまたま sub/sub.php はエラーにならないのですが、今度は test.php がエラーになってしまいます。

では、どうするか?

代表的な方法は以下の2つじゃないかと思います。

  • $_SERVER['DOCUMENT_ROOT'] を使う
  • dirname(__FILE__) を使う

ここからは人によって考え方が変わってくると思いますが、私が好きな書き方は $_SERVER['DOCUMENT_ROOT'] を使う方法です。

$_SERVER['DOCUMENT_ROOT'] はドキュメントルートのパスを返してくれるので、インクルードしたいファイルのドキュメントルートからのパスを付けて記述すればOKです。

この場合、test.php がドキュメントルートに存在するならば inc/inc.php は以下のような書き方になります。

include $_SERVER['DOCUMENT_ROOT'] . '/class/class.php';

インクルード先のファイルのドキュメントルートからの絶対パスになるので、実行されるファイルとか作業ディレクトリとかを考慮する必要が無いのが最大のメリットです。

例えば上記のサンプルで inc/inc.php を読み込む場合、test.php と sub/sub.php では記述が変わりますが、$_SERVER['DOCUMENT_ROOT'] 以下のように統一した書き方に出来ます。

include $_SERVER['DOCUMENT_ROOT'] . '/inc/inc.php';

なら、全てこの書き方で書けばいいじゃないかと思うかも知れませんが、そう単純には行かないのも難しい所で、$_SERVER['DOCUMENT_ROOT'] はシェルから実行された場合には使えません。
そのため、プロジェクトの中で cron などシェルから実行されたり参照される可能性があるような部分で使うと、エラーになってしまいます。

で、こういう場合は dirname(__FILE__) を使うことになります。


dirname(__FILE__) は 2つの要素からできているのですが、dirname は指定されたファイルの親のディレクトリを返す関数で、__FILE__ はファイルのフルパスを返し、インクルードされるファイルの中に記述した場合にはインクルードされるファイルのパスを返します。

つまり、dirname(__FILE__) はインクルードされるファイルが存在するディレクトリを返します。

これを使った場合、inc/inc.php は以下のように記述できます。

include dirname(__FILE__) . '/../class/class.php';

この場合も、test.php と sub/sub.php の両方共正常に実行できます。

しかも、dirname(__FILE__) はシェルから実行されるものでも使えるので、そういう意味では、dirname(__FILE__) を使う記述が安全で一番お勧め書き方かも知れません。

が、私はあまり好きじゃないんですよねぇ・・・

何故か?
呼び出し元のスクリプトによって同じファイルをインクルードする場合でも書き方を変えなくてはならないからです。

例えば、先ほどのサンプルの test.php と sub/sub.php から inc/inc.php を呼び出す場合、$_SERVER['DOCUMENT_ROOT'] を使う場合には同じ書き方で問題ないと書きましたが、dirname(__FILE__) の場合には変わります。(当たり前ですが)

test.php

include dirname(__FILE__) . '/inc/inc.php';

sub/sub.php

include dirname(__FILE__) . '/../inc/inc.php';

まぁ、エラーになる訳ではないので、別に良いのですが多数のファイルから参照する場合などはやはり使いにくい感じがします。

特に WEB でのみ参照されるような場合は $_SERVER['DOCUMENT_ROOT'] の方が使いやすいような気がしますが、まぁケースバイケースというやつですね。


私の場合は以下のように使い分けていますが、なかなか「コレだ!」っという感じのスッキリした使い分けが出来ていません。

  • include するファイルが同じディレクトリかサブディレクトリにある場合
    パスを指定しない方法(inc/inc.php など)
  • 上位ディレクトリにあるような場合で呼び出し元のファイルが多くない場合
    dirname(__FILE__)
  • 呼び出し元のファイルが複数かつ多階層に散っている場合で WEBでのみ参照されるような場合
    $_SERVER['DOCUMENT_ROOT']

そして、include や require のパスの扱いを毎回忘れて??となると言う感じ・・・


ちなみに上の文章では include で書いてきましたが、私は require 派です。

require派って事も無いですが、基本読み込めなかったら fatal error で落ちたほうが良いと思ってます。

更新日: