|
PHP里拿绝对路径,取绝最直接的对路办法是 __DIR__ 和 __FILE__。这俩魔术常量从PHP 5.3开始就稳定可用,取绝至今没变过。对路__FILE__ 返回当前文件的取绝完整路径,__DIR__ 返回当前文件所在目录。对路比如你在 /var/www/html/index.php 里写 echo __DIR__;,取绝输出就是对路 /var/www/html。没有尾部斜杠,取绝这点要注意。对路 很多人喜欢用 dirname(__FILE__),取绝这写法在PHP 5.2时代很流行,对路因为那时候还没有 __DIR__。取绝现在完全没必要了,对路__DIR__ 编译时就解析好了,取绝比函数调用快那么一丁点。别小看这点差异,在高频加载的场景下还是有意义的。 但魔术常量有个硬伤——它绑定的是文件本身的位置。如果你在 /var/www/html/lib/helper.php 里写 __DIR__,拿到的永远是 /var/www/html/lib,不管你从哪里include它。这既是优点也是限制。优点是你不用操心调用方在哪;限制是你没法用它来定位入口文件所在的目录。 想拿入口文件的目录,得用 $_SERVER['DOCUMENT_ROOT']。这个值来自Web服务器配置,Apache下通常是 DocumentRoot 指令的值,Nginx配PHP-FPM时则由 fastcgi_param DOCUMENT_ROOT 决定。它指向网站根目录,跟具体PHP文件位置无关。我见过不少项目在入口文件里定义 define('ROOT_PATH', $_SERVER['DOCUMENT_ROOT']);,然后全项目都用这个常量拼路径。 问题来了:CLI模式下 $_SERVER['DOCUMENT_ROOT'] 通常是空的。跑定时脚本、队列任务的时候,这个方案直接歇菜。所以更稳的做法是用 dirname(__DIR__) 往上退。假设入口文件在项目根目录,某个类文件在 src/Util/ 下,类里写 dirname(__DIR__, 2) 就能回到项目根。PHP 7.0以后 dirname() 支持第二个参数指定层级,写起来比嵌套调用清爽得多。 还有一种场景是获取当前工作目录,用 getcwd()。它返回的是PHP进程启动时的工作目录,CLI下就是你执行命令时所在的目录,Web下通常是入口文件所在目录(但不保证)。这函数容易被误用。比如你在 /tmp 下执行 php /var/www/script.php,getcwd() 返回 /tmp,不是 /var/www。用之前想清楚你到底要的是哪个“当前”。 实际项目里我习惯这么干——入口文件 index.php 里定义: define('BASE_PATH', dirname(__DIR__)); define('APP_PATH', BASE_PATH . '/app'); 然后所有路径拼接都基于这两个常量。好处是无论从哪个文件引用,结果都一样,不依赖 $_SERVER,CLI和Web通吃。坏处是引入了一个全局常量,但比起路径混乱,这点代价完全值得。 有个细节容易踩坑:Windows下路径分隔符是反斜杠。__DIR__ 在Windows上返回的也是反斜杠,比如 C:\project\src。拼路径时别硬编码 /,用 DIRECTORY_SEPARATOR 或者干脆用 /——PHP在Windows上也能识别正斜杠,文件系统API会自动转换。我倾向统一用 /,代码看起来干净,跨平台也没问题。 符号链接是另一个坑。假设 /var/www/current 是指向 /var/www/releases/v1.2.3 的软链,__DIR__ 返回的是解析后的真实路径还是软链路径?答案是真实路径。__FILE__ 和 __DIR__ 都会解析符号链接。如果你需要保留软链路径,得用 $_SERVER['SCRIPT_FILENAME'] 配合 realpath() 的反操作,但PHP没有直接提供不解析软链的函数。这种需求很少见,部署脚本里才可能碰到。 性能方面,__DIR__ 是编译期常量,零运行时开销。dirname()、realpath() 都是函数调用,有开销但微乎其微。realpath() 会做文件系统检查,确认路径真实存在,不存在返回 false。别在循环里反复调 realpath(),缓存结果。 最后提一个常见错误:用 $_SERVER['PHP_SELF'] 拼路径。PHP_SELF 来自URL,用户可控,存在安全风险。拿它做文件包含或者路径拼接,等于把刀递给攻击者。绝对路径的获取永远不该依赖任何用户输入。 |