PHP多租户子域名识别须优先从$_SERVER['HTTP_HOST']提取租户标识,反向代理下改用X-Forwarded-Host;Nginx需通过map+fastcgi_param传递$subdomain;数据库映射需缓存domains表查询结果;Apache可用mod_rewrite注入subdomain参数;最后应构建gethostname等降级链确保容错。

如果您在搭建PHP多租户SaaS平台时需为每个租户分配独立子域名(如tenant1.example.com、tenant2.example.com),但服务器仅配置了泛域名DNS解析(*.example.com → 服务器IP),则PHP应用必须主动识别并路由至对应租户上下文。以下是实现该功能的具体方法:
一、基于HTTP_HOST头解析子域名
PHP可通过$_SERVER['HTTP_HOST']获取客户端请求的完整主机名,提取前缀部分作为租户标识,适用于标准HTTP/HTTPS请求且未启用反向代理的环境。
1、在入口文件(如index.php)顶部添加子域名提取逻辑。
2、使用parse_url($_SERVER['HTTP_HOST'], PHP_URL_HOST)或直接字符串分割获取主域名前的部分。
3、过滤掉www、api等保留子域名,排除空值和非法字符。
4、将提取的子域名作为tenant_id传入路由或数据库查询条件。
5、若未匹配到有效租户,返回404 Not Found或重定向至注册页。
二、通过Nginx传递真实子域名变量
当站点部署在Nginx后端且前端存在CDN或负载均衡器时,原始HTTP_HOST可能被覆盖,需在Nginx配置中显式传递子域名信息至PHP-FPM。
1、在server块中添加fastcgi_param指令:fastcgi_param X-TENANT-SUBDOMAIN $subdomain;
2、使用map指令预定义$subdomain变量:map $host $subdomain { ~^(?.+)\.example\.com$ $sub; default ""; }
3、确保php-fpm.conf或pool配置允许接收自定义参数。
4、在PHP中读取:$tenant = $_SERVER['HTTP_X_TENANT_SUBDOMAIN'] ?? '';
5、验证该值是否为非空且符合正则^[a-z0-9][a-z0-9\-]{1,61}[a-z0-9]$格式。
三、利用数据库动态映射子域名与租户ID
为支持租户自助注册并绑定自定义子域名,需将子域名与租户数据解耦存储,避免硬编码或频繁重启Web服务。
1、创建domains表,字段包含id、tenant_id、domain_name、is_primary、created_at。
2、在请求入口处执行SELECT tenant_id FROM domains WHERE domain_name = ? AND is_primary = 1 LIMIT 1。
3、使用PDO预处理语句防止SQL注入,绑定参数为strtolower($host)。
4、缓存查询结果至APCu或Redis,键名为domain:{$host},过期时间设为300秒。
5、若缓存命中且tenant_id有效,则跳过数据库查询;否则强制刷新缓存并记录未命中日志。
四、Apache环境下使用mod_rewrite提取子域名
在共享主机或无法修改Nginx配置的场景中,可借助Apache的mod_rewrite模块在URL重写阶段剥离子域名并注入查询参数。
1、启用mod_rewrite并在.htaccess中添加RewriteEngine On指令。
2、插入规则:RewriteCond %{HTTP_HOST} ^([a-z0-9\-]+)\.example\.com$ [NC]
3、追加重写:RewriteRule ^(.*)$ /index.php?subdomain=%1&path=$1 [QSA,L]
4、在PHP中通过$_GET['subdomain']获取值,并校验其长度不超过63字符。
5、若%1为空或含非法符号,拒绝路由并返回400 Bad Request。
五、使用PHP内置的gethostname与$_SERVER组合容错识别
当HTTP_HOST不可靠(如HTTP/1.0请求缺失Host头)或需兼容CLI模式测试时,应构建降级识别链以保障基础租户判定能力。
1、优先读取$_SERVER['HTTP_HOST'],若为空则尝试$_SERVER['SERVER_NAME']。
2、若仍为空,调用gethostname()获取系统主机名,并截取首段作为默认租户标识。
3、对所有来源的候选值统一执行trim()、strtolower()及preg_replace('/[^a-z0-9\-]/', '', $input)清洗。
4、检查清洗后字符串是否存在于白名单数组(如['demo', 'staging', 'prod'])中。
5、若全部失败,设置tenant_id为null并触发租户上下文初始化中断。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











