PHP 8.3 内置服务器不支持传统虚拟目录,仅以当前目录为根;需通过同目录下的 router.php 实现路径映射,如将 /admin 请求转发至 ./backend/ 下文件。

php -S 启动时怎么指定虚拟目录路径
PHP 8.3 内置服务器不支持传统意义的“虚拟目录”,它只认当前工作目录为根,但可通过 router.php 文件做路径映射——这本质是路由层转发,不是 Web 服务器级的 alias 或 location。直接运行 php -S localhost:8000 时,所有请求都以当前目录为 DocumentRoot。
若你希望访问 /admin 实际读取 ./backend/ 下的文件,必须写 router.php:
if (preg_match('/^\/admin(\/|$)/', $_SERVER['REQUEST_URI'])) {
$_SERVER['REQUEST_URI'] = preg_replace('/^\/admin/', '', $_SERVER['REQUEST_URI']);
require __DIR__ . '/backend/index.php';
exit;
}
// 其余请求走默认逻辑(如静态文件)
注意:router.php 必须位于启动命令所在目录;$_SERVER['DOCUMENT_ROOT'] 不会变,别依赖它做路径判断。
CLI 模式下验证 open_basedir 是否限制了虚拟目录访问
很多所谓“虚拟目录”其实是靠 open_basedir 配合软链接或路径别名实现的。但在 CLI 模式下,open_basedir 依然生效,且容易被忽略——比如你用 php artisan serve 或 php bin/console 时,如果脚本试图 include 超出范围的文件,会直接报错:
立即学习“PHP免费学习笔记(深入)”;
Warning: include(): open_basedir restriction in effect.Failed opening required '/path/outside/basedir/config.php'
验证方法很简单:
php -r "echo ini_get('open_basedir');"
输出为空说明未启用;输出一串路径(如 /var/www/site/:/tmp/)则表示已生效。注意:CLI 和 FPM 的 open_basedir 是独立配置的,php --ini 查到的 php.ini 路径才是 CLI 真实加载的文件。
为什么 php -S 无法加载 .user.ini 里的 open_basedir
.user.ini 是 PHP-FPM / Apache SAPI 特有的机制,CLI 模式(包括 php -S)完全不读它。这是最容易踩的坑:你在网站根目录写了 .user.ini 并重载了 Nginx,FPM 下一切正常;但一跑 php -S 就发现 file_get_contents 突然能读 /etc/passwd 了——因为 CLI 根本没受限制。
解决方案只有两个:
- 在 CLI 启动前手动设置:
PHP_VALUE="open_basedir=/my/project/:/tmp/" php -S localhost:8000 - 或改 CLI 专用的
php.ini(路径由php --ini输出确认),写死open_basedir = "/my/project/:/tmp/"
别指望 .user.ini 在命令行里起作用,它连被扫描的资格都没有。
检查虚拟目录相关函数是否可用(比如 symlink、realpath)
有些“虚拟目录”逻辑依赖 symlink() 或 realpath() 解析路径。PHP 8.3 默认允许这些函数,但若你改过 disable_functions,它们可能已被禁用:
php -r "var_dump(function_exists('symlink'));"
输出 bool(false) 就说明被禁了。常见被误删的函数还有 readlink、chdir、getcwd——尤其是 Laravel 的 Artisan::call() 或 Symfony 的 console 命令,内部会频繁切换工作目录。
检查方式:
php -r "print_r(ini_get('disable_functions'));"
如果输出包含 symlink,readlink 等,就得去对应 php.ini 里删掉它们——注意 CLI 和 FPM 的 disable_functions 是分开配的,别只改了一个。
CLI 下的“虚拟目录”从来不是 Web 服务器概念,而是路径控制 + 运行时权限的组合。最常漏掉的是 CLI 与 FPM 配置分离这件事,以及误以为 .user.ini 对所有 SAPI 都有效。



















