Eloquent: 关联模型
简介
数据库中的表经常性的关联其它的表。比如,一个博客文章可以有很多的评论,或者一个订单会关联一个用户。Eloquent 使管理和协作这些关系变的非常的容易,并且支持多种不同类型的关联:
一对一
一对多
多对多
远程一对多
多态关联
多态多对多关联
定义关联
Eloquent 关联可以像定义方法一样在 Eloquent 模型类中进行定义。同时,它就像 Eloquent 模型自身一样也提供了强大的查询生成器。这允许关联模型可以链式的执行查询能力。比如:
$user->posts()->where('active', 1)->get();
但是,在更深入的使用关联之前,让我们先来学习一下如何定义各种类型的关联。
一对一
一对一的关联是最基础的关联。比如,一个 User
模型可能关联一个 Phone
。我们需要在 User
模型上放置一个 phone
方法来定义这种关联。phone
方法应该返回一个基类 Eloquent 模型上 hasOne
方法的结果:
<"htmlcode">$phone = User::find(1)->phone;Eloquent 假定所关联的外键是基于模型的名称的。在这个前提下,
Phone
模型会自动的假定其拥有一个user_id
外键。如果你希望修改这个惯例,你可以传递第二个参数到hasOne
方法中:return $this->hasOne('App\Phone', 'foreign_key');另外,Eloquent 也会假定外键应该在其上层模型上拥有一个匹配的
id
(或者自定义的$primaryKey
)值。换句话说,Eloquent 会查询Phone
记录中的user_id
列所对应的用户的id
列的记录。如果你希望关联使用id
以外的值,你可以传递第三个参数到hasOne
方法来指定自定义的键:return $this->hasOne('App\Phone', 'foreign_key', 'local_key');定义相对的关联
那么,我们可以从我们的
User
中访问Phone
模型。现在,让我们在Phone
模型上定义一个关联,让我们可以从Phone
模型中访问其所属的User
。我们使用belongsTo
方法来定义hasOne
相对的关联:<"htmlcode">/** * Get the user that owns the phone. */ public function user() { return $this->belongsTo('App\User', 'foreign_key'); }如果你的上级模型并没有使用
id
作为主键名,或者你希望下级模型关联一个不同的列。你可以传递第三个参数到belongsTo
方法来指定上级模型表中的自定义键:/** * Get the user that owns the phone. */ public function user() { return $this->belongsTo('App\User', 'foreign_key', 'other_key'); }一对多
一个一对多的关联常常用来定义一个模型拥有其他任意数目的模型。比如,一个博客文章可以拥有很多条评论。就像其他的 Eloquent 关联一样,一对多关联在 Eloquent 模型中通过方法来进行定义:
<"htmlcode">$comments = App\Post::find(1)->comments; foreach ($comments as $comment) { // }当然,由于所有的关联都提供了查询生成器的功能,所以你可以在调用
comments
方法时继续的添加一些限制条件,你可以通过链式的调用进行查询条件的添加:$comments = App\Post::find(1)->comments()->where('title', 'foo')->first();就像
hasOne
方法,你可以通过添加额外的参数到hasMany
方法中来重置外键和主键:return $this->hasMany('App\Comment', 'foreign_key'); return $this->hasMany('App\Comment', 'foreign_key', 'local_key');定义相对的关联
现在我们可以访问文章中所有的评论了,让我们为评论定义一个关联使其可以访问它的上层文章模型。为了定义一个
hasMany
相对的关联,你需要在下层模型中定义一个关联方法并调用belongsTo
方法:<"htmlcode">$comment = App\Comment::find(1); echo $comment->post->title;在上面的例子中,
Eloquent
会尝试从Comment
模型中的post_id
字段检索与其相对应id
的Post
模型。Eloquent
会使用关联模型的蛇形命名和_id
来作为默认的外键。如果Comment
模型的外键不是post_id
,你可以传递一个自定义的键名到belongsTo
方法的第二个参数:/** * Get the post that owns the comment. */ public function post() { return $this->belongsTo('App\Post', 'foreign_key'); }如果上层模型并没有使用 id 作为主键,或者你想在下层模型中关联其他的列,你可以传递第三个参数到 belongsTo 方法中:
/** * Get the post that owns the comment. */ public function post() { return $this->belongsTo('App\Post', 'foreign_key', 'other_key'); }多对多
多对多的关联比
hasOne
和hasMany
关联要稍微复杂一些。假如一个用户拥有多个角色,而角色又可以被其他的用户所共享。比如,多个用户可以拥有管理员的角色。如果定义这种关联,我们需要定义三个数据库表:users
,roles
,和role_user
。role_user
表的命名是以相关联的两个模型数据表来依照字母顺序命名,并且表中包含了user_id
和role_id
列。多对多关联需要编写一个方法调用基础
Eloquent
类belongsToMany
方法。比如,让我们在User
模型中定义一个roles
方法:<"htmlcode">$user = App\User::find(1); foreach ($user->roles as $role) { // }当然,就像其他类型的关联,你可以调用
roles
方法并且链式调用查询条件:$roles = App\User::find(1)->roles()->orderBy('name')->get();就如先前所提到的,Eloquent 会合并两个关联模型并依照字母顺序进行命名。当然你也可以随意的重写这个约定,你可以传递第二个参数到
belongsToMany
方法:return $this->belongsToMany('App\Role', 'role_user');除了自定义合并数据表的名称之外,你也可以通过往
belongsToMany
方法传传递额外参数来自定义数据表里的键的字段名称。第三个参数是你定义在关联中模型外键的名称。第四个参数则是你要合并的模型外键的名称:return $this->belongsToMany('App\Role', 'role_user', 'user_id', 'role_id');定义相对关联
你只需要在相对应的关联模型里放置其他的方法来调用
belongsToMany
方法就可以定义相对关联。继续我们上面的用户角色示例,让我们在Role
模型中定义一个users
方法:<"htmlcode">$user = App\User::find(1); foreach ($user->roles as $role) { echo $role->pivot->created_at; }注意我们取出的每个
Role
对象,都会被自动的分配pivot
属性。这个属性包含了一个代表中间表的模型,并且可以像其他 Eloquent 模型一样被使用。默认的,只有模型的键会被
pivot
对象提供,如果你的中间表包含了额外的属性,你必须在定义关联时指定它们:return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');如果你想要中间表自动维护
created_at
和updated_at
时间戳,你可以在定义关联时使用withTimestamps
方法:return $this->belongsToMany('App\Role')->withTimestamps();通过中间表字段过滤关系
你可以通过在定义关联时使用
wherePrivot
和wherePivotIn
方法来在返回的结果中进行过滤:return $this->belongsToMany('App\Role')->wherePivot('approved', 1); return $this->belongsToMany('App\Role')->wherePivotIn('approved', [1, 2]);远程一对多
远程一对多关联提供了简短便捷的方法通过中间关联件来访问远端的关联。比如,一个
Country
模型应该通过User
模型可以拥有很多的Post
模型。在这个例子中,你可以非常容易的就检索出一个国家中的所有的文章。让我们来看一下定义这些关联所需要的表:countries id - integer name - string users id - integer country_id - integer name - string posts id - integer user_id - integer title - string远端的
posts
并没有包含country_id
列,hasManyThrough
关联可以通过$country->posts
来访问一个国家的文章。为了执行这个查询,Eloquent
会通过中间表users
的country_id
来检索posts
表中用户 ID 相匹配的记录。现在我们已经明确了关联表的结构,那么让我们来在 Country 模型上定义关联:
<"htmlcode">class Country extends Model { public function posts() { return $this->hasManyThrough( 'App\Post', 'App\User', 'country_id', 'user_id', 'id' ); } }多态关联
表结构
多态关联允许一个模型在单个关联中从属一个或多个其它模型。比如,想象一下应用中的用户可以喜欢文章及其评论。如果使用多态关联,那么你就可以使用一个单独的
likes
表来关联这两个场景。首先,让我们确定定义这种关联所需要的表结构:posts id - integer title - string body - text comments id - integer post_id - integer body - text likes id - integer likeable_id - integer likeable_type - string你需要注意到的两个在
likes
表中重要的字段likeable_id
和likeable_type
。likeable_id
字段会包含文章或者评论的 ID 值,而likeable_type
字段会包含其所属的模型的类名。likeable_type
就是当访问likeable
关联时 ORM 用来判断所属的模型是哪个类型。模型结构
接着,让我们检查一下这个关联所需要的模型定义:
<"htmlcode">$post = App\Post::find(1); foreach ($post->likes as $like) { // }你也可以通过在模型上调用提供
morphTo
的方法来获取多态模型其关系所有者。在上面的例子中,指的就是Like
模型中的likeable
方法。所以,我们可以像使用动态属性一样使用方法来进行访问:$like = App\Like::find(1); $likeable = $like->likeable;
Like
模型的likeable
关联将会返回一个Post
或者Comment
实例,这取决于其所属者的类型。自定义多态类型
默认的,Laravel 会使用包完全限定类名来存储所关联模型的类型。比如,上面的例子中
Like
可以属于Post
或者Comment
。默认的likeable_type
应该是App\Post
或者App\Comment
。事实上,你可能希望从你的应用程序的内部结构分离数据库。在这个例子中,你可以定义一个关联的多态映射来指导 Eloquent 使用模型关联的表名称来替代类名:use Illuminate\Database\Eloquent\Relations\Relation; Relation::morphMap([ App\Post::class, App\Comment::class, ]);或者,你可以指定一个自定的字符串与每个模型进行关联:
use Illuminate\Database\Eloquent\Relations\Relation; Relation::morphMap([ 'posts' => App\Post::class, 'likes' => App\Like::class, ]);你可以在你的
AppServiceProvider
或者一个分离的服务提供者的boot
方法中注册你的morphMap
。多态多对多关联
表结构
除了传统的多态关联,你也可以定义多对多的多态关联。比如,一个博客的
Post
和Video
模型应该可以共享一个多态关联的Tag
模型。使用多对多的多态关联可以允许你的博客文章和视频能够共享独特标签的单个列表。首先,让我们来看一下表结构:posts id - integer name - string videos id - integer name - string tags id - integer name - string taggables tag_id - integer taggable_id - integer taggable_type - string模型结构
接着,我们来定义模型中的关联。
Post
和Video
模型将都会包含调用基础Eloquent
类的morphToMany
方法的tags
方法:<"htmlcode"><"htmlcode">$post = App\Post::find(1); foreach ($post->tags as $tag) { // }你也可以通过访问模型中提供执行
morphedByMany
方法的方法来获取关联模型的所属模型。在上面的例子中,就是Tag
模型上的posts
或者videos
方法。所以,你可以像动态属性一样访问这些方法:$tab = App\Tag::find(1); foreach ($tag->videos as $video) { // }关联查询
由于所有的 Eloquent 关联类型都是通过方法定义的,所以你可以调用这些方法来获取所关联的模型的实例而无需实际的执行关联查询。另外,所有的 Eloquent 关联也都提供了查询生成器服务,这允许你可以继续的链式执行查询操作。
比如,想象一下博客系统中
User
模型拥有很多Post
关联的模型:<"htmlcode">$user = App\User::find(1); $user->posts()->where('active', 1)->get();你应该注意到了,你可以在关联中使用任何的查询生成器的方法。
关联方法 Vs. 动态属性
如果你不需要在进行 Eloquent 关联查询时添加额外的约束,你可以简单的像它的属性一样进行访问。比如,我们继续使用
User
和Post
示例模型。我们可以像这样来访问用户的所有文章:$user = App\User::find(1); foreach ($user->posts as $post) { // }动态属性是惰性加载的,这意味着在你实际访问他们之前,其关联数据是不会加载的。正因为如此,开发的时候通常使用预加载来进行加载一些即将用到的关联模型。预加载要求必须加载一个模型的关系,这有效的减少了查询的次数。
查询关联是否存在
当访问一个模型的记录时,你可能会希望基于关联的记录是否存在来对结果进行限制。比如,想象一下你希望获取最少有一条评论的博客文章。你可以传递关联的名称到
has
方法来做这些:// Retrieve all posts that have at least one comment... $posts = App\Post::has('comments')->get();你也可以指定操作符,和数量来进一步定制查询:
// Retrieve all posts that have three or more comments... $posts = Post::has('comments', '>=', 3)->get();你也可以使用 . 语法来构造嵌套的
has
语句。比如,你可以获取所有包含至少一条评论和投票的文章:// Retrieve all posts that hava at least one comment with votes... $posts = Post::has('comments.votes')->get();如果你需要更高的控制,你可以使用
whereHas
和orWhereHas
方法来在has
查询中插入where
子句。这些方法允许你为关联进行自定义的约束查询。比如检查评论的内容:// Retrieve all posts with at least one comment containing words like foo% $posts = Post::whereHas('comments', function ($query) { $query->where('content', 'like', 'foo%'); })->get();统计关联结果
如果你希望统计关联的结果而不实际的加载它们,你可以使用
withCount
方法,这将在你的结果模型中添加{relation}_count
列。比如:$posts = App\Post::withCount('comments')->get(); foreach ($posts as $post) { echo $post->comments_count; }你也可以同时检索多个关联的统计,以及添加查询约束:
$posts = Post::withCount(['votes', 'comments' => function ($query) { $query->where('content', 'like', 'foo%'); }])->get(); echo $posts[0]->votes_count; echo $posts[0]->comments_count;预加载
当通过属性访问 Eloquent 关联时,该关联的数据会被延迟加载。这意味着该关联数据只有在你真实的访问属性时才会进行加载。事实上,Eloquent 可以在上层模型中一次性预加载的。预加载有效避免了 N + 1 的查找问题。要说明 N + 1 查找问题,我们可以来看一个
Author
关联Book
的示例:<"htmlcode">$books = App\Book::all(); foreach ($books as $book) { echo $book->author->name; }这个循环会执行一次查找回所有的书籍,接着每本书会运行一次查找作者的操作。所以,如果我们拥有 25 本书,那么循环将会进行 26 次查询:1 次查询所有的书籍,25 次查询相关书籍的作者。
非常幸运的,我们可以使用预加载来将查询有效的控制在 2 次。当查询时,使用
with
方法来指定关联的预加载:$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, ...)预加载多个关联
有时候你可能需要在一个操作中预加载多个关联,你只需要传递额外的参数到
with
方法中就可以:$books = App\Book::with('author', 'publisher')->get();嵌套的预加载
你可以使用 . 语法来加载嵌套的关联。比如,让我们在一个 Eloquent 语句中一次加载所有书籍的作者以及作者的死人通讯簿:
$books = App\Book::with('author.contacts')->get();预加载约束
有时候你可能希望预加载一些关联,但是也需要对预加载查询指定额外的约束,这里有个示例:
$users = App\User::with(['posts' => function ($query) { $query->where('title', 'like', '%first%'); }])->get();在这个例子中,
Eloquent
会值预加载文章的title
列包含first
单词的记录。当然,你也可以调用其他查询生成器可用的方法:$users = App\User::with(['posts' => function ($query) { $query->orderBy('created_at', 'desc'); }])->get();延迟预加载
有时候你可能需要在上层模型被获取后才预加载其关联。当你需要来动态决定是否加载关联模型时尤其有用:
$books = App\Book::all(); if ($someCondition) { $books->load('author', 'publisher'); }如果你需要对预加载做一些查询约束,你可以传递
Closure
到load
方法:$books->load(['author' => function ($query) { $query->orderBy('published_date', 'asc'); }]);插入关系模型
Save 方法
Eloquent
提供了方便的方法来为模型添加一个关联。比如,也许你需要为Post
模型新增一个Comment
。除了手动的设置Comment
的post_id
属性,你也可以直接在关联模型中调用save
方法来插入Comment
:$comment = new App\Comment(['message' => 'A new comment.']); $post = App\Post::find(1); $post->comments()->save($comment);注意上面我们并没有使用关联模型的动态属性的方式来访问
comments
,而是使用comments
方法的形式来获取关联模型的实例。save
方法会自动的添加相应的post_id
值到新的Comment
模型上。如果你需要一次添加多个关联模型,你需要使用
saveMany
方法:$post = App\Post::find(1); $post->comments()->saveMany([ new App\Comment(['message' => 'A new comment.']), new App\Comment(['message' => 'Another comment.']), ]);Save & 多对多关联
当与多对多关联互动时,
save
方法接收一个中间层表属性的额外参数数组作为第二个参数:App\User::find(1)->roles()->save($role, ['expires' => $expires]);Create 方法
除了
save
和saveMany
方法之外,你也可以使用create
方法,它可以接收属性组成的数组,创建一个模型并且将其存储到数据库。这一次,save
和create
方法的区别是save
接收一个完整的Eloquent
模型实例,而create
接收的是一个原生的 PHParray
:$post = App\Post::find(1); $comment = $post->comments()->create([ 'message' => 'A new comment.', ]);在使用
create
方法之前,你应该确保已经阅读了属性的 批量赋值文档。更新从属关联模型
当更新一个
belongsTo
关联时,你应该使用associate
方法。这个方法会在下层模型中设置外键:$account = App\Account::find(10); $user->account()->associate($account); $user->save();当删除
belongsTo
关联时,你应该使用dissociate
方法,该方法会重置下层模型所关联的外键:$user->account()->dissociate(); $user->save();多对多关联
附加 / 抽离
当使用多对多关联时,Eloquent 提供了一些额外的帮助方法来更方便的管理关联模型。比如,让我们想象一下用户可以有很多角色并且角色可以有很多用户。你可以使用
attach
方法来附加一个角色到用户并且在中间表中加入这条记录:$user = App\User::find(1); $user->roles()->attach($roleId);
当附加关联到模型时,你也可以传递一个含有额外数据的数组来将其添加到中间表中:$user->roles()->attach($roleId, ['expires' => $expires]);当然,有时候你可能需要从用户中删除一个角色。你可以使用
detach
方法来删除多对多关联的记录。datech
方法将从中间表中删除相应的记录。但是,除了中间表,其它两个模型的记录都还会被保留:// Detach a single role from the user... $user->roles()->detach($roleId); // Detach all roles from the user... $user->roles()->detach();为了更加的便捷,
attach
和detach
也可以接收 IDs 所组成的数组作为输入:$user = App\User::find(1); $user->roles()->detach([1, 2, 3]); $user->roles()->attach([1 => ['expires' => $expires], 2, 3]);更新中间表的记录
如果你需要更新中间表中存在的行,你可以使用
updateExistingPivot
方法:$user = App\User::find(1); $user->roles()->updateExistingPivot($roleId, $attributes);便利的同步
你也可以使用
sync
方法来构建多对多的关联。sync
方法接收放置中间表 IDs 所组成的数组。任意 IDs 如果没有在所给定的数组中,那么其将会从中间表中进行删除。所以,在操作完成之后,只有存在于给定数组里的 IDs 才会存在于中间表中:$user->roles()->sync([1, 2, 3]);你也可以同时传递额外的中间表的键值对:
$user->roles()->sync([1 => ['expires' => true], 2, 3]);联动上层模型时间戳
当一个模型
belongsTo
或者belongsToMany
另外一个模型时,比如Comment
从属于Post
,这对下层模型更新时同时要求更新上层模型的时间戳时很有帮助。比如,当Comment
模型更新了,你想要自动的更新其所属的Post
模型的updated_at
时间戳。Eloquent
使之变的非常容易。你只需要在下层模型中添加一个touches
属性来包含关联的名称就可以了:<"htmlcode">$comment = App\Comment::find(1); $comment->text = 'Edit to this comment!'; $comment->save();以上就是laravel学习教程之关联模型的全部内容,希望对大家学习php有所帮助。
相思资源网 Design By www.200059.com广告合作:本站广告合作请联系QQ:858582 申请时备注:广告合作(否则不回)
免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com相思资源网 Design By www.200059.com暂无laravel学习教程之关联模型的评论...