Laravel5.8
  1. 安装配置及核心内容
  2. 框架基础
  3. 进阶知识
  4. 数据库相关
    1. Laravel - 数据库相关 - 简介与配置
    2. Laravel - 数据库相关 - 原生 SQL 操作
    3. Laravel - 数据库相关 - 查询构造器
    4. Laravel - 数据库相关 - 分页
    5. Laravel - 数据库相关 - 数据迁移
    6. Laravel - 数据库相关 - 数据填充
    7. Laravel - 数据库相关 - redis
    8. Laravel - Eloquent 模型 - 入门
    9. Laravel - Eloquent 模型 - 查询作用域(全局,本地,动态)
    10. Laravel - Eloquent 模型 - 事件与监听方法
    11. Laravel - Eloquent 模型 - 关联关系
    12. Laravel - Eloquent 模型 - 关联查询
    13. Laravel - Eloquent 模型 - 访问器和修改器

Laravel - Eloquent 模型 - 关联查询

程序员日记      2019-09-09

所有 Eloquent 关联关系类型同时也是查询构建器,允许你在最终数据库执行 SQL 之前继续添加条件约束到关联查询上。

示例1.通过关联方法访问关联对象

在模型中定义关联

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model{
    /**
     * 获取指定用户的所有文章
     */
    public function posts()
    {
        return $this->hasMany('App\Post');
    }
}

关联模型的使用

$user = App\User::find(1);
$user->posts()->where('active', 1)->get();


示例2.使用动态属性访问关联对象

$user = App\User::find(1);
foreach ($user->posts as $post) {
    //
}

添加关联模型的约束(查询条件)

获取所有至少有一条评论的文章

$posts = App\Post::has('comments')->get();


获取所有至少有三条评论的文章

$posts = Post::has('comments', '>=', 3)->get();


获取所有至少有一条评论获得投票的文章

$posts = Post::has('comments.votes')->get();


获取至少1条评论内容包含foo的文章

$posts = App\Post::whereHas('comments', function ($query) {
    $query->where('content', 'like', 'foo%');
})->get();


获取至少10条评论内容包含foo的文章

$posts = App\Post::whereHas('comments', function ($query) {
    $query->where('content', 'like', 'foo%');
}, '>=', 10)->get();


获取没有评论的文章

$posts = App\Post::doesntHave('comments')->get();


获取评论内容里没有包含foo的文章

$posts = App\Post::whereDoesntHave('comments', function (Builder $query) {
    $query->where('content', 'like', 'foo%');
})->get();


嵌套关联关系,获取有效作者写的文章

$posts = App\Post::whereDoesntHave('comments.author', function (Builder $query) {
    $query->where('banned', 1);
})->get();

统计关联模型

在不加载关联关系的情况下统计关联结果数目

$posts = App\Post::withCount('comments')->get();
foreach ($posts as $post) {
    echo $post->comments_count;
}

说明

使用 withCount 方法,该方法会放置一个  {relation}_count  字段到结果模型。


像添加约束条件到查询一样来添加多个关联关系的「计数」

$posts = Post::withCount(['votes', 'comments' => function ($query) {
    $query->where('content', 'like', 'foo%');
}])->get();
echo $posts[0]->votes_count;
echo $posts[0]->comments_count;


还可以为关联关系计数结果设置别名

$posts = App\Post::withCount([
    'comments',
    'comments as pending_comments' => function ($query) {
        $query->where('approved', false);
    }
])->get();
echo $posts[0]->comments_count;
echo $posts[0]->pending_comments_count;


如果你将 withCount 和 select 语句组合起来使用,需要在 select 方法之后调用 withCount

$posts = App\Post::select(['title', 'body'])->withCount('comments');
echo $posts[0]->title;
echo $posts[0]->body;
echo $posts[0]->comments_count;

渴求式加载

以属性方式访问 Eloquent 关联关系的时候,关联关系数据是懒惰式加载的

创建一个模型

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Book extends Model
{
    /**
     * 获取写这本书的作者
     */
    public function author()
    {
        return $this->belongsTo('App\Author');
    }
}


懒惰式加载

$books = App\Book::all();
foreach ($books as $book) {
    echo $book->author->name;
}

说明

该循环先执行 1 次查询获取表中的所有书,然后另一个查询获取每一本书的作者,

因此,如果有25本书,要执行26次查询:1次是获取书本身,剩下的25次查询是为每一本书获取其作者。


渴求式加载

$books = App\Book::with('author')->get();
foreach ($books as $book) {
    echo $book->author->name;
}

说明

在该操作中,只执行两次查询即可:

select * from books
select * from authors where id in (1, 2, 3, 4, 5, ...)


渴求式加载多个关联关系

$books = App\Book::with('author', 'publisher')->get();


嵌套的渴求式加载

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


渴求式加载指定字段

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

说明

注:使用这个特性时,id 字段是必须列出的。


带条件约束的渴求式加载

$users = App\User::with(['posts' => function ($query) {
    $query->where('title', 'like', '%first%');
}])->get();


懒惰渴求式加载

$books = App\Book::all();
if ($someCondition) {
    $books->load('author', 'publisher');
}

说明

父模型已经被获取后渴求式加载一个关联关系


设置更多的条件到懒惰渴求式加载

$books->load(['author' => function ($query) {
    $query->orderBy('published_date', 'asc');
}]);

说明

当你需要传递更多条件的时候,可以传递一个闭包。


如果想要在关系管理尚未被加载的情况下加载它,可以使用 loadMissing 方法

public function format(Book $book)
{
    $book->loadMissing('author');
    return [
        'name' => $book->name,
        'author' => $book->author->name
    ];
}

插入及更新关联模型

插入新的 Comment 到 Post 模型

$comment = new App\Comment(['message' => 'A new comment.']);
$post = App\Post::find(1);
$post->comments()->save($comment);

说明

可以从关联关系的 save 方法直接插入 Comment,不用手动设置 Comment 的 post_id 属性


保存多个关联模型

$post = App\Post::find(1);
$post->comments()->saveMany([
    new App\Comment(['message' => 'A new comment.']),
    new App\Comment(['message' => 'Another comment.']),
]);


递归保存模型及关联关系

$post = App\Post::find(1);
$post->comments[0]->message = 'Message';
$post->comments[0]->author->name = 'Author Name';
$post->push();


create 方法创建关联模型

$post = App\Post::find(1);
$comment = $post->comments()->create([
    'message' => 'A new comment.',
]);

说明

该方法接收属性数组、创建模型、然后插入数据库。

save 和 create 的不同之处

save 接收整个 Eloquent 模型实例

create 接收原生 PHP 数组


使用 createMany 方法来创建多个关联模型

$post = App\Post::find(1);
$post->comments()->createMany([
    [
        'message' => 'A new comment.',
    ],
    [
        'message' => 'Another new comment.',
    ],
]);


从属关联关系

更新 belongsTo 关联

$account = App\Account::find(10);
$user->account()->associate($account);
$user->save();

说明

使用 associate 方法,该方法会在子模型设置外键


移除 belongsTo 关联

$user->account()->dissociate();
$user->save();

说明

使用 dissociate 方法。该方法会设置关联关系的外键为 null


默认模型

belongsTo 关联关系允许你在给定关联关系为 null 的情况下定义一个默认的返回模型

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

说明

这种模式叫做空对象模式,使用这种模式的好处是不用在代码中编写大量的判断检查逻辑


通过属性填充默认模型

public function user()
{
    return $this->belongsTo('App\User')->withDefault([
        'name' => 'Guest Author',
    ]);
}

或者

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


多对多关联

我们假定一个用户可能有多个角色,同时一个角色属于多个用户

要通过在连接模型的中间表中插入记录附加角色到用户上,可以使用 attach 方法。

$user = App\User::find(1);
$user->roles()->attach($roleId);

以数组形式传递额外被插入数据到中间表

$user->roles()->attach($roleId, ['expires' => $expires]);

从用户中移除角色,要移除一个多对多关联记录,使用 detach 方法

// 从指定用户中移除角色
$user->roles()->detach($roleId);
// 从指定用户移除所有角色
$user->roles()->detach();

attach 和 detach 还接收数组形式的 ID 作为输入

$user = App\User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([
    1 => ['expires' => $expires],
    2 => ['expires' => $expires]
]);


同步关联

$user->roles()->sync([1, 2, 3]);

说明

sync 方法接收数组形式的 ID 并将其放置到中间表。

任何不在该数组中的 ID 对应记录将会从中间表中移除。

因此,该操作完成后,只有在数组中的 ID 对应记录还存在于中间表。

传递额外的值

$user->roles()->sync([1 => ['expires' => true], 2, 3]);

你不想要删除已存在的ID,可以使用 syncWithoutDetaching 方法

$user->roles()->syncWithoutDetaching([1, 2, 3]);


切换关联

$user->roles()->toggle([1, 2, 3]);

说明

多对多关联还提供了一个 toggle 方法用于切换给定 ID 的附加状态,

如果给定ID当前被附加,则取消附加,类似的,如果当前没有附加,则附加


在中间表上保存额外数据

App\User::find(1)->roles()->save($role, ['expires' => $expires]);

说明

处理多对多关联时,save 方法接收额外中间表属性数组作为第二个参数


更新中间表记录

如果你需要更新中间表中已存在的行,可以使用 updateExistingPivot 方法。

该方法接收中间记录外键和属性数组进行更新。

$user = App\User::find(1);
$user->roles()->updateExistingPivot($roleId, $attributes);

触发父模型时间戳更新

当一个模型属于另外一个时,例如 Comment 属于 Post,子模型更新时父模型的时间戳也被更新将很有用。

例如

当 Comment 模型被更新时,你可能想要”触发“更新其所属模型 Post 的 updated_at 时间戳。

Eloquent 使得这项操作变得简单,只需要添加包含关联关系名称的 touches 属性到子模型即可

<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model{
    /**
     * 要触发的所有关联关系
     */
    protected $touches = ['post'];
    /**
     * 评论所属文章
     */
    public function post()
    {
        return $this->belongsTo('App\Post');
    }
}

现在,当你更新 Comment 时,所属模型 Post 将也会更新其 updated_at 值,从而方便得知何时更新 Post 模型缓存

$comment = App\Comment::find(1);
$comment->text = 'Edit to this comment!';
$comment->save();