2. 模型关联

未匹配的标注

在 Eloquent 关系中使用 OrderBy

您可以在关联关系中直接指定orderBy()。

public function products()
{
    return $this->hasMany(Product::class);
}

public function productsByName()
{
    return $this->hasMany(Product::class)->orderBy('name');
}

在 Eloquent 关系中添加条件

假如你经常在模型关联关系中添加某些相同的 where 条件,可以创建一个独立方法。

Model:

public function comments()
{
    return $this->hasMany(Comment::class);
}

public function approved_comments()
{
    return $this->hasMany(Comment::class)->where('approved', 1);
}

DB 原生查询: havingRaw()

你可以在很多地方使用原始数据库查询,比如在 groupBy() 后面调用 havingRaw()

Product::groupBy('category_id')->havingRaw('COUNT(*) > 1')->get();

Eloquent 使用 has() 实现多层调用查询

你可以在关联关系查询中使用 has() 实现两层关联查询。

// Author -> hasMany(Book::class);
// Book -> hasMany(Rating::class);
$authors = Author::has('books.ratings')->get();

一对多关系中获取符合指定数量的信息

在一对多关系中,你可以通过条件过滤,获取符合的数据。比如需要查找有哪些作者出版书的数量大于5。

// Author -> hasMany(Book::class)
$authors = Author::has('books', '>', 5)->get();

返回默认模型

你可以在 belongsTo 关系中设置返回一个默认的模型,从而避免类似于使用 {{ $post->user->name }}$post->user 不存在的时候,引起的致命的错误。

public function user()
{
    return $this->belongsTo('App\User')->withDefault();
}

一对多关系中一次创建多条关联数据

在一对多关系中,你可以使用 saveMany() 通过一次提交,保存多条关联数据。

$post = Post::find(1);
$post->comments()->saveMany([
    new Comment(['message' => 'First comment']),
    new Comment(['message' => 'Second comment']),
]);

多层级渴求式加载

在 Laravel 中,你可以在一条语句中渴求式加载多个层级,在这个例子中,我们不仅加载作者关系,而且还加载作者模型上的国家关系。

$users = App\Book::with('author.country')->get();

渴求式加载特定字段

你可以在 Laravel 中渴求式加载并指定关联中的特定字段。

$users = App\Book::with('author:id,name')->get();

你同样可以在深层级中这样做,如第二层级关系:

$users = App\Book::with('author.country:id,name')->get();

轻松触及父级 updated_at

如果你想更新一条数据同时更新它父级关联的 updated_at 字段 (例如:你添加一条帖子评论,想同时更新帖子的 posts.updated_at),只需要在子模型中使用 $touches = ['post']; 属性。

class Comment extends Model
{
    protected $touches = ['post'];
}

一直检查关联是否存在

永远不要在不检查关联是否存在时使用 $model->relationship->field

它可能因为任何原因,如在你的代码之外,被别人的队列任务等等被删除。用 if-else,或者在 Blade 模板中 {{$model->relationship->field ? ? '' }},或者 {{optional($model->relationship)->field }} 。在 php8 中,你甚至可以使用null安全操作符{{ $model->relationship?->field) }}

使用 withCount() 来统计子关联记录数

如果你有 hasMany() 的关联,并且你想统计子关联记录的条数,不要写一个特殊的查询。例如,如果你的用户模型上有帖子和评论,使用 withCount()

public function index()
{
    $users = User::withCount(['posts', 'comments'])->get();
    return view('users', compact('users'));
}

同时,在 Blade 文件中,您可以通过使用 {relationship}_count 属性获得这些数量:

@foreach ($users as $user)
<tr>
    <td>{{ $user->name }}</td>
    <td class="text-center">{{ $user->posts_count }}</td>
    <td class="text-center">{{ $user->comments_count }}</td>
</tr>
@endforeach

也可以按照这些统计字段进行排序:

User::withCount('comments')->orderBy('comments_count', 'desc')->get(); 

关联关系中过滤查询

假如您想加载关联关系的数据,同时需要指定一些限制或者排序的闭包函数。例如,您想获取人口最多的前3座城市信息,可以按照如下方式实现:

$countries = Country::with(['cities' => function($query) {
    $query->orderBy('population', 'desc');
    $query->take(3);
}])->get();

动态预加载相关模型

您不仅可以实现对关联模型的实时预加载,还可以根据情况动态设置某些关联关系,需要在模型初始化方法中处理:

class ProductTag extends Model
{
    protected $with = ['product'];

    public function __construct() {
        parent::__construct();
        $this->with = ['product'];

        if (auth()->check()) {
            $this->with[] = 'user';
        }
    }
}

使用 hasMany 代替 belongsTo

在关联关系中,如果创建子关系的记录中需要用到父关系的 ID ,那么使用 hasMany 比使用 belongsTo 更简洁。

// if Post -> belongsTo(User), and User -> hasMany(Post)...
// Then instead of passing user_id...
Post::create([
    'user_id' => auth()->id(),
    'title' => request()->input('title'),
    'post_text' => request()->input('post_text'),
]);

// Do this
auth()->user()->posts()->create([
    'title' => request()->input('title'),
    'post_text' => request()->input('post_text'),
]);

自定义 pivot 属性名称

如果你想要重命名「pivot」并用其他的什么方式来调用关系,你可以在你的关系声明中使用 ->as('name') 来为关系取名。

模型 Model:

public function podcasts() {
    return $this->belongsToMany('App\Podcast')
        ->as('subscription')
        ->withTimestamps();
}

控制器 Controller:

$podcasts = $user->podcasts();
foreach ($podcasts as $podcast) {
    // instead of $podcast->pivot->created_at ...
    echo $podcast->subscription->created_at;
}

仅用一行代码更新归属关系

如果有一个 belongsTo() 关系,你可以在仅仅一条语句中更新这个 Elquent 关系:

// if Project -> belongsTo(User::class)
$project->user->update(['email' => 'some@gmail.com']); 

Laravel 7+ 的外键

从 Laravel 7 开始,你不需要在迁移(migration)中为一些关系字段写两行代码 —— 一行是字段,一行是外键。你可以使用 foreignId() 方法。

// Laravel 7 之前
Schema::table('posts', function (Blueprint $table)) {
    $table->unsignedBigInteger('user_id');
    $table->foreign('user_id')->references('id')->on('users');
}

// 从 Laravel 7 开始
Schema::table('posts', function (Blueprint $table)) {
    $table->foreignId('user_id')->constrained();
}

// 或者你的字段不同于表中的引用的
Schema::table('posts', function (Blueprint $table)) {
    $table->foreignId('created_by_id')->constrained('users', 'column');
}

两种 「whereHas」 组合使用

在 Eloquent 中, 你可以在同一条语句中使用 whereHas()orDoesntHave()

User::whereHas('roles', function($query) {
    $query->where('id', 1);
})
->orDoesntHave('roles')
->get();

检查关系方法是否已经存在

如果你的 Eloquent 关系名是动态的,那么你需要检查项目中是否存在相同名称的关系。你可以使用这个PHP方法 method_exists($object, $methodName)

$user = User::first();
if (method_exists($user, 'roles')) {
    // 使用 $user->roles()-> 做其它事情...
}

获取中间表中的关联关系数据

在多对多关系中,您定义的中间表里面可能会包含扩展字段,甚至可能包含其它的关联关系。

下面生成一个中间表模型:

php artisan make:model RoleUser --pivot

然后,给 belongsToMany() 指定 ->using() 方法。下面就是见证奇迹的时刻:

// in app/Models/User.php
public function roles()
{
    return $this->belongsToMany(Role::class)
        ->using(RoleUser::class)
        ->withPivot(['team_id']);
}

// app/Models/RoleUser.php: 注意继承的是 Pivot, 不是 Model
use Illuminate\Database\Eloquent\Relations\Pivot;

class RoleUser extends Pivot
{
    public function team()
    {
        return $this->belongsTo(Team::class);
    }
}

// 在控制器里面就可以直接使用如下方式获取中间表 RoleUser 的 Team 信息:
$firstTeam = auth()->user()->roles()->first()->pivot->team->name;

便捷获取一对多关系中子集的数量

除了可以使用 Eloquent 中的 withCount() 方法统计子集数量外,还可以直接用 loadCount() 更加便捷和快速获取:

// if your Book hasMany Reviews...
$book = App\Book::first();

$book->loadCount('reviews');
// 使用 $book->reviews_count 获取 Reviews 数量;

// 还可以在 loadCount 中添加额外的查询条件
$book->loadCount(['reviews' => function ($query) {
    $query->where('rating', 5);
}]);

对关联模型数据进行随机排序

您可以使用 inRandomOrder() 对 Eloquent 的查询结果进行随机排序,同时也可以作用于关联关系中,实现关联数据的随机排序。

// If you have a quiz and want to randomize questions...

// 1. 获取随机答案:
$questions = Question::inRandomOrder()->get();

// 2. 获取随机问题的随机答案:
$questions = Question::with(['answers' => function($q) {
    $q->inRandomOrder();
}])->inRandomOrder()->get();

过滤一对多关联

通过我项目中的一个代码例子,展示了过滤一对多关系的可能性。
TagType -> hasMany tags -> hasMany examples
如果你想查询所有的标签类型,伴随他们的标签,但只包含有实例的标签,并按照实例数量倒序。

$tag_types = TagType::with(['tags' => function ($query) {
    $query->has('examples')
        ->withCount('examples')
        ->orderBy('examples_count', 'desc');
    }])->get();

通过中间表字段过滤多对多关联

如果你有一个多对多关联,你可以在中间表中添加一个额外字段,这样你可以在查询列表时用它排序。

class Tournament extends Model
{
    public function countries()
    {
        return $this->belongsToMany(Country::class)->withPivot(['position']);
    }
}
class TournamentsController extends Controller

public function whatever_method() {
    $tournaments = Tournament::with(['countries' => function($query) {
            $query->orderBy('position');
        }])->latest()->get();
}

whereHas 的一个更简短的方法

在 Laravel 8.57 中发布: 通过包含一个简单条件的简短方法来写 whereHas()

// 以前
User::whereHas('posts', function ($query) {
    $query->where('published_at', '>', now());
})->get();

// 现在
User::whereRelation('posts', 'published_at', '>', now())->get();

提取一个重复回调作为变量

如果你有一个重复使用的回调函数,你可以提取它作为变量。

// 你有一个很长的包含重复的回调函数
$results = Model::with('relationships')
    ->whereHas('relationships', function($query) use ($var1, $var2) {
        return $query->where('field1', $var1)->where('field2', $var2);
    })
    ->withCount('relationships', function($query) use ($var1, $var2) {
        return $query->where('field1', $var1)->where('field2', $var2);
    })
    ->get();

// 你可以提取它作为变量
$callback = function($query) use ($var1, $var2) {
        return $query->where('field1', $var1)->where('field2', $var2);
    });
$results = Model::with('relationships')
    ->whereHas('relationships', $callback)
    ->withCount('relationships', $callback)
    ->get();

你可以为你的模型关联添加条件

class User
{
    public function posts()
    {
        return $this->hasMany(Post::class);
    }

    // 添加一个获取器
    public function getPublishedPostsAttribute()
    {
        return $this->posts->filter(fn ($post) => $post->published);
    }

    // 添加一个关联
    public function publishedPosts()
    {
        return $this->hasMany(Post::class)->where('published', true);
    }
}

感谢 @anwar_nairi 提供

新的 Eloquent 查询构建器方法whereBelongsTo()

Laravel 8.63.0 版本带有一个新的 Eloquent 查询构建器方法 whereBelongsTo()
这允许你从你的查询中删除 BelongsTo 外键名称,并使用关联方法替代(该方法会根据类名自动确定关联与外键,也可以添加第二个参数手动关联)。

// 以前:
$query->where('author_id', $author->id)

// 现在:
$query->whereBelongsTo($author)

// 轻松添加更多的过滤功能:
Post::query()
    ->whereBelongsTo($author)
    ->whereBelongsTo($cateogry)
    ->whereBelongsTo($section)
    ->get();

// 指定一个自定义的关系:
$query->whereBelongsTo($author, 'author')

感谢 @danjharrin 提供

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
Summer
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
贡献者:7
讨论数量: 0
发起讨论 只看当前版本


暂无话题~