Windows 10 + XAMPPという環境において、PHPでサイトを作っておりました。
パスの扱いについて困ったことがあったので、これを機会に__DIR__
やら$_SERVER['DOCUMENT_ROOT']
やらの中身がどうなるかについて調べてみました。
内部リンクを絶対パスを使って指定できない?
まだまだ初心者でサイト構成の基本も知らないこともあり、フォルダやファイルの配置がコロコロ変わってしまう可能性があることから、相対パスは使いたくありませんでした。
絶対パスなら、少なくともリンク元となるファイルの配置には左右されなくなるため、内部リンクを絶対パスを使って指定しようとしました。
A.phpから同フォルダ内にあるB.phpにリンクするため、次のようなコードを書きました。
絶対パスを調べるために、__DIR__
で実行ファイルの場所を取得しました。
<?php
include(__DIR__.'/B.php');
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<?php echo '<a href=B.php>B</a><br />' ?>
<?php echo '<a href='.__DIR__.'/B.php>B</a><br />' ?>
</body>
</html>
<?php
echo 'This is B.';
echo nl2br("\n");
ブラウザ(Google Chrome)でA.phpを開いたところ、次のように表示されました。
This is B.
B
B
1行目から、絶対パスを使ったincludeに成功していることが分かります。
2行目と3行目は青く表示されています。B.phpにリンクできていれば成功です。
2行目は、ちゃんとBにリンクされ、
This is B.
と表示されました。
ところが、3行目は、2行目と同じようにリンクっぽく青くなっているのにも関わらず、クリックしても反応がありません。
ページのソースを表示すると、
This is B.<br />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<a href=C:\xampp\htdocs\index\test/B.php>B</a><br /><a href=B.php>B</a><br />
</body>
</html>
となっています。
困りました。どうしましょう?
パス取得の実験
PHPではパスを取得するためにいくつかの方法があります。
その中でも、
__FILE__
dirname(__FILE__)
__DIR__
$_SERVER['DOCUMENT_ROOT']
$_SERVER['HTTP_HOST']
$_SERVER['SCRIPT_NAME']
の6つについて調べてみることにしました。
実験用に、A、B、C、Dという4つのPHPファイルを用意しました。
フォルダ構成は次の通りです。
C:/xampp/htdocs/index/test/
├ A.php
├ B.php
├ test2/C.php
└ D.php
htdocs直下にはインストール時から色々入っているため、自分で作ったフォルダやファイルは置きたくありませんでした。
そこで、indexというフォルダを作り、その中に自分で作ったフォルダやファイルを置いてあります。
今回実験に使うのは、「test」という名前のフォルダです。
Aを起点に、includeでBを呼び出し、BがCを呼び出し、CがDを呼び出す、といった動きをさせます。
コードは以下の通りです。
<?php
echo 'This is A.';
echo nl2br("\n");
echo __FILE__;
echo nl2br("\n");
echo dirname(__FILE__);
echo nl2br("\n");
echo __DIR__;
echo nl2br("\n");
echo $_SERVER['DOCUMENT_ROOT'];
echo nl2br("\n");
echo $_SERVER['HTTP_HOST'];
echo nl2br("\n");
echo $_SERVER['SCRIPT_NAME'];
echo nl2br("\n");
echo nl2br("\n");
include('B.php');
include(__DIR__.'/B.php');
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<?php echo '<a href=B.php>B</a><br />' ?>
<?php echo '<a href='.__DIR__.'/B.php>B</a><br />' ?>
<?php echo '<a href='.$_SERVER['DOCUMENT_ROOT'].'/index/test/B.php>B</a><br />' ?>
<?php echo '<a href='.$_SERVER['HTTP_HOST'].'/index/test/B.php>B</a><br />' ?>
<?php echo '<a href='.dirname($_SERVER['SCRIPT_NAME']).'/B.php>B</a><br />' ?>
</body>
</html>
<?php
echo 'This is B.';
echo nl2br("\n");
echo __FILE__;
echo nl2br("\n");
echo dirname(__FILE__);
echo nl2br("\n");
echo __DIR__;
echo nl2br("\n");
echo $_SERVER['DOCUMENT_ROOT'];
echo nl2br("\n");
echo $_SERVER['HTTP_HOST'];
echo nl2br("\n");
echo $_SERVER['SCRIPT_NAME'];
echo nl2br("\n");
echo nl2br("\n");
include('test2/C.php');
<?php
echo 'This is C.';
echo nl2br("\n");
echo __FILE__;
echo nl2br("\n");
echo dirname(__FILE__);
echo nl2br("\n");
echo __DIR__;
echo nl2br("\n");
echo $_SERVER['DOCUMENT_ROOT'];
echo nl2br("\n");
echo $_SERVER['HTTP_HOST'];
echo nl2br("\n");
echo $_SERVER['SCRIPT_NAME'];
echo nl2br("\n");
echo nl2br("\n");
include('../D.php');
include(__DIR__.'/../D.php');
<?php
echo 'This is D.';
echo nl2br("\n");
echo __FILE__;
echo nl2br("\n");
echo dirname(__FILE__);
echo nl2br("\n");
echo __DIR__;
echo nl2br("\n");
echo $_SERVER['DOCUMENT_ROOT'];
echo nl2br("\n");
echo $_SERVER['HTTP_HOST'];
echo nl2br("\n");
echo $_SERVER['SCRIPT_NAME'];
echo nl2br("\n");
echo nl2br("\n");
ブラウザでA.phpを開いたところ、次のように表示されました。
This is A.
C:\xampp\htdocs\index\test\A.php
C:\xampp\htdocs\index\test
C:\xampp\htdocs\index\test
C:/xampp/htdocs
localhost
/index/test/A.php
This is B.
C:\xampp\htdocs\index\test\B.php
C:\xampp\htdocs\index\test
C:\xampp\htdocs\index\test
C:/xampp/htdocs
localhost
/index/test/A.php
This is C.
C:\xampp\htdocs\index\test\test2\C.php
C:\xampp\htdocs\index\test\test2
C:\xampp\htdocs\index\test\test2
C:/xampp/htdocs
localhost
/index/test/A.php
( ! ) Warning: include(../D.php): failed to open stream: No such file or directory in C:\xampp\htdocs\index\test\test2\C.php on line 25
Call Stack
# Time Memory Function Location
1 0.0003 393472 {main}( ) ...\A.php:0
2 0.0005 396872 include( 'C:\xampp\htdocs\index\test\B.php' ) ...\A.php:25
3 0.0007 400392 include( 'C:\xampp\htdocs\index\test\test2\C.php' ) ...\B.php:25
( ! ) Warning: include(): Failed opening '../D.php' for inclusion (include_path='C:\xampp\php\PEAR') in C:\xampp\htdocs\index\test\test2\C.php on line 25
Call Stack
# Time Memory Function Location
1 0.0003 393472 {main}( ) ...\A.php:0
2 0.0005 396872 include( 'C:\xampp\htdocs\index\test\B.php' ) ...\A.php:25
3 0.0007 400392 include( 'C:\xampp\htdocs\index\test\test2\C.php' ) ...\B.php:25
This is D.
C:\xampp\htdocs\index\test\D.php
C:\xampp\htdocs\index\test
C:\xampp\htdocs\index\test
C:/xampp/htdocs
localhost
/index/test/A.php
This is B.
C:\xampp\htdocs\index\test\B.php
C:\xampp\htdocs\index\test
C:\xampp\htdocs\index\test
C:/xampp/htdocs
localhost
/index/test/A.php
This is C.
C:\xampp\htdocs\index\test\test2\C.php
C:\xampp\htdocs\index\test\test2
C:\xampp\htdocs\index\test\test2
C:/xampp/htdocs
localhost
/index/test/A.php
( ! ) Warning: include(../D.php): failed to open stream: No such file or directory in C:\xampp\htdocs\index\test\test2\C.php on line 25
Call Stack
# Time Memory Function Location
1 0.0003 393472 {main}( ) ...\A.php:0
2 0.0028 397296 include( 'C:\xampp\htdocs\index\test\B.php' ) ...\A.php:26
3 0.0030 400576 include( 'C:\xampp\htdocs\index\test\test2\C.php' ) ...\B.php:25
( ! ) Warning: include(): Failed opening '../D.php' for inclusion (include_path='C:\xampp\php\PEAR') in C:\xampp\htdocs\index\test\test2\C.php on line 25
Call Stack
# Time Memory Function Location
1 0.0003 393472 {main}( ) ...\A.php:0
2 0.0028 397296 include( 'C:\xampp\htdocs\index\test\B.php' ) ...\A.php:26
3 0.0030 400576 include( 'C:\xampp\htdocs\index\test\test2\C.php' ) ...\B.php:25
This is D.
C:\xampp\htdocs\index\test\D.php
C:\xampp\htdocs\index\test
C:\xampp\htdocs\index\test
C:/xampp/htdocs
localhost
/index/test/A.php
B
B
B
B
B
上から順に見ていきましょう。
最初の2~7行目から分かったことは、
-
__FILE__
は、記述されたファイルのコンピューター上での場所を取得する。 -
dirname(__FILE__)
は、__FILE__
で取得したファイルの存在するフォルダの場所を取得する。 -
__DIR__
は、dirname(__FILE__)
と同じ意味。PHP 5.3.0から追加され、これからはこちらを使用した方が良いそうな。 -
$_SERVER['DOCUMENT_ROOT']
は、ルートの場所を取得する。フォルダの区切りがWindows独特の\
ではなく/
なので、問題を起こすことが少なそう。 -
$_SERVER['HTTP_HOST']
は、正直、今の知識ではよく分からない・・・ 推測するに、htdocsの単純なコンピューター的な場所(ルート?)ではなく、htdocsのサーバー的な場所(localhost?)を取得しているのではないか。 -
$_SERVER['SCRIPT_NAME']
は、実行した大本のファイルのルートから先の場所を取得する。C:
が付いていないことから、5. と同じく、実行した大本のファイルのサーバー的な場所を取得しているのではないか。ただ、**「最初に/
が付いている」**ということが、後ほど重要な意味を持つ。
ということです。
何かヒントになりそうですね。
続いて、include('B.php')
によるB.phpの呼び出しとinclude('C.php')
C.phpの呼び出しに成功しています。
しかし、include('../D.php')
によるD.phpの呼び出しには失敗しています。
どうやら、C.phpにあるコード
include('../D.php');
は、実行した大本のファイル(A.php)から見た相対パスをincludeしようとしているみたいです。
試しに、C.phpを直接開いたところ、
This is C.
C:\xampp\htdocs\index\test\test2\C.php
C:\xampp\htdocs\index\test\test2
C:\xampp\htdocs\index\test\test2
C:/xampp/htdocs
localhost
/index/test/test2/C.php
This is D.
C:\xampp\htdocs\index\test\D.php
C:\xampp\htdocs\index\test
C:\xampp\htdocs\index\test
C:/xampp/htdocs
localhost
/index/test/test2/C.php
This is D.
C:\xampp\htdocs\index\test\D.php
C:\xampp\htdocs\index\test
C:\xampp\htdocs\index\test
C:/xampp/htdocs
localhost
/index/test/test2/C.php
と、正しくincludeできています。
続いて、D.phpの呼び出しに成功しています。
__DIR__
を付けたことにより、実行した大本のファイルではなく、C.phpから見た相対パスをincludeすることができたようです。
続いて、include(__DIR__.'/B.php')
によるB.phpの呼び出しに成功し、先ほどと同じ流れになります。
どうやら、今回の配置ではA.phpとB.phpが同じ階層にあるため、include('B.php')
とinclude(__DIR__.'/B.php')
は同じ動きをしたようです。
続いて、Bが4つ並んでいますが、ブラウザ上では全て青いリンクのように見えています。
ページのソースを表示すると、リンク部分は、
<a href=B.php>B</a><br />
<a href=C:\xampp\htdocs\index\test/B.php>B</a><br />
<a href=C:/xampp/htdocs/index/test/B.php>B</a><br />
<a href=localhost/index/test/B.php>B</a><br />
<a href=/index/test/B.php>B</a><br />
となっています。
1つ目は、ちゃんとBにリンクされています。また、カーソルを合わせると左下に「localhost/index/test/B.php」と表示されます。
2つ目と3つ目は、クリックしても反応がありません。気になったのは、先ほどのincludeではコンピューター的な場所が有効だったのに対し、リンクでは無効であるということです。おそらく、includeという処理は同じコンピューター内にあるファイルを呼び出しているのに対し、リンクという処理はサーバーとクライアントの間のインタラクティブなやり取りであるからだと思います。
4つ目は、http://localhost/index/test/localhost/index/test/B.php
に飛んでしまい、「Object not found!」の404エラーが出てしまいます。どうやら、「相対パス」とは、「実行された大本のファイルのパス(今回の場合はhttp://localhost/index/test/)
を自動で付けて、サーバー的な絶対パスにしたもの」のようです。
5つ目は、ちゃんとBにリンクされています。というか、$_SERVER['SCRIPT_NAME']
は実行した大本のファイルのルートから先のサーバー的な場所を取得するから、dirname($_SERVER['SCRIPT_NAME']).'/B.php
はサーバー的な絶対パスと同じものになり、単純な相対パスを使った1つ目の場合と結果的に同じなんだと思います。実はこれは、ルート相対パスというものと同じ書き方になっています。詳しくは後ほど説明します。また、カーソルを合わせるとなぜか右下に「localhost/index/test/B.php」と表示されます。
実験から分かったこと
この実験から分かったことは、大きく分けて3つ。
1. 相対パスは、実行した大本のファイルの場所が基準となる。
2. パスには、「コンピューター的なもの(C:から始まる)」と「サーバー的なもの(http://localhost
から始まる)」があり、前者はincludeなどには使えるがリンク先には使えない。
3. 相対パスとは、「実行された大本のファイルのパス(今回の場合はhttp://localhost/index/test/
)を自動で付けて、サーバー的な絶対パスにしたもの」
内部リンクを絶対パスを使って指定する方法
・・・さて、当初の目的は、「内部リンクを絶対パスを使って指定すること」でした。
ただ、ここまで調べれば、なんとなくできるような気がします。
実験から分かったことの3番目は裏を返せば、**「http://localhost/index/test/
を手動で付ければ、サーバー的な絶対パスにできる」**ということを意味します。
そこで、次のようなコードを書きました。
<?php echo '<a href=http://localhost/index/test/B.php>B</a><br />' ?>
すると、ページのソースのリンク部分は、
<a href=http://localhost/index/test/B.php>B</a><br />
となり、ちゃんとB.phpに飛ぶことができました。
以上、無事に内部リンクを絶対パスを使って指定できたのでした。
(よくよく考えたら、ブラウザのアドレスバーに「http://localhost/index/test/A.php」とか出てるんだから、当然だよなぁ・・・)
ルート相対パスというものがあるらしい
恥ずかしながら(?)、知りませんでした。
上の1つ目、<a href=B.php>B</a><br />
では、パスの最初に/
が付いていません。これは「相対パス」です。
これに対し、5つ目、<a href=/index/test/B.php>B</a><br />
では、パスの最初に/
が付いています。これは「ルート相対パス」というものだそうです。
ルート相対パスは、ルートに対する相対パスです。
実験から分かったことの3番目を参考にすると、ルート相対パスとは、**「実行された大本のファイルのルートのパス(今回の場合はhttp://localhost
)を自動で付けて、サーバー的な絶対パスにしたもの」**と言うことができそうです。
つまり、絶対パスを書くのと同じような書き方をしながら記述量を削減できる、さらにはルートの変更にも対応可能ということです。
ルートのパスを意識する必要はありますが、ルートなんてそうそう変更するものではないでしょう。多分。
絶対パスと相対パスの間にある便利なもの、それがルート相対パスです。(ですよね?)
includeではルート相対パスは使えない?
使えないこともないようですが、サーバーの設定なんかに振り回されることになりかねないそうです。
実際に私のサイト制作でも、正しくパスを書いたつもりなのに「No such file or directory」と怒られてしまいました。
リンク先の場合とは反対に、コンピューター的なものは使えるが、サーバー的なものは使えないのかもしれません。(でも、普通の相対パスは使えるんだよなぁ・・・)
include('/index/test/B.php')
ではなく、include($_SERVER['DOCUMENT_ROOT'].'/index/test/B.php')
と書いた方が良いみたいです。
結局、何パスを使えば良いの?
どうやら、内部リンクでは絶対パスは使わないのが一般的みたいです。
とりあえず、当分の間はルート相対パスを使おうと思います。
フレームワークを使えば、こういった苦労からは解放されるのかな?