許多開發者在框架源碼中見過 dirname(__FILE__) 拼接路徑的寫法,卻不知其必要性。本文透過多層嵌套包含場景,深度剖析 PHP 文件包含的相對路徑解析規則,證明 dirname(__FILE__)(或 PHP 5.3+ 的 __DIR__)是解決「包含鏈路徑漂移」的標準方案。
目錄結構:
/wwwroot ├── a.php ├── d/d.php ├── b/b.php └── c/c.php
<?php
$file_name = 'a.php';
echo "this is a.php <hr>";
require('c/c.php');
?>
<?php
$c_file_name = 'c.php';
echo 'this is c.php, is required by ' . $file_name . '<hr>';
require('../d/d.php'); // 錯誤!相對於 a.php 應為 d/d.php
?>
Warning: require(../d/d.php): failed to open stream — 因包含文件中的相對路徑始終相對於執行入口腳本 (a.php) 解析,而非當前文件 (c.php)。
PHP require 實質是將目標文件代碼「複製粘貼」到調用位置。上述案例等價於:
<?php
$file_name = 'a.php';
echo "this is a.php <hr>";
// require('c/c.php') 展開如下:
$c_file_name = 'c.php';
echo 'this is c.php, is required by ' . $file_name . '<hr>';
require('../d/d.php'); // 此處相對路徑基於 a.php 所在目錄
?>
若修正為 require('d/d.php'),a.php 正常;但若 b/b.php 也包含 c.php:
<?php // b/b.php
$file_name = 'b.php';
echo "this is b.php <hr>";
require('../c/c.php');
?>
展開後 require('d/d.php') 相對於 b.php 應為 ../d/d.php — 同一被包含文件無法同時滿足不同入口的相對路徑。
<?php // c/c.php 修正版 $c_file_name = 'c.php'; echo 'this is c.php, is required by ' . $file_name . '<hr>'; // 以「當前文件所在目錄」為基準,彈性構建目標路徑 require(dirname(__FILE__) . '/../d/d.php'); // PHP 5.3+ 推薦更簡潔的魔術常量: require(__DIR__ . '/../d/d.php'); ?>
__FILE__ / __DIR__ 在編譯期確定為當前文件的絕對路徑/目錄,不受調用者影響。雖然 __DIR__ 解決了手動包含路徑問題,但現代 PHP (Composer/PSR-4) 推薦自動載入機制:
// composer.json
"autoload": {
"psr-4": { "App\\": "src/" }
}
// 代碼中直接 use App\Module\ClassName; 無需手動 require
自動載入器內部同樣依賴 __DIR__ 構建映射表,徹底消除了路徑拼接的心智負擔。
require/include 路徑均以 __DIR__ 為基準。理解「包含即展開」與「路徑相對於入口腳本」這兩大核心機制,是寫出健壯 PHP 應用的基石。