__DIR__で相対パス問題を一掃する
__DIR__ とは、そのコードが書かれているファイルの置かれたディレクトリの絶対パスを表すマジック定数です。require の基準にすると cwd 依存の事故を防げます。
相対パスがcwd依存で壊れる話
require や fopen で 'config/app.php' のような相対パスを書くと、基準になるのは「そのファイルの場所」ではなく「実行時のカレントディレクトリ(cwd)」です。ここを勘違いしていると、CLI から叩いたときと Web から叩いたときでパスが変わって、片方だけ動くという事故が起きます。cron に登録した途端に読み込めなくなる、というのはだいたいこれですね。
// bad: cwd が想定と違うと壊れる
require 'lib/helper.php';
// cwd を確認してみると分かりやすい
echo getcwd(); // 実行した場所によって変わる
相対パスは「呼び出し元の気分」で解決されると思っておくと安全です。ライブラリの中で相対パスを書くのは特に危ういです。
__DIR__を基準にする
解決策はシンプルで、__DIR__(そのファイルが置かれているディレクトリの絶対パス)を基準にしてしまうことです。これで cwd が何であろうと結果は変わりません。私はプロジェクト内の require はほぼ全部この形で書いています。
// そのファイルの場所を基準に解決する
require __DIR__ . '/lib/helper.php';
$config = require __DIR__ . '/../config/app.php';
$logPath = __DIR__ . '/../var/log/app.log';
__FILE__ はファイル自身のフルパス、__DIR__ はその親ディレクトリで、dirname(__FILE__) と同じです。今はほぼ __DIR__ で足りると思います。
プロジェクトルートを一度だけ定義する
__DIR__ . '/../../' が何段も続くと、階層を数えるのが面倒になってきます。エントリポイントでルートを定数化しておくと、以降は基準がぶれません。もっとも、Composer を使っているならオートロードに任せるのが一番で、手書きの require はあくまで設定ファイルの読み込みなどに絞るのが現実的だと思います。
// public/index.php などエントリポイントで一度だけ
define('APP_ROOT', dirname(__DIR__));
// 以降はどこからでも同じ基準
$config = require APP_ROOT . '/config/app.php';
まとめ
相対パスの基準は cwd であって、書いてあるファイルの場所ではありません。ここが直感とずれているせいでハマるので、require や設定読み込みでは __DIR__ を前置してしまうのが確実です。ルートを一度定義しておくか、いっそオートロードに寄せてしまえば、環境が変わっても壊れないコードになると思います。
よくある質問
Q. 相対パスの require はなぜ壊れるのですか?
A. 相対パスの基準は実行時のカレントディレクトリ(cwd)で、ファイルの場所ではないからです。CLI と Web、cron 登録などで動いたり動かなかったりします。
Q. __FILE__ と __DIR__ の違いは?
A. __FILE__ はファイル自身のフルパス、__DIR__ はその親ディレクトリです。dirname(__FILE__) と同じで、今はほぼ __DIR__ で足ります。
Q. 階層が深くて ../ が続くときは?
A. エントリポイントでプロジェクトルートを定数化すると基準がぶれません。Composer を使っているなら、オートロードに寄せるのが一番です。