Laravel动态设置模型表名后关联查询报错问题求解
Laravel 动态表名模型关联预加载异常解决方案
场景说明
- 开发全自动化流程项目,支持用户无命名规则限制自定义分类及对应子项
- 后端实现逻辑:根据用户提交的名称自动创建分类表、子项表,仅通过
ParentModel、RelatedModel两个模型分别承载父级分类、子项数据,实例化模型时传入表名动态绑定对应数据表 - 原有实现代码:
- 模型实例化
$parent = new ParentModel('product_categories'); $related = new RelatedModel('products');
- ParentModel 关联定义
public function childrens() { return $this->hasMany(RelatedModel::class , 'parent_id' ,'id'); }
- RelatedModel 原有代码
public function __construct($table = null ,array $attributes = []) { parent::__construct($attributes); $this->setTable($table); } public function parent() { return $this->belongsTo(ParentModel::class , 'parent_id' ,'id'); }
异常表现
- 单表查询正常:执行
$query = $parent->newQuery()->get()可正确返回对应表数据 - 关联预加载报错:执行
$query = $parent->newQuery()->with('childrens')->get()时,Laravel 忽略动态设置的表名,默认读取RelatedModel对应的默认表名related_models生成SQL,抛出表不存在异常,实际生成的错误查询语句为:
select * from `related_models` where `related_models`.`parent_id` in (1,2)
问题根因
Laravel 关联预加载解析关联关系时,不会复用当前模型实例上的动态配置,而是根据关联定义中传入的模型类名重新实例化一个全新的默认模型对象构建查询。原关联方法直接传入RelatedModel::class类名,生成的新实例未经过setTable动态配置表名,因此会读取模型默认配置的表名触发错误。
实现方案
第一步:改造ParentModel
新增关联子表名存储属性,重写构造方法支持传入关联表名,关联定义时直接传入已绑定动态表名的模型实例,而非类名字符串:
class ParentModel extends Model { /** * 存储当前分类对应的子项表名 * @var string */ protected string $relatedTable = ''; public function __construct($table = null, $relatedTable = null, array $attributes = []) { parent::__construct($attributes); // 绑定自身表名 if (!is_null($table)) { $this->setTable($table); } // 绑定关联子表名 if (!is_null($relatedTable)) { $this->relatedTable = $relatedTable; } } public function childrens() { // 直接传入已配置动态表名的关联模型实例,不使用类名让框架自动实例化 $relatedInstance = new RelatedModel($this->relatedTable, $this->getTable()); return $this->hasMany($relatedInstance, 'parent_id', 'id'); } }
第二步:改造RelatedModel
新增父表名存储属性,反向关联定义时同样传入已绑定动态表名的父模型实例:
class RelatedModel extends Model { /** * 存储当前子项对应的父分类表名 * @var string */ protected string $parentTable = ''; public function __construct($table = null, $parentTable = null, array $attributes = []) { parent::__construct($attributes); // 绑定自身表名 if (!is_null($table)) { $this->setTable($table); } // 绑定关联父表名 if (!is_null($parentTable)) { $this->parentTable = $parentTable; } } public function parent() { // 传入已配置动态表名的父模型实例 $parentInstance = new ParentModel($this->parentTable, $this->getTable()); return $this->belongsTo($parentInstance, 'parent_id', 'id'); } }
第三步:调整实例化调用方式
实例化父模型时同时传入自身表名和对应的子项表名即可,不需要额外调整查询逻辑:
// 示例:分类表为product_categories,对应子项表为products $parent = new ParentModel('product_categories', 'products'); // 关联预加载可正常执行,自动匹配动态传入的表名 $query = $parent->newQuery()->with('childrens')->get();
注意:所有关联定义处都要传入提前配置好表名的模型实例,禁止直接传入模型类名字符串,否则框架会重新实例化默认模型导致动态表名失效。
内容的提问来源于stack exchange,提问作者Ahmed Mohammed
相关产品推荐
相关产品推荐

