Laravel:如何加载含对应Category标签的所有Post?
解决Laravel中通过共享标签加载关联Post的问题
嘿,我来帮你搞定这个需求!首先咱们得先确认三个模型的多态关联配置是正确的,这是后续查询的基础,然后再分场景给出具体的实现方案。
第一步:确认模型关联配置
先确保你的Tag、Post、Category模型的多态关联是这么写的:
Tag 模型
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphTo; class Tag extends Model { protected $fillable = ['name']; public function taggable(): MorphTo { return $this->morphTo(); } }
Post 模型
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphToMany; class Post extends Model { protected $fillable = ['title', 'content']; public function tags(): MorphToMany { return $this->morphToMany(Tag::class, 'taggable'); } }
Category 模型
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphToMany; class Category extends Model { protected $fillable = ['name']; public function tags(): MorphToMany { return $this->morphToMany(Tag::class, 'taggable'); } }
第二步:根据需求实现查询
下面分几种常见的场景来写查询逻辑,你可以根据自己的实际需求选择:
场景1:获取某个特定Category的标签关联的所有Post
比如你想拿到ID为1的Category下所有标签对应的Post(避免重复结果),可以这么写:
$category = Category::find(1); // 方式1:先拿到该Category的所有标签ID,再筛选关联这些标签的Post $tagIds = $category->tags()->pluck('tags.id'); $posts = Post::whereHas('tags', function ($query) use ($tagIds) { $query->whereIn('tags.id', $tagIds); })->distinct()->get(); // 方式2:更简洁的链式写法,不用提前拉取标签ID $posts = Post::whereHas('tags', function ($query) use ($category) { $query->whereHas('taggable', function ($q) use ($category) { $q->where('taggable_type', Category::class) ->where('taggable_id', $category->id); }); })->distinct()->get();
场景2:获取Post并同时加载对应的关联Category(通过共享标签)
如果需要同时拿到Post和它们通过共享标签关联的Category,可以用嵌套预加载:
$posts = Post::with(['tags.taggable' => function ($query) { $query->where('taggable_type', Category::class); }])->get(); // 遍历Post时,记得去重Category(一个Post可能多个标签关联同一个Category) foreach ($posts as $post) { $relatedCategories = $post->tags->pluck('taggable') ->filter() // 过滤掉空值(如果标签没有关联Category的话) ->unique('id'); }
场景3:高效查询(避免N+1问题)
如果数据量比较大,用join的方式会更高效:
// 获取所有和Category共享标签的Post(可指定特定Category) $posts = Post::select('posts.*') ->join('taggables as post_taggables', 'posts.id', '=', 'post_taggables.taggable_id') ->where('post_taggables.taggable_type', Post::class) ->join('taggables as category_taggables', 'post_taggables.tag_id', '=', 'category_taggables.tag_id') ->where('category_taggables.taggable_type', Category::class) // 如果要指定某个Category,取消下面这行注释 // ->where('category_taggables.taggable_id', $categoryId) ->distinct() ->get();
额外补充
- 为什么要加
distinct()?因为一个Post可能关联多个属于同一个Category的标签,会导致查询结果重复,所以去重是必须的。 - 如果你需要的是Post必须关联该Category的所有标签(而不是至少一个),可以用下面的写法:
$category = Category::find(1); $totalTags = $category->tags()->count(); $posts = Post::whereHas('tags', function ($query) use ($category) { $query->whereHas('taggable', function ($q) use ($category) { $q->where('taggable_type', Category::class) ->where('taggable_id', $category->id); }); }, '=', $totalTags)->get();
内容的提问来源于stack exchange,提问作者slickness
相关产品推荐
相关产品推荐

