如何动态修改Eloquent查询实现Laravel无侵入多租户
是否可以拦截并修改Eloquent查询的组件与作用域?
完全可以,Eloquent提供了多层扩展钩子,完全能做到无侵入改造,不需要逐行修改现有500多处硬编码,以插件化形式适配多租户的ID映射需求。
现有全局作用域的基础扩展
你已经实现的tenant_id全局过滤作用域本身就可以拿到查询实例的全量条件:
- 调用
$builder->getQuery()->wheres就能获取当前查询已经绑定的所有where条件结构,递归遍历该数组,检测到针对主键、外键、状态类ID字段的等值查询时,直接把你识别到的硬编码相对ID,替换为提前计算好的当前租户对应真实自增ID即可。 - 嵌套where、orWhere等复杂条件会以子数组形式存在于wheres结构中,递归遍历即可覆盖所有场景,不会漏判。
查询环节的全量拦截方案
如果要覆盖所有where调用(包括业务代码里直接调用where、模型内部自动追加的条件),可以自定义Eloquent查询构建器子类:
- 重写子类的
where、orWhere、whereIn等常用条件方法,在方法执行前先判断:当前查询的模型是否开启租户ID映射、传入的查询字段是否是需要做ID转换的字段(主键、关联外键、配置类ID字段),如果是就先做ID值转换,再调用父类对应方法拼接条件。 - 在模型基类中重写
newEloquentBuilder方法,返回你自定义的构建器实例,所有继承基类的模型自动生效,不需要改业务代码。
// 自定义Builder示例片段 class TenantScopedBuilder extends Builder { public function where($column, $operator = null, $value = null, $boolean = 'and') { // 识别参数签名,兼容where('id', 2)这种省略操作符的写法 if (/* 判断$column是需要转换的ID字段 */) { $value = $this->convertTenantRelativeIdToRealId($value); } return parent::where($column, $operator, $value, $boolean); } }
对于部分直接用DB门面写的原生查询,可以注册
DB::beforeExecuting回调,在SQL执行前遍历绑定参数,对识别到的ID参数做转换即可覆盖。
写入环节的拦截方案
针对create、update等写入操作的硬编码ID,用模型事件即可无侵入处理:
- 抽离一个多租户适配的trait,在trait的boot方法中注册
saving模型事件,在数据落库前遍历待写入的属性数组,把所有需要转换的ID字段值替换为当前租户对应的真实ID。 - 所有需要适配多租户的模型直接use这个trait即可,不需要改内部的create、update调用逻辑。
- 不想逐模型引入trait的话,可以在服务提供者中通过
Model::resolving钩子,给所有符合规则的模型自动绑定saving事件回调,做到全局生效。
适配效果
你提到的两段示例代码,不需要做任何修改就能自动适配多租户逻辑:
- 查询场景代码
$notification_type= NotificationTypes::where('id', '2')->get();
执行前会自动将id条件的2替换为当前租户下相对ID为2的NotificationTypes真实主键(比如4、7),配合已有的tenant_id全局作用域,返回的就是目标租户下的对应记录。
2. 写入场景代码
$tickets = Tickets::create( $title, $body, $sender, '1'); // 1 as statusID
写入前会自动将传入的statusID值1替换为当前租户下状态表中相对ID为1的真实状态ID,不会出现跨租户ID错乱的问题。
内容的提问来源于stack exchange,提问作者Mostafa Lotfi
相关产品推荐
相关产品推荐

