多态多对多关系子查询中如何自动获取ID列名?
我们有一个Laravel函数,用于检索与给定模型有重叠关联(如标签)的相关条目,核心代码如下:
<?php public function getRelated($entity, $fields = [], $relations = []) { $related = new EloquentCollection(); // 获取拥有共同关联的同类型模型 if (count($relations)) { foreach ($relations as $relation) { // 检查当前模型是否存在当前遍历的关联 if ($entity->{$relation}()->exists()) { // 获取关联模型的所有ID,例如标签ID $relationIds = $entity->{$relation}()->get()->pluck('id')->toArray(); // 获取除当前模型外的所有同类型模型 $relationEntities = $this->entity::whereNot('id', $entity->id) // 排除已找到的模型 ->whereNotIn('id', $related->modelKeys()) // 仅获取拥有当前关联的模型(如标签) ->whereHas($relation, function (Builder $query) use($relationIds, $relation) { $query->whereIn($relation . '.id', $relationIds); }) ->get() ->sortKeysDesc() ->take(10); $related = $related->concat($relationEntities->except($related->modelKeys())); } } } }
调用示例:
$post = Post::find(1); $related = $this->getRelated($post, [], ['tags']);
现在需要支持多关联类型(如标签、分类等),但遇到了问题:当关联方法名是驼峰式(比如fooBars()),代码里用$relation . '.id'会生成fooBar.id,但实际关联表是foo_bar,导致查询错误。
所有关联均为morphToMany/morphedByMany多态关联,模型示例如下:
<?php class Post extends Entity { public function tags() { return $this->morphToMany(Tag::class, 'taggable'); } public function fooBars() { return $this->morphToMany(FooBar::class, 'foo_barable'); } } class Tag extends Entity { public function posts() { return $this->morphedByMany(Post::class, 'taggable'); } } class FooBar extends Entity { public function posts() { return $this->morphedByMany(Post::class, 'foo_barable'); } }
需求明确:
- 有没有办法让Laravel自动获取关联对应表的ID列名?
- 能否不用手动定义每个关联的表名?
- 这种场景下的最佳实践是什么?
试过蛇形命名转换Str::of($relation)->snake() . '.id',但感觉不够严谨。
方法1:通过关联实例自动获取表名
Laravel的关联实例本身携带了关联模型的完整信息,不需要手动转换命名。直接从关联实例中提取表名即可:
修改whereHas闭包内的逻辑:
->whereHas($relation, function (Builder $query) use($relationIds, $entity, $relation) { // 获取关联实例 $relationInstance = $entity->{$relation}(); // 获取关联模型对应的数据库表名 $relatedTable = $relationInstance->getRelated()->getTable(); // 使用正确的表名拼接ID列 $query->whereIn("{$relatedTable}.id", $relationIds); })
原理:getRelated()返回关联对应的模型实例,getTable()会自动适配Laravel默认命名规则或模型自定义的表名(比如模型中设置protected $table = 'custom_table'的场景),完全避免命名转换的误差。
方法2:优化关联ID的获取逻辑(附带性能提升)
原代码中$relationIds = $entity->{$relation}()->get()->pluck('id')->toArray();会加载整个关联模型集合,可优化为直接查询ID:
$relationIds = $entity->{$relation}()->pluck('id')->toArray();
方法3:重构完整函数,提升可靠性与性能
结合以上优化,重构后的完整函数示例:
<?php use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection as EloquentCollection; public function getRelated($entity, $fields = [], $relations = []) { $related = new EloquentCollection(); if (empty($relations)) { return $related; } foreach ($relations as $relation) { $relationInstance = $entity->{$relation}(); // 跳过无关联的场景 if (!$relationInstance->exists()) { continue; } // 获取关联模型的ID列表和表名 $relationIds = $relationInstance->pluck('id')->toArray(); $relatedTable = $relationInstance->getRelated()->getTable(); // 查询相关模型:用数据库排序代替集合排序,性能更优 $relationEntities = get_class($entity)::whereNot('id', $entity->id) ->whereNotIn('id', $related->modelKeys()) ->whereHas($relation, function (Builder $query) use($relationIds, $relatedTable) { $query->whereIn("{$relatedTable}.id", $relationIds); }) ->orderByDesc('id') ->take(10) ->get(); $related = $related->concat($relationEntities->diffKeys($related)); } return $related; }
为什么不推荐蛇形命名转换?
蛇形命名转换依赖严格遵守Laravel命名规则,如果某个关联模型自定义了表名(比如protected $table = 'custom_foo_bars'),这种转换就会失效。而通过getRelated()->getTable()的方式能自动适配所有场景,更可靠。
内容的提问来源于stack exchange,提问作者Alex

