数据库:关系
Introduction
数据库表通常彼此相关。例如,一篇博文可能有很多评论,或者一个订单可能与下订单的用户相关。 Winter 使管理和处理这些关系变得容易,并支持多种不同类型的关系。
NOTE: 如果您在查询中选择特定的列并且还想加载关系,则需要确保包含键控数据的列(即
id
,foreign_key
等)包含在您的选择语句中。否则,温特无法连接关系。
定义关系
模型关系被定义为模型类的属性。定义关系的示例:
class User extends Model
{
public $hasMany = [
'posts' => 'Acme\Blog\Models\Post'
]
}
人际关系就像模特本身一样,也具有强大的作用查询构建器,将关系作为函数访问提供了强大的方法链接和查询功能。例如:
$user->posts()->where('is_active', true)->get();
也可以将关系作为属性访问:
$user->posts;
NOTE:所有关系查询都有启用内存缓存 默认情况下。这
load($relation)
方法不会强制缓存刷新。要重新加载内存缓存,请使用reloadRelations()
或者reload()
模型对象上的方法。
详细定义
每个定义都可以是一个数组,其中键是关系名称,值是详细信息数组。 detail 数组的第一个值始终是相关的模型类名称,所有其他值都是必须具有键名的参数。
public $hasMany = [
'posts' => ['Acme\Blog\Models\Post', 'delete' => true]
];
以下是可用于所有关系的参数:
Argument | Description |
---|---|
order |
多条记录的排序顺序。 |
conditions |
使用原始 where 查询语句过滤关系。 |
scope |
使用提供的范围方法过滤关系。 |
push |
如果设置为 false,则不会通过以下方式保存此关系push ,默认值:true。 |
delete |
如果设置为true,如果主模型被删除或关系被破坏,相关模型将被删除,默认值:false。 |
detach |
如果设置为 false,如果主模型被删除或关系被破坏,相关模型将不会自动分离。被使用belongsToMany 仅关系,默认值:true。 |
count |
如果设置为 true,则结果包含count column only,用于统计关系,默认:false。 |
示例过滤器使用order 和conditions:
public $belongsToMany = [
'categories' => [
'Acme\Blog\Models\Category',
'order' => 'name desc',
'conditions' => 'is_active = 1'
]
];
示例过滤器使用scope:
class Post extends Model
{
public $belongsToMany = [
'categories' => [
'Acme\Blog\Models\Category',
'scope' => 'isActive'
]
];
}
class Category extends Model
{
public function scopeIsActive($query)
{
return $query->where('is_active', true)->orderBy('name', 'desc');
}
}
示例过滤器使用count:
public $belongsToMany = [
'users' => ['Backend\Models\User'],
'users_count' => ['Backend\Models\User', 'count' => true]
];
关系类型
以下关系类型可用:
一对一
一对一关系是一种非常基本的关系。例如,一个User
模型可能与一个相关联Phone
.为了定义这种关系,我们添加了一个phone
进入$hasOne
上的财产User
模型。
<?php namespace Acme\Blog\Models;
use Model;
class User extends Model
{
public $hasOne = [
'phone' => 'Acme\Blog\Models\Phone'
];
}
定义关系后,我们可以使用同名的模型属性检索相关记录。这些属性是动态的,允许您像访问模型上的常规属性一样访问它们:
$phone = User::find(1)->phone;
该模型根据模型名称假定关系的外键。在这种情况下,Phone
模型被自动假设为user_id
外键。如果你想覆盖这个约定,你可以通过key
参数定义:
public $hasOne = [
'phone' => ['Acme\Blog\Models\Phone', 'key' => 'my_user_id']
];
此外,该模型假定外键的值应与id
父项的列。换句话说,它会寻找用户的价值id
列中的user_id
列的Phone
记录。如果您希望关系使用的值不是id
,你可以通过otherKey
参数定义:
public $hasOne = [
'phone' => ['Acme\Blog\Models\Phone', 'key' => 'my_user_id', 'otherKey' => 'my_id']
];
定义关系的逆
现在我们可以访问Phone
我们的模型User
.让我们反其道而行之,定义一个关系Phone
让我们访问的模型User
拥有电话的人。我们可以定义 a 的逆hasOne
关系使用$belongsTo
财产:
class Phone extends Model
{
public $belongsTo = [
'user' => 'Acme\Blog\Models\User'
];
}
在上面的示例中,模型将尝试匹配user_id
来自Phone
模型到id
在User
模型。它通过检查关系定义的名称并在名称后加上后缀来确定默认的外键名称_id
.但是,如果外键Phone
模型不是user_id
,您可以使用key
定义参数:
public $belongsTo = [
'user' => ['Acme\Blog\Models\User', 'key' => 'my_user_id']
];
如果您的父模型不使用id
作为其主键,或者您希望将子模型连接到不同的列,您可以通过otherKey
指定父表自定义键的定义参数:
public $belongsTo = [
'user' => ['Acme\Blog\Models\User', 'key' => 'my_user_id', 'otherKey' => 'my_id']
];
默认型号
这belongsTo
relationship 允许您定义一个默认模型,如果给定的关系是null
.这种模式通常被称为空对象模式 并且可以帮助删除代码中的条件检查。在下面的示例中,user
关系将返回一个空Acme\Blog\Models\User
如果没有模型user
附在帖子上:
public $belongsTo = [
'user' => ['Acme\Blog\Models\User', 'default' => true]
];
要使用属性填充默认模型,您可以将数组传递给default
范围:
public $belongsTo = [
'user' => [
'Acme\Blog\Models\User',
'default' => ['name' => 'Guest']
]
];
一对多
一对多关系用于定义单个模型拥有任意数量的其他模型的关系。例如,一篇博文可能有无限数量的评论。与所有其他关系一样,一对多关系的定义是在$hasMany
您模型上的属性:
class Post extends Model
{
public $hasMany = [
'comments' => 'Acme\Blog\Models\Comment'
];
}
请记住,模型会自动确定正确的外键列Comment
模型。按照惯例,它将采用拥有模型的“snake case”名称并将其后缀为_id
.所以对于这个例子,我们可以假设外键Comment
模型是post_id
.
一旦定义了关系,我们就可以通过访问comments
财产。请记住,由于模型提供了“动态属性”,我们可以访问关系,就好像它们被定义为模型的属性一样:
$comments = Post::find(1)->comments;
foreach ($comments as $comment) {
//
}
当然,由于所有关系也充当查询构建器,您可以通过调用comments
方法并继续将条件链接到查询:
$comments = Post::find(1)->comments()->where('title', 'foo')->first();
像hasOne
关系,您还可以通过传递key
和otherKey
分别定义参数:
public $hasMany = [
'comments' => ['Acme\Blog\Models\Comment', 'key' => 'my_post_id', 'otherKey' => 'my_id']
];
定义关系的逆
现在我们可以访问帖子的所有评论,让我们定义一个关系以允许评论访问其父帖子。定义 a 的逆hasMany
关系,定义$belongsTo
子模型的属性:
class Comment extends Model
{
public $belongsTo = [
'post' => 'Acme\Blog\Models\Post'
];
}
一旦定义了关系,我们就可以检索Post
一个模型Comment
通过访问post
“动态属性”:
$comment = Comment::find(1);
echo $comment->post->title;
在上面的示例中,模型将尝试匹配post_id
来自Comment
模型到id
在Post
模型。它通过检查关系的名称并将其添加后缀来确定默认的外键名称_id
.但是,如果外键Comment
模型不是post_id
,您可以使用key
范围:
public $belongsTo = [
'post' => ['Acme\Blog\Models\Post', 'key' => 'my_post_id']
];
如果您的父模型不使用id
作为其主键,或者您希望将子模型连接到不同的列,您可以通过otherKey
指定父表自定义键的定义参数:
public $belongsTo = [
'post' => ['Acme\Blog\Models\Post', 'key' => 'my_post_id', 'otherKey' => 'my_id']
];
多对多
多对多关系比hasOne
和hasMany
关系。这种关系的一个示例是具有多个角色的用户,其中这些角色也由其他用户共享。例如,许多用户可能具有“管理员”角色。要定义这种关系,需要三个数据库表:users
,roles
, 和role_user
.这role_user
表是从相关模型名称的字母顺序派生的,并包含user_id
和role_id
列。
下面是一个例子,显示数据库表结构 用于创建连接表。
Schema::create('role_user', function($table)
{
$table->integer('user_id')->unsigned();
$table->integer('role_id')->unsigned();
$table->primary(['user_id', 'role_id']);
});
多对多关系被定义为添加一个条目到$belongsToMany
模型类的属性。例如,让我们定义roles
我们的方法User
模型:
class User extends Model
{
public $belongsToMany = [
'roles' => 'Acme\Blog\Models\Role'
];
}
定义关系后,您可以使用roles
动态属性:
$user = User::find(1);
foreach ($user->roles as $role) {
//
}
当然,像所有其他关系类型一样,您可以调用roles
继续将查询约束链接到关系的方法:
$roles = User::find(1)->roles()->orderBy('name')->get();
如前所述,为了确定关系连接表的表名,模型将按字母顺序连接两个相关的模型名称。但是,您可以自由地覆盖此约定。你可以通过传递table
参数到belongsToMany
定义:
public $belongsToMany = [
'roles' => ['Acme\Blog\Models\Role', 'table' => 'acme_blog_role_user']
];
除了自定义连接表的名称外,您还可以通过传递额外的参数来自定义表上键的列名belongsToMany
定义。这key
参数是您在其上定义关系的模型的外键名称,而otherKey
参数是您要加入的模型的外键名称:
public $belongsToMany = [
'roles' => [
'Acme\Blog\Models\Role',
'table' => 'acme_blog_role_user',
'key' => 'my_user_id',
'otherKey' => 'my_role_id'
]
];
定义关系的逆
要定义多对多关系的逆向,您只需放置另一个$belongsToMany
相关模型上的属性。为了继续我们的用户角色示例,让我们定义users
上的关系Role
模型:
class Role extends Model
{
public $belongsToMany = [
'users' => 'Acme\Blog\Models\User'
];
}
如您所见,该关系的定义与它的完全相同User
对方,除了简单地引用Acme\Blog\Models\User
模型。由于我们正在重用$belongsToMany
属性,在定义多对多关系的反向时,所有常用的表和键自定义选项都可用。
检索中间表列
正如您已经了解到的,处理多对多关系需要存在中间连接表。模型提供了一些与此表交互的非常有用的方法。例如,假设我们的User
对象有很多Role
与之相关的对象。访问此关系后,我们可以使用pivot
模型上的属性:
$user = User::find(1);
foreach ($user->roles as $role) {
echo $role->pivot->created_at;
}
请注意,每个Role
我们检索的模型会自动分配一个pivot
属性。该属性包含表示中间表的模型,可以像任何其他模型一样使用。
默认情况下,只有模型键会出现在pivot
目的。如果您的数据透视表包含额外的属性,您必须在定义关系时指定它们:
public $belongsToMany = [
'roles' => [
'Acme\Blog\Models\Role',
'pivot' => ['column1', 'column2']
]
];
如果您希望数据透视表自动维护created_at
和updated_at
时间戳,使用timestamps
关系定义的参数:
public $belongsToMany = [
'roles' => ['Acme\Blog\Models\Role', 'timestamps' => true]
];
这些是支持的参数belongsToMany
关系:
Argument | Description |
---|---|
table |
连接表的名称。 |
key |
定义模型的键列名称(在数据透视表内)。默认值由模型名称和_id 后缀,即user_id |
parentKey |
定义模型的键列名称(在定义模型表中)。默认值:id |
otherKey |
相关模型的键列名称(在数据透视表内)。默认值由模型名称和_id 后缀,即role_id |
relatedKey |
相关模型的键列名称(在相关模型表中)。默认值:id |
pivot |
在连接表中找到的数据透视列数组,属性可通过$model->pivot . |
pivotModel |
指定访问枢轴关系时要返回的自定义模型类。默认为Winter\Storm\Database\Pivot .笔记:pivot 仍然需要定义以便在任何数据库查询中包含数据透视列。 |
timestamps |
如果为真,则连接表应包含created_at 和updated_at 列。默认值:假 |
有很多通过
has-many-through 关系为通过中间关系访问远距离关系提供了一个方便的捷径。例如,一个Country
模型可能有很多Post
通过中间模型User
模型。在此示例中,您可以轻松收集特定国家/地区的所有博客文章。让我们看一下定义这种关系所需的表:
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
.为了执行这个查询,模型检查了country_id
在中间users
桌子。找到匹配的用户 ID 后,它们用于查询posts
桌子。
现在我们已经检查了关系的表结构,让我们在Country
模型:
class Country extends Model
{
public $hasManyThrough = [
'posts' => [
'Acme\Blog\Models\Post',
'through' => 'Acme\Blog\Models\User'
],
];
}
第一个参数传递给$hasManyThrough
relation 是我们希望访问的最终模型的名称,而through
参数是中间模型的名称。
执行关系查询时将使用典型的外键约定。如果你想自定义关系的键,你可以将它们作为key
,otherKey
和throughKey
参数到$hasManyThrough
定义。这key
参数是中间模型上的外键名称,throughKey
参数是最终模型的外键名称,而otherKey
是本地密钥。
public $hasManyThrough = [
'posts' => [
'Acme\Blog\Models\Post',
'key' => 'my_country_id',
'through' => 'Acme\Blog\Models\User',
'throughKey' => 'my_user_id',
'otherKey' => 'my_id'
],
];
有一个通过
has-one-through 关系通过单个中间关系链接模型。例如,如果每个供应商都有一个用户,并且每个用户都与一个用户历史记录相关联,那么供应商模型可以通过用户访问用户的历史记录。让我们看一下定义此关系所需的数据库表:
users
id - integer
supplier_id - integer
suppliers
id - integer
history
id - integer
user_id - integer
虽然history
表不包含supplier_id
专栏,hasOneThrough
关系可以提供对供应商模型的用户历史的访问。现在我们已经检查了关系的表结构,让我们在Supplier
模型:
class Supplier extends Model
{
public $hasOneThrough = [
'userHistory' => [
'Acme\Supplies\Model\History',
'through' => 'Acme\Supplies\Model\User'
],
];
}
传递给的第一个数组参数$hasOneThrough
属性是我们希望访问的最终模型的名称,而through
key 是中间模型的名称。
执行关系查询时将使用典型的外键约定。如果你想自定义关系的键,你可以将它们作为key
,otherKey
和throughKey
参数到$hasManyThrough
定义。这key
参数是中间模型上的外键名称,throughKey
参数是最终模型的外键名称,而otherKey
是本地密钥。
public $hasOneThrough = [
'userHistory' => [
'Acme\Supplies\Model\History',
'key' => 'supplier_id',
'through' => 'Acme\Supplies\Model\User'
'throughKey' => 'user_id',
'otherKey' => 'id'
],
];
多态关系
多态关系允许一个模型在单个关联上属于多个其他模型。
一对一
表结构
一对一的多态关系类似于简单的一对一关系;但是,目标模型可以属于单个关联上的多个模型类型。例如,假设您想为员工和产品存储照片。使用多态关系,您可以使用单个photos
这两种情况的表。首先,让我们检查一下建立这种关系所需的表结构:
staff
id - integer
name - string
products
id - integer
price - integer
photos
id - integer
path - string
imageable_id - integer
imageable_type - string
需要注意的两个重要列是imageable_id
和imageable_type
上的列photos
桌子。这imageable_id
列将包含拥有人员或产品的 ID 值,而imageable_type
列将包含所属模型的类名。这imageable_type
列是 ORM 如何确定在访问时返回哪种“类型”的拥有模型imageable
关系。
模型结构
接下来,让我们检查建立这种关系所需的模型定义:
class Photo extends Model
{
public $morphTo = [
'imageable' => []
];
}
class Staff extends Model
{
public $morphOne = [
'photo' => ['Acme\Blog\Models\Photo', 'name' => 'imageable']
];
}
class Product extends Model
{
public $morphOne = [
'photo' => ['Acme\Blog\Models\Photo', 'name' => 'imageable']
];
}
检索多态关系
定义数据库表和模型后,您可以通过模型访问关系。例如,要访问工作人员的照片,我们可以简单地使用photo
动态属性:
$staff = Staff::find(1);
$photo = $staff->photo;
您还可以通过访问多态关系的名称从多态模型中检索多态关系的所有者morphTo
关系。在我们的例子中,这是imageable
上的定义Photo
模型。所以,我们将把它作为一个动态属性来访问:
$photo = Photo::find(1);
$imageable = $photo->imageable;
这imageable
上的关系Photo
模型将返回Staff
或者Product
例如,取决于哪种类型的模型拥有照片。
一对多
表结构
一对多多态关系类似于简单的一对多关系;但是,目标模型可以属于单个关联上的多个模型类型。例如,假设您的应用程序的用户可以对帖子和视频进行“评论”。使用多态关系,您可以使用单个comments
这两种情况的表。首先,让我们检查一下建立这种关系所需的表结构:
posts
id - integer
title - string
body - text
videos
id - integer
title - string
url - string
comments
id - integer
body - text
commentable_id - integer
commentable_type - string
模型结构
接下来,让我们检查建立这种关系所需的模型定义:
class Comment extends Model
{
public $morphTo = [
'commentable' => []
];
}
class Post extends Model
{
public $morphMany = [
'comments' => ['Acme\Blog\Models\Comment', 'name' => 'commentable']
];
}
class Product extends Model
{
public $morphMany = [
'comments' => ['Acme\Blog\Models\Comment', 'name' => 'commentable']
];
}
检索关系
定义数据库表和模型后,您可以通过模型访问关系。例如,要访问帖子的所有评论,我们可以使用comments
动态属性:
$post = Author\Plugin\Models\Post::find(1);
foreach ($post->comments as $comment) {
//
}
您还可以通过访问多态关系的名称从多态模型中检索多态关系的所有者morphTo
关系。在我们的例子中,这是commentable
上的定义Comment
模型。所以,我们将把它作为一个动态属性来访问:
$comment = Author\Plugin\Models\Comment::find(1);
$commentable = $comment->commentable;
这commentable
上的关系Comment
模型将返回Post
或者Video
例如,取决于哪种类型的模型拥有评论。
您还可以通过使用模型的名称设置属性来更新相关模型的所有者morphTo
关系,在这种情况下commentable
.
$comment = Author\Plugin\Models\Comment::find(1);
$video = Author\Plugin\Models\Video::find(1);
$comment->commentable = $video;
$comment->save();
多对多
表结构
除了“一对一”和“一对多”关系之外,您还可以定义“多对多”多态关系。例如,一个博客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
模型都有tags
中定义的关系$morphToMany
基本模型类的属性:
class Post extends Model
{
public $morphToMany = [
'tags' => ['Acme\Blog\Models\Tag', 'name' => 'taggable']
];
}
定义关系的逆
接下来,在Tag
模型,您应该为它的每个相关模型定义一个关系。所以,对于这个例子,我们将定义一个posts
关系和一个videos
关系:
class Tag extends Model
{
public $morphedByMany = [
'posts' => ['Acme\Blog\Models\Post', 'name' => 'taggable'],
'videos' => ['Acme\Blog\Models\Video', 'name' => 'taggable']
];
}
检索关系
定义数据库表和模型后,您可以通过模型访问关系。例如,要访问帖子的所有标签,您可以简单地使用tags
动态属性:
$post = Post::find(1);
foreach ($post->tags as $tag) {
//
}
您还可以通过访问在多态模型中定义的关系的名称来检索多态关系的所有者$morphedByMany
财产。在我们的例子中,这是posts
或者videos
上的方法Tag
模型。因此,您将访问这些关系作为动态属性:
$tag = Tag::find(1);
foreach ($tag->videos as $video) {
//
}
自定义多态类型
默认情况下,完全限定的类名用于存储相关的模型类型。例如,给定上面的示例,其中Photo
可能属于Staff
或Product
, 默认imageable_type
值是Acme\Blog\Models\Staff
或者Acme\Blog\Models\Product
分别。
使用自定义多态类型可以让您将数据库与应用程序的内部结构分离。您可以定义一个关系“morph map”来为每个模型提供一个自定义名称而不是类名:
use Winter\Storm\Database\Relations\Relation;
Relation::morphMap([
'staff' => 'Acme\Blog\Models\Staff',
'product' => 'Acme\Blog\Models\Product',
]);
最常见的注册地点morphMap
在里面boot
一个方法插件注册文件.
查询关系
由于所有类型的模型关系都可以通过函数调用,您可以调用这些函数来获取关系的实例,而无需实际执行关系查询。此外,所有类型的关系也作为查询构建器,允许您在最终对数据库执行 SQL 之前继续将约束链接到关系查询。
例如,想象一个博客系统,其中User
模型有很多关联Post
楷模:
class User extends Model
{
public $hasMany = [
'posts' => ['Acme\Blog\Models\Post']
];
}
通过关系方式访问
您可以查询posts 关系并使用posts
方法。这使您能够链接任何查询生成器 关系的方法。
$user = User::find(1);
$posts = $user->posts()->where('is_active', 1)->get();
$post = $user->posts()->first();
通过动态属性访问
如果您不需要向关系查询添加额外的约束,您可以简单地访问关系,就好像它是一个属性一样。例如,继续使用我们的User
和Post
示例模型,我们可以使用$user->posts
财产代替。
$user = User::find(1);
foreach ($user->posts as $post) {
// ...
}
动态属性是“延迟加载”,这意味着它们只会在您实际访问它们时加载它们的关系数据。正因为如此,开发人员经常使用急切加载 预加载他们知道将在加载模型后访问的关系。预加载显着减少了加载模型关系所必须执行的 SQL 查询。
查询关系是否存在
当访问模型的记录时,您可能希望根据关系的存在来限制您的结果。例如,假设您想要检索至少有一条评论的所有博客文章。为此,您可以将关系的名称传递给has
方法:
// Retrieve all posts that have at least one comment...
$posts = Post::has('comments')->get();
您还可以指定运算符和计数以进一步自定义查询:
// Retrieve all posts that have three or more comments...
$posts = Post::has('comments', '>=', 3)->get();
嵌套has
语句也可以使用“点”符号来构造。例如,您可以检索至少有一个评论和投票的所有帖子:
// Retrieve all posts that have at least one comment with votes...
$posts = Post::has('comments.votes')->get();
如果你需要更多的力量,你可以使用whereHas
和orWhereHas
将“where”条件放在你的方法上has
查询。这些方法允许您向关系约束添加自定义约束,例如检查评论的内容:
// Retrieve all posts with at least one comment containing words like foo%
$posts = Post::whereHas('comments', function ($query) {
$query->where('content', 'like', 'foo%');
})->get();
预加载
当访问作为属性的关系时,关系数据是“延迟加载”的。这意味着在您第一次访问该属性之前,关系数据不会真正加载。但是,模型可以在您查询父模型时“预先加载”关系。预加载缓解了 N+1 查询问题。为了说明 N+1 查询问题,考虑一个Book
相关的模型Author
:
class Book extends Model
{
public $belongsTo = [
'author' => ['Acme\Blog\Models\Author']
];
}
现在让我们检索所有书籍及其作者:
$books = Book::all();
foreach ($books as $book) {
echo $book->author->name;
}
此循环将执行 1 个查询以检索表中的所有书籍,然后对每本书执行另一个查询以检索作者。因此,如果我们有 25 本书,这个循环将运行 26 次查询:1 次用于原始书,另外 25 次查询用于检索每本书的作者。
值得庆幸的是,我们可以使用预先加载将此操作减少到只有 2 个查询。查询时,您可以使用with
方法:
$books = 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 = Book::with('author', 'publisher')->get();
嵌套预加载
要急切加载嵌套关系,您可以使用“点”语法。例如,让我们在一个语句中加载所有书籍的作者和作者的所有个人联系人:
$books = Book::with('author.contacts')->get();
约束急切加载
有时您可能希望预加载关系,但也为预加载查询指定额外的查询约束。这是一个例子:
$users = User::with([
'posts' => function ($query) {
$query->where('title', 'like', '%first%');
}
])->get();
在这个例子中,如果帖子是title
列包含单词first
.当然,你也可以调用其他查询生成器 进一步自定义预加载操作的方法:
$users = User::with([
'posts' => function ($query) {
$query->orderBy('created_at', 'desc');
}
])->get();
延迟加载
有时您可能需要在检索到父模型后立即加载关系。例如,如果您需要动态决定是否加载相关模型,这可能很有用:
$books = Book::all();
if ($someCondition) {
$books->load('author', 'publisher');
}
如果你需要在预加载查询上设置额外的查询约束,你可以通过一个Closure
到load
方法:
$books->load([
'author' => function ($query) {
$query->orderBy('published_date', 'asc');
}
]);
插入相关模型
就像你会的查询关系, Winter 支持使用方法或动态属性方法定义关系。例如,也许您需要插入一个新的Comment
为一个Post
模型。而不是手动设置post_id
的属性Comment
, 你可以插入Comment
直接来自关系。
通过关系方法插入
Winter 提供了将新模型添加到关系中的便捷方法。主要是模型可以添加到关系中或从关系中删除。在每种情况下,关系分别关联或解除关联。
添加方法
使用add
关联新关系的方法。
$comment = new Comment(['message' => 'A new comment.']);
$post = Post::find(1);
$comment = $post->comments()->add($comment);
请注意,我们没有访问comments
关系作为动态属性。相反,我们称comments
获取关系实例的方法。这add
方法会自动添加合适的post_id
对新的价值Comment
模型。
如果您需要保存多个相关模型,您可以使用addMany
方法:
$post = Post::find(1);
$post->comments()->addMany([
new Comment(['message' => 'A new comment.']),
new Comment(['message' => 'Another comment.']),
]);
删除方法
相比之下,remove
方法可用于取消关联关系,使其成为孤立记录。
$post->comments()->remove($comment);
在多对多关系的情况下,记录会从关系的集合中移除。
$post->categories()->remove($category);
在“属于”关系的情况下,您可以使用dissociate
方法,它不需要传递给它的相关模型。
$post->author()->dissociate();
添加数据透视表
在处理多对多关系时,add
方法接受一组额外的中间“透视”表属性作为其第二个参数作为数组。
$user = User::find(1);
$pivotData = ['expires' => $expires];
$user->roles()->add($role, $pivotData);
的第二个参数add
方法还可以指定使用的会话密钥延迟绑定 当作为字符串传递时。在这些情况下,数据透视数据可以作为第三个参数提供。
$user->roles()->add($role, $sessionKey, $pivotData);
创建方法
尽管add
和addMany
接受一个完整的模型实例,你也可以使用create
方法,它接受一个 PHP 属性数组,创建一个模型,并将其插入到数据库中。
$post = Post::find(1);
$comment = $post->comments()->create([
'message' => 'A new comment.',
]);
在使用之前create
方法,请务必查看有关属性的文档批量分配 因为 PHP 数组中的属性受模型的“可填充”定义限制。
通过动态属性插入
可以像访问它们一样通过它们的属性直接设置关系。使用此方法设置关系将覆盖以前存在的任何关系。之后应像使用任何属性一样保存模型。
$post->author = $author;
$post->comments = [$comment1, $comment2];
$post->save();
或者,您可以使用主键设置关系,这在处理 HTML 表单时很有用。
// Assign to author with ID of 3
$post->author = 3;
// Assign comments with IDs of 1, 2 and 3
$post->comments = [1, 2, 3];
$post->save();
可以通过将 NULL 值分配给属性来取消关联。
$post->author = null;
$post->comments = null;
$post->save();
如同延迟绑定,在不存在的模型上定义的关系在内存中延迟,直到它们被保存。在此示例中,帖子尚不存在,因此post_id
不能通过评论设置属性$post->comments
.因此,关联会延迟到通过调用save
方法。
$comment = Comment::find(1);
$post = new Post;
$post->comments = [$comment];
$post->save();
多对多关系
安装/拆卸
在处理多对多关系时,模型提供了一些额外的辅助方法,以便更方便地处理相关模型。例如,假设一个用户可以有多个角色,一个角色可以有多个用户。要通过在连接模型的中间表中插入记录来将角色附加到用户,请使用attach
方法:
$user = User::find(1);
$user->roles()->attach($roleId);
将关系附加到模型时,您还可以传递一组要插入到中间表中的附加数据:
$user->roles()->attach($roleId, ['expires' => $expires]);
当然,有时可能需要删除用户的角色。要删除多对多关系记录,请使用detach
方法。这detach
方法将从中间表中删除适当的记录;但是,这两个模型都将保留在数据库中:
// Detach a single role from the user...
$user->roles()->detach($roleId);
// Detach all roles from the user...
$user->roles()->detach();
为了方便,attach
和detach
也接受 ID 数组作为输入:
$user = User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([1 => ['expires' => $expires], 2, 3]);
同步为了方便
您也可以使用sync
构造多对多关联的方法。这sync
方法接受一组 ID 以放置在中间表上。任何不在给定数组中的 ID 都将从中间表中删除。所以,这个操作完成后,中间表中只会存在数组中的ID:
$user->roles()->sync([1, 2, 3]);
您还可以使用 ID 传递额外的中间表值:
$user->roles()->sync([1 => ['expires' => true], 2, 3]);
触摸父时间戳
当模特belongsTo
或者belongsToMany
另一个模型,例如Comment
属于一个Post
,有时在更新子模型时更新父模型的时间戳是有帮助的。例如,当一个Comment
模型已更新,您可能希望自动“触摸”updated_at
拥有的时间戳Post
.只需添加一个touches
包含与子模型的关系名称的属性:
class Comment extends Model
{
/**
* All of the relationships to be touched.
*/
protected $touches = ['post'];
/**
* Relations
*/
public $belongsTo = [
'post' => ['Acme\Blog\Models\Post']
];
}
现在,当你更新一个Comment
, 拥有Post
会有它的updated_at
专栏也更新了:
$comment = Comment::find(1);
$comment->text = 'Edit to this comment!';
$comment->save();
延迟绑定
延迟绑定允许您推迟模型关系绑定,直到主记录提交更改。如果您需要准备一些模型(例如文件上传)并将它们关联到另一个尚不存在的模型,这将特别有用。
您可以延迟任意数量的slave 针对一个模型master 模型使用会话密钥.当主记录与会话密钥一起保存时,与从属记录的关系会自动为您更新。后端支持延迟绑定表单行为自动,但您可能想在其他地方使用此功能。
生成会话密钥
延迟绑定需要会话密钥。您可以将会话密钥视为事务标识符。相同的会话密钥应该用于绑定/解除绑定关系和保存主模型。您可以使用 PHP 生成会话密钥uniqid()
功能。请注意,两者后端表单 和前端form()
功能 自动生成包含会话密钥的隐藏字段。
$sessionKey = uniqid('session_key', true);
延迟关系绑定
除非保存帖子,否则下一个示例中的评论将不会添加到帖子中。
$comment = new Comment;
$comment->content = "Hello world!";
$comment->save();
$post = new Post;
$post->comments()->add($comment, $sessionKey);
NOTE: 这
$post
对象尚未保存,但如果保存发生,将创建关系。
推迟关系解除绑定
除非保存帖子,否则不会删除下一个示例中的评论。
$comment = Comment::find(1);
$post = Post::find(1);
$post->comments()->remove($comment, $sessionKey);
列出所有绑定
使用withDeferred
加载所有记录的关系方法,包括延迟的。结果也将包括现有关系。
$post->comments()->withDeferred($sessionKey)->get();
取消所有绑定
取消延迟绑定并删除从属对象而不是让它们成为孤儿是个好主意。
$post->cancelDeferred($sessionKey);
提交所有绑定
您可以在保存主模型时提交(绑定或取消绑定)所有延迟绑定,方法是提供会话密钥和save
方法。
$post = new Post;
$post->title = "First blog post";
$post->save(null, $sessionKey);
同样的方法适用于模型的create
方法:
$post = Post::create(['title' => 'First blog post'], $sessionKey);
延迟提交绑定
如果您无法提供$sessionKey
保存时,您可以随时使用以下代码提交绑定:
$post->commitDeferred($sessionKey);
清理孤立的绑定
销毁所有未提交且超过 1 天的绑定:
Winter\Storm\Database\Models\DeferredBinding::cleanUp(1);
NOTE: Winter 会自动销毁超过 5 天的延迟绑定。它发生在后端用户登录系统时。
禁用延迟绑定
有时您可能需要为给定模型完全禁用延迟绑定,例如,如果您从单独的数据库连接加载它。为此,您需要确保模型的sessionKey
财产是null
在运行内部保存方法中的前置和后置延迟绑定挂钩之前。为此,您可以绑定到模型的model.saveInternal
事件:
public function __construct(array $attributes = [])
{
$result = parent::__construct($attributes);
$this->bindEvent('model.saveInternal', function () {
$this->sessionKey = null;
});
return $result;
}
NOTE: 这将为您应用此覆盖的任何模型完全禁用延迟绑定。