ThinkPHP8比PHP更适合开发SaaS系统,因其内建多租户、应用隔离等工程能力,并通过子域名识别前置、数据库连接动态重建、模型全局作用域强制过滤三大机制封堵跨租户数据泄露。

ThinkPHP8比PHP更适合开发SaaS系统,是因为PHP只是一门编程语言,不具备开箱即用的多租户、应用隔离、中间件管道、依赖注入容器等SaaS架构必需的工程能力,而ThinkPHP8在框架层已深度内建这些机制,并通过子域名识别前置、数据库连接动态重建、模型全局作用域强制过滤三大硬性约束,封堵了90%以上的跨租户数据泄露路径。
子域名识别必须在请求生命周期最前端完成
在app/middleware/CheckTenant.php中,直接读取$_SERVER['HTTP_X_FORWARDED_HOST']或$_SERVER['HTTP_HOST'],用正则/^([a-z0-9\-]+)\.example\.com$/提取合法子域名,拒绝www、api等保留字;【绝对不用request()->domain(),它可能被Nginx rewrite污染,且在CLI/队列场景下直接返回空】。
解析出tenant1后,立即将$request->tenantId = 'tenant1'绑定到请求实例,后续所有组件(日志、缓存、模型)都从此处取值。
该中间件必须排在app/middleware.php数组第一位,早于路由解析、模型加载、日志初始化——漏掉这一步,整个租户隔离就形同虚设。
立即学习“PHP免费学习笔记(深入)”;
数据库连接必须动态注册并立即清空连接池
方法一:在CheckTenant中间件中调用Db::setConnectConfig()替换database、charset、prefix三项配置,确保连接指向db_tenant1而非默认库。
方法二:紧接着执行Db::clearConnectionPool(),【不执行这步,旧连接仍复用默认库,写入和查询都会错库】。
所有业务模型统一声明protected $connection = 'tenant',禁止使用Db::table('user')绕过模型连接配置——这是人为制造租户越权漏洞的高危操作。
模型层必须启用全局作用域强制tenant_id过滤
第一步:创建app/model/scopes/TenantScope.php,实现apply()方法,在where条件中自动追加tenant_id = $this->request->tenantId。
第二步:在基类模型app/model/BaseModel.php的boot()方法中调用static::addGlobalScope('tenant', new TenantScope())。
第三步:验证是否生效——执行User::count()、User::with('profile')->find(1),结果必须严格限定在当前租户数据范围内;若未生效,说明作用域未注册或模型未继承BaseModel。



















