PHP获取本地域名与完整路径,本质就是读$_SERVER:协议看HTTPS或HTTP_X_FORWARDED_PROTO,域名看HTTP_HOST(自带端口)或SERVER_NAME(不带端口),路径看REQUEST_URI和DOCUMENT_ROOT,把这几段拼起来就是完整URL。
php获取当前域名和完整的url方法
在PHP里拿域名,绕不开超全局数组$_SERVER,它由Web服务器(Apache、Nginx、Caddy)在转发请求时填充,所以不同环境下字段的完整度并不一样,理解每个字段的来源,比记住一段拼接代码更重要。
三个关键变量分别负责什么
$_SERVER['HTTP_HOST']:来自请求头Host,格式是域名:端口,例如shop.test:8080,它反映用户浏览器实际访问的地址。$_SERVER['SERVER_NAME']:来自服务器配置文件里的ServerName指令,通常只有域名,没有端口,虚拟主机多站点场景下,它可能和用户访问的域名不一致。$_SERVER['REQUEST_URI']:浏览器请求的路径加查询串,例如/index.php?id=3,不含域名。
据PHP官方文档说明,$_SERVER中的值由Web服务器提供,不能保证在所有服务器上都存在,所以取用前必须做存在性判断。
拼接完整URL的通用写法
<?php
function currentUrl(): string
{
// 1. 判断协议,兼容反向代理
$https = $_SERVER['HTTPS'] ?? '';
$proto = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '';
$scheme = ($https === 'on' || $https === '1' || $proto === 'https') ? 'https' : 'http';
// 2. 取主机,优先 Host 头
$host = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? 'localhost';
// 3. 取路径
$uri = $_SERVER['REQUEST_URI'] ?? '/';
return $scheme . '://' . $host . $uri;
}
echo currentUrl(); // 输出 https://shop.test:8080/index.php?id=3
这段代码有两个细节:一是HTTP_HOST自带端口,拼接时不要再手工加和端口号,否则会出现8080:8080;二是$_SERVER['HTTPS']在部分服务器上会被设为off而不是空值,所以判断时用严格比较更稳。
只想要域名,不要路径
$host = $_SERVER['HTTP_HOST'] ?? 'localhost'; // 带端口
$domain = explode(':', $host)[0]; // 去掉端口
$domain = preg_replace('/^www\./i', '', $domain); // 去掉 www
在需要写Cookie作用域、生成邮件链接、做跨域白名单时,通常只需要裸域名。parse_url()也可以,但传给它的是完整URL,多一步拼接,不如直接处理HTTP_HOST来得直接。
php获取本地域名和项目路径的区别
初学者常把这两个概念混在一起,写出来的代码在本地能跑,上服务器就报错,业内共识认为,域名属于"访问层",路径属于"文件层",它们来自不同的数据源,不能互相推导。
域名是浏览器地址栏里的东西
本地开发时,这个"域名"可能是三种形态:
localhost或0.0.1,配合php -S localhost:8000或XAMPP默认端口;- 配置了虚拟主机的自定义后缀,比如phpstudy里的
shop.test、Laragon里的app.test; - Docker环境下的容器服务名,如
web、php-fpm,从宿主机访问时又变成localhost:映射端口。
这三种情况下HTTP_HOST都能正确反映用户访问入口,但SERVER_NAME未必跟着变,尤其在Docker里,容器内SERVER_NAME可能还是localhost。
路径是文件在磁盘上的位置
获取项目根目录,常用三种方式,差异明显:
| 写法 | 适用场景 | |
|---|---|---|
__DIR__ |
当前文件所在目录的绝对路径 | 引入配置、类文件,最稳定 |
$_SERVER['DOCUMENT_ROOT'] |
站点根目录 | 需要拼静态资源物理路径 |
getcwd() |
进程当前工作目录 | CLI脚本,会被chdir()改变 |
__DIR__是编译期常量,不受运行目录影响,写require __DIR__ . '/config.php'几乎不会出错,而DOCUMENT_ROOT在Nginx+PHP-FPM下有时会是空值,取用前要加判断。
URL路径要用另一套变量
想要网站根URL,用$_SERVER['SCRIPT_NAME']去掉文件名部分:
$basePath = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'])), '/');
// 子目录部署返回 /shop,根目录部署返回空字符串
这一步在项目部署到/shop这类子目录时特别关键,否则生成的静态资源链接会全部404。
本地开发环境php获取域名端口失败怎么办
这是高频场景:命令行跑脚本,$_SERVER['HTTP_HOST']压根不存在,直接报"未定义索引"警告,PHP 7.0以后可以用做兜底,但更重要的是给一个合理默认值。
先判断当前运行模式
function baseUrl(): string
{
if (PHP_SAPI === 'cli') {
return 'http://localhost'; // CLI 下没有域名概念
}
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? 'localhost';
return $scheme . '://' . $host;
}
PHP_SAPI能直接区分cli、fpm-fcgi、apache2handler,比猜测某个$_SERVER键是否存在更可靠。
Docker 与端口映射的坑
容器内HTTP_HOST是localhost:80,宿主机映射到8080,用户实际访问的是localhost:8080,如果代码用HTTP_HOST生成回调地址,就会漏掉映射端口,解决办法是在docker-compose.yml里给容器加环境变量,比如APP_URL=http://localhost:8080,代码优先读它,读不到再走$_SERVER兜底,这套写法不依赖任何付费组件,纯原生PHP即可实现,适合预算有限的小型内网项目参考。
反向代理下拿到的是内网地址
在华东、华北不少机房的Nginx反向代理后面,PHP-FPM收到的REMOTE_ADDR是代理机内网IP,协议也常显示为http,这时要读代理透传的头:
$realIp = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR']; $protocol = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? 'http';
前提是代理层确实配置了这两条头,否则读到的是空值,据工信部相关机房建设规范提及的分层架构,七层代理透传已是常规做法,但具体头字段名由运维配置决定,需要和部署方确认。
安全与兼容:几个容易忽略的细节
第一,HTTP_HOST来自客户端请求头,可以被伪造,把它直接拼进邮件链接或写进日志,存在被注入恶意域名的风险,输出前用preg_match('/^[a-zA-Z0-9\.\-]+(:\d+)?$/', $host)过滤一次,成本极低。
第二,做重定向跳转时,优先使用配置文件里的固定域名,而不是动态读取,业内专家指出,开放重定向漏洞中有相当一部分源于动态拼接主机名。
第三,Windows开发机下__DIR__返回反斜杠,拼接URL前必须str_replace('\\', '/', ...),否则http://localhostC:\www这类畸形地址会直接暴露在页面上。
第四,$_SERVER['SERVER_PORT']能拿到端口,但反向代理下返回的是容器端口,不如直接信任HTTP_HOST里携带的端口。
一套可复用的封装
class UrlHelper
{
public static function host(): string
{
$host = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME'] ?? 'localhost';
return preg_match('/^[a-zA-Z0-9\.\-]+(:\d+)?$/', $host) ? $host : 'localhost';
}
public static function baseUrl(): string
{
$https = $_SERVER['HTTPS'] ?? '';
$proto = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '';
$scheme = ($https === 'on' || $https === '1' || $proto === 'https') ? 'https' : 'http';
return $scheme . '://' . self::host();
}
public static function root(): string
{
return dirname(__DIR__); // 按实际层级调整
}
}
调用UrlHelper::baseUrl()拿到https://shop.test:8080,UrlHelper::root()拿到项目绝对路径,两个需求一次覆盖。
PHP获取域名靠请求头变量,获取路径靠魔术常量和服务器变量,两者分开处理,再各自加兜底和过滤,代码才能在本地、测试、生产三种环境里稳定运行。
PHP获取域名与完整路径常见问答
Q1:HTTP_HOST 和 SERVER_NAME 到底该用哪个?
生成访问链接用HTTP_HOST,它带端口、跟随用户实际访问入口,更符合预期,做日志标记或服务器内部判断用SERVER_NAME,它不受请求头篡改影响,两者都不存在时统一回退到localhost。
Q2:本地用 localhost 访问,生成的回调地址也是 localhost,怎么在测试环境改成真实域名?
在项目配置里定义APP_URL常量,通过.env文件区分环境,代码里优先读配置,读不到再走$_SERVER兜底,这样本地是http://localhost:8000,测试环境是https://test.example.com,同一份代码不用改。
Q3:获取到的域名是内网IP而不是域名,问题出在哪?
多数情况是请求没有经过带Host头的七层代理,或者代理未透传X-Forwarded-Host,检查Nginx配置里是否写了proxy_set_header Host $host;,缺少这一行时,PHP-FPM收到的HTTP_HOST就是上游地址。