You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多态多对多关系子查询中如何自动获取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');
    }
}

需求明确:

  1. 有没有办法让Laravel自动获取关联对应表的ID列名?
  2. 能否不用手动定义每个关联的表名?
  3. 这种场景下的最佳实践是什么?

试过蛇形命名转换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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 23:15:28