You need to enable JavaScript to run this app. Laravel MongoDB多对多关联获取中间表sequence字段更优方案求解
优惠活动
大模型
产品
解决方案
定价
更多

Laravel MongoDB多对多关联获取中间表sequence字段更优方案求解

问题描述

需求背景

现有Post模型与Categories模型为多对多关联关系,中间关联表为PostCategory,表中存储post_id、category_id以及文章对应的排序字段sequence。需要在查询Post模型基础属性(title、description等)的同时,获取对应关联下的sequence值。

当前实现代码如下:

$posts = Post::where([
    'is_active' => 1,
    'is_deleted' => 0,
    'is_published' => 1,
    'status' => 'publish'
])->whereHas('category', function ($query) use ($params) {
    return $query->where([
        'category_id' => $params['categoryId'],
    ]);
})->with([
    'category' => function ($query) use ($params) {
        return $query->where([
            'category_id' => $params['categoryId'],
        ]);
    }
])
    ->orderBy('live_date', 'DESC')
    ->orderBy('publish_time', 'DESC')
    ->get()
    ->toArray();
    
foreach ($posts as &$post) {
    $post['sequence'] = $post['category']['sequence'];
}

上述代码可以得到预期结果,但存在明显冗余:需要在whereHas和with方法中重复编写相同的筛选闭包,且查询完成后需要遍历整个集合手动将sequence字段提到Post数组顶层,需要更优的实现方案。

现有模型定义

Post.php

namespace App\Models\Mongo;


/**
 * @mixin \Illuminate\Database\Eloquent\Builder
 * @mixin \Jenssegers\Mongodb\Query\Builder
 */
class POST extends \Jenssegers\Mongodb\Eloquent\Model
{
    /** @var string Mongo Connection Name */
    //protected $connection = 'mongodb';

    /** @var string Mongo Collection Name */
    protected $collection = 'posts';

    /** @var bool Enable/Disable Timestamp */
    public $timestamps = true;

    /** @var string Date format */
    protected $dateFormat = 'Y-m-d H:i:s';

    /** @var array */
    protected $dates = ['created_at', 'updated_at', 'live_date', 'expire_date'];

    /**
     * // 我知道这个关联不正确,应该用belongsToMany或者hasMany
     * // 但目前我需要查询归属单个分类ID的文章,所以暂时用hasOne关联
     * @return \Jenssegers\Mongodb\Relations\HasOne
     */
    public function category()
    {
        return $this->hasOne(
            PostCategory::class,
            'post_id',
            '_id'
        );
    }
}

PostCategory.php

namespace App\Models\Mongo;


/**
 * @mixin \Illuminate\Database\Eloquent\Builder
 * @mixin \Jenssegers\Mongodb\Query\Builder
 */
class PostCategory extends \Jenssegers\Mongodb\Eloquent\Model
{
    /** @var string Mongo Connection Name */
    //protected $connection = 'mongodb';

    /** @var string Mongo Collection Name */
    protected $collection = 'post_category';

    /**
     * @return \Jenssegers\Mongodb\Relations\HasMany
     */
    public function post()
    {
        return $this->hasMany(Post::class, '_id', 'post_id');
    }
}

已尝试的修改

尝试将Post中的关联改为belongsToMany,但关联无法正常生效,修改代码如下:

return $this->belongsToMany(
    Category::class,
    'post_category',
    'post_id',
    'category_id',
    '_id', // post primary key
    '_id', // category primary key
)->withPivot('sequence');

解决方案

分两种场景实现:

场景1:暂时保留现有hasOne关联,快速优化冗余代码

直接用Laravel内置的withWhereHas方法替代分开的whereHas+with调用,自动同时完成关联筛选和预加载,无需重复编写闭包;再用集合的map方法替代引用遍历处理字段,代码更安全:

$posts = Post::where([
        'is_active' => 1,
        'is_deleted' => 0,
        'is_published' => 1,
        'status' => 'publish'
    ])
    ->withWhereHas('category', fn ($query) => $query->where('category_id', $params['categoryId']))
    ->orderBy('live_date', 'DESC')
    ->orderBy('publish_time', 'DESC')
    ->get()
    ->map(fn($post) => [...$post->toArray(), 'sequence' => $post->category->sequence])
    ->toArray();

场景2:修复belongsToMany关联,彻底符合多对多设计规范

之前关联不生效大概率是Mongo的主键类型匹配问题,首先确保Category模型已正确定义,再修正Post模型的关联:

// Post.php中新增关联,替换原有临时的category关联
public function categories()
{
    return $this->belongsToMany(
        Category::class,
        'post_category',
        'post_id',
        'category_id',
        '_id',
        '_id',
    )->withPivot('sequence');
}

查询时注意,如果$params['categoryId']是字符串形式的MongoID,需要先转成ObjectId类型再匹配,避免关联查询失效:

use MongoDB\BSON\ObjectId;

$categoryId = new ObjectId($params['categoryId']);
$posts = Post::where([
        'is_active' => 1,
        'is_deleted' => 0,
        'is_published' => 1,
        'status' => 'publish'
    ])
    ->withWhereHas('categories', fn ($query) => $query->where('_id', $categoryId))
    ->orderBy('live_date', 'DESC')
    ->orderBy('publish_time', 'DESC')
    ->get()
    ->map(function($post) {
        $data = $post->toArray();
        $data['sequence'] = $post->categories[0]->pivot->sequence;
        // 可按需删除不需要的关联数据
        unset($data['categories']);
        return $data;
    })
    ->toArray();

内容的提问来源于stack exchange,提问作者Hari Darshan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.20 15:15:02