
本文介绍在 Laravel 中通过模型关系与查询优化,准确判断当前登录用户是否已收藏某条广告,涵盖 hasMany 关系定义、Eloquent 预加载计数(withCount)及运行时判断方法,并提供安全、可扩展的 Blade 模板用法。
本文介绍在 laravel 中通过模型关系与查询优化,准确判断当前登录用户是否已收藏某条广告,涵盖 `hasmany` 关系定义、eloquent 预加载计数(`withcount`)及运行时判断方法,并提供安全、可扩展的 blade 模板用法。
在 Laravel 应用中,实现「用户是否已收藏某广告」的判定,关键在于正确建模 Advert 与 Favorite 的一对多关系,并避免 N+1 查询问题。以下为完整、生产就绪的实现方案。
✅ 正确建模关系(Advert 模型)
首先,在 app/Models/Advert.php 中定义一对多关系(一个广告可被多个用户收藏):
<?php namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Advert extends Model
{
protected $fillable = ['name']; // 移除 'id' —— 主键无需手动填充
/**
* 获取该广告的所有收藏记录
*/
public function favorites()
{
return $this->hasMany(Favorite::class, 'advert_id', 'id');
}
/**
* 判断当前登录用户是否收藏了该广告(运行时轻量判断)
* 注意:仅适用于已预加载 $advert->favorites 的场景,否则触发懒加载
*/
public function isFavoriteOfUser($user)
{
if (!$user) return false;
return $this->favorites->contains('user_id', $user->id);
}
}
⚠️ 注意:isFavoriteOfUser() 方法依赖 $advert->favorites 已加载。若未预加载,应改用数据库级判断(见下文推荐方式)。
✅ 推荐方式:使用 withCount() 预加载 + 条件计数(高效 & 安全)
在控制器中,不查询全部收藏记录,而是统计当前用户对该广告的收藏数(0 或 1),并别名为 is_favorite(语义清晰):
// 在控制器中(如 AdvertController@index)
use Illuminate\Support\Facades\Auth;
public function index()
{
$adverts = Advert::withCount([
'favorites as is_favorite' => function ($query) {
$query->where('user_id', Auth::id());
}
])->get();
return view('adverts', compact('adverts'));
}
此时每个 $advert 实例将携带属性 is_favorite: int(值为 0 或 1),可在 Blade 中直接使用:
@foreach ($adverts as $advert)
<div>
{{ $advert->name }}
<input type="checkbox" disabled>is_favorite ? 'checked' : '' }}>
</div>
@endforeach
✅ 优势:
- 单次 SQL 查询完成所有判断(无 N+1);
- 不加载冗余数据(只查 COUNT(*));
- 支持 Auth::id() 为空时自动跳过条件(Laravel 自动处理);
- 属性名 is_favorite 语义明确,比 favorite_count 更直观。
✅ 进阶:添加作用域(Scope)提升复用性
为便于跨控制器复用,可在 Advert 模型中添加本地作用域:
// app/Models/Advert.php
public function scopeWithUserFavoriteStatus($query, $userId = null)
{
$userId = $userId ?? auth()->id();
return $query->withCount([
'favorites as is_favorite' => fn ($q) => $q->where('user_id', $userId)
]);
}
控制器调用:
$adverts = Advert::withUserFavoriteStatus()->get();
❌ 常见误区纠正
- 错误关系定义:belongsTo(Favorite::class, 'id', 'advert_id') 是反向关系(属于单条 Favorite),无法表达“广告被谁收藏”,且会导致逻辑混乱;
- 滥用 whereHas() 在循环中:在 @foreach 内执行 $advert->favorites()->where(...)->exists() 会引发严重 N+1 查询;
- 忽略未登录用户:务必在 Auth::id() 前校验用户状态(Auth::check()),或使用 auth()->id()(返回 null 而非报错)。
✅ 总结
| 方案 | 查询效率 | 安全性 | 推荐度 |
|---|---|---|---|
| withCount(['favorites as is_favorite' => ...]) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ✅ 强烈推荐 |
| 预加载 favorites + contains() | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⚠️ 仅限小数据量 |
| 循环内 exists() 查询 | ⭐ | ⚠️ | ❌ 禁止用于列表页 |
最终,始终优先使用 withCount 配合条件子查询——它兼顾性能、可读性与 Laravel 最佳实践。











