数据库表通常相互关联。 例如,一篇博客文章可能有很多评论,或者一个订单可能与下订单的用户相关联。 October 使管理和处理这些关系变得容易,并支持多种不同类型的关系。
模型关系在您的模型类上被定义为属性。一个定义关系的示例:
class User extends Model
{
public $hasMany = [
'posts' => \Acme\Blog\Models\Post::class
]
}关系,就像模型本身一样,也充当着强大的 查询构建器,将关系作为函数访问可提供强大的方法链和查询能力。例如:
$user->posts()->where('is_active', true)->get();将关系作为属性访问也是可能的。
$user->posts;每个定义可以是一个数组,其中键是关系名称且值是一个详情数组。该详情数组的第一个值始终是关联的模型类名且所有其他值都是必须具有键名的参数。
public $hasMany = [
'posts' => [\Acme\Blog\Models\Post::class, 'delete' => true]
];以下是可用于所有关系的参数:
| Argument | Description |
|---|---|
| order | sorting order for multiple records. |
| conditions | filters the relation using a raw where query statement. |
| scope | filters the relation using a supplied model query scope method. |
| push | if set to false, this relation will not be saved via the push method. Default: true |
| delete | if set to true, the related model will be deleted if the primary model is deleted or relationship is destroyed. Default: false |
| softDelete | if set to true, the related model will be soft deleted if the primary model is soft deleted. Default: false |
| replicate | if set to true, the related model will duplicated or associated via the replicate method. Default: false. |
| relationClass | specify a custom class name for the related object. |
示例过滤器使用 order 和 conditions 参数。
public $belongsToMany = [
'categories' => [
\Acme\Blog\Models\Category::class,
'order' => 'name desc',
'conditions' => 'is_active = 1'
]
];示例过滤器使用 scope 参数。
class Post extends Model
{
public $belongsToMany = [
'categories' => [
\Acme\Blog\Models\Category::class,
'scope' => 'isActive'
]
];
}
class Category extends Model
{
public function scopeIsActive($query)
{
return $query->where('is_active', true)->orderBy('name', 'desc');
}
}scope 参数也可以指一个静态方法。
public $belongsToMany = [
'categories' => [
\Acme\Blog\Models\Category::class,
'scope' => [self::class, 'myFilterMethod']
]
];
public static function myFilterMethod($query, $related, $parent)
{
// ...
}示例实现使用 relationClass 参数。
public $belongsToMany = [
'users' => [
\Backend\Models\User::class,
'relationClass' => \Backend\Classes\MyBelongsToMany::class
]
];该
relationClass应继承指定类型的类。例如,在使用belongsTo时,该类必须继承October\Rain\Database\Relations\BelongsTo。
以下关系类型可用。
一对一关系是一种非常基本的关系. 例如, 一个 User 模型可能与一个 Phone 关联. 为了定义这种关系, 我们在 User 模型上将 phone 条目添加到 $hasOne 属性中.
namespace Acme\Blog\Models;
use Model;
class User extends Model
{
public $hasOne = [
'phone' => \Acme\Blog\Models\Phone::class
];
}一旦关系被定义,我们就可以使用同名的模型属性来检索相关记录。这些属性是动态的,并允许你像访问模型上的常规属性一样访问它们。
$phone = User::find(1)->phone;模型根据模型名称假定关系的外键。在本例中,Phone 模型被自动假定拥有 user_id 外键。如果您希望覆盖此约定,您可以将 key 参数传入定义中。
public $hasOne = [
'phone' => [\Acme\Blog\Models\Phone::class, 'key' => 'my_user_id']
];此外,模型假定外键的值应与父级的 id 列匹配。换句话说,它将在 Phone 记录的 user_id 列中查找用户的 id 列的值。如果你希望关系使用 id 以外的值,你可以将 otherKey 参数传递给定义:
public $hasOne = [
'phone' => [\Acme\Blog\Models\Phone::class, 'key' => 'my_user_id', 'otherKey' => 'my_id']
];现在我们已经可以从我们的 User 访问 Phone 模型。让我们反过来操作,并且在 Phone 模型上定义一个关系,这将让我们能够访问拥有该手机的 User。我们可以使用 $belongsTo 属性定义 hasOne 关系的反向关系:
class Phone extends Model
{
public $belongsTo = [
'user' => \Acme\Blog\Models\User::class
];
}在上面的示例中,模型将尝试匹配 Phone 模型中的 user_id 与 User 模型中的 id。它通过检查关系定义的名称并在该名称后添加 _id 后缀来确定默认的外键名。但是,如果 Phone 模型上的外键不是 user_id,你可以使用定义上的 key 参数传递自定义键名:
public $belongsTo = [
'user' => [Acme\Blog\Models\User::class, 'key' => 'my_user_id']
];如果您的父模型不使用 id 作为其主键,或者您希望将子模型关联到不同的列,您可以将 otherKey 参数传递给定义,以指定您的父表的自定义键:
public $belongsTo = [
'user' => [\Acme\Blog\Models\User::class, 'key' => 'my_user_id', 'otherKey' => 'my_id']
];belongsTo 关联允许您定义一个默认模型,当给定关联为 null 时将返回该模型。这种模式通常被称为空对象模式,并有助于消除代码中的条件检查。在下面的示例中,如果没有 user 附加到文章,则 user 关联将返回一个空的 Acme\Blog\Models\User 模型。
public $belongsTo = [
'user' => [\Acme\Blog\Models\User::class, 'default' => true]
];要用属性填充默认模型,您可以将一个数组传递给 default 参数。
public $belongsTo = [
'user' => [
\Acme\Blog\Models\User::class,
'default' => ['name' => 'Guest']
]
];一对多关系用于定义单个模型拥有任意数量的其他模型的关系。例如,一篇博客文章可以拥有无限数量的评论。与所有其他关系一样,一对多关系通过在模型上添加一个条目到 $hasMany 属性来定义:
class Post extends Model
{
public $hasMany = [
'comments' => \Acme\Blog\Models\Comment::class
];
}记住,模型将自动确定 Comment 模型上的正确外键列。按照约定,它将采用拥有模型的“蛇形命名法”名称,并后缀 _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::class, 'key' => 'my_post_id', 'otherKey' => 'my_id']
];既然我们现在可以访问帖子的所有评论,那么让我们定义一个关系,允许评论访问其父帖子。要定义 hasMany 关系的逆向,请在子模型上定义 $belongsTo 属性:
class Comment extends Model
{
public $belongsTo = [
'post' => \Acme\Blog\Models\Post::class
];
}一旦定义了关系,我们可以通过访问 post “动态属性”来检索 Comment 的 Post 模型:
$comment = Comment::find(1);
echo $comment->post->title;在上面的例子中,模型将尝试将 Comment 模型中的 post_id 匹配到 Post 模型上的 id。它通过检查关系的名称并为其添加 _id 后缀来确定默认外键名称。然而,如果 Comment 模型上的外键不是 post_id,您可以使用 key 参数传递自定义键名:
public $belongsTo = [
'post' => [\Acme\Blog\Models\Post::class, 'key' => 'my_post_id']
];如果你的父模型不使用 id 作为其主键,或者你希望将子模型连接到不同的列,你可以将 otherKey 参数传递给定义,以指定你父表的自定义键:
public $belongsTo = [
'post' => [Acme\Blog\Models\Post::class, 'key' => 'my_post_id', 'otherKey' => 'my_id']
];多对多关系比 hasOne 和 hasMany 关系稍微复杂。此类关系的一个例子是一个用户拥有多个角色,其中这些角色也由其他用户共享。例如,许多用户可能拥有 "Admin" 角色。为了定义这种关系,需要三张数据库表: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 属性添加一个条目来定义。例如,让我们在 User 模型上定义 roles 方法:
class User extends Model
{
public $belongsToMany = [
'roles' => \Acme\Blog\Models\Role::class
];
}一旦关系被定义,你即可使用 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::class, 'table' => 'acme_blog_role_user']
];除了自定义连接表的名称,你还可以通过向 belongsToMany 定义传递额外参数来自定义表中键的列名。key 参数是你正在定义关系的模型的外部键名,而 otherKey 参数是你正在连接的模型的外部键名:
public $belongsToMany = [
'roles' => [
\Acme\Blog\Models\Role::class,
'table' => 'acme_blog_role_user',
'key' => 'my_user_id',
'otherKey' => 'my_role_id'
]
];要定义多对多关系的逆关系,你只需在你的相关模型上放置另一个 $belongsToMany 属性。为了继续我们的用户角色示例,让我们在 Role 模型上定义 users 关系:
class Role extends Model
{
public $belongsToMany = [
'users' => \Acme\Blog\Models\User::class
];
}如您所见,这种关系定义方式与 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::class,
'pivot' => ['column1', 'column2']
]
];如果您希望数据透视表自动维护 created_at 和 updated_at 时间戳,请使用 timestamps 参数来定义关系:
public $belongsToMany = [
'roles' => [
\Acme\Blog\Models\Role::class,
'timestamps' => true
]
];如果您想定义一个自定义模型来表示您关系的中间表,您可以在定义关系时使用 pivotModel 属性。自定义的多对多中间模型应该扩展 October\Rain\Database\Pivot 类,而自定义的多态多对多中间模型应该扩展 October\Rain\Database\MorphPivot 类。
public $belongsToMany = [
'roles' => [
\Acme\Blog\Models\Role::class,
'pivotModel' => \Acme\Blog\Models\UserRolePivot::class
]
];在某些情况下,您可能希望将同一个关系关联两次或多次,为每个附加记录使用不同的中间表数据。下面是一个示例,展示了数据库表结构,该结构在连接表上使用自增主键而不是复合主键。
Schema::create('role_user', function($table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->integer('role_id')->unsigned();
});此配置必须使用自定义的 pivotModel,并且 pivotKey 属性设置为自增列名 (id)。
public $belongsToMany = [
'roles' => [
\Acme\Blog\Models\Role::class,
'pivotModel' => \Acme\Blog\Models\UserRolePivot::class,
'pivotKey' => 'id'
]
];枢轴模型定义应该将 $incrementing 属性设置为 true,以启用自增主键,该主键默认为 id,与其他所有模型一样。
class UserRolePivot extends \October\Rain\Database\Pivot
{
public $incrementing = true;
}以下是 belongsToMany 关系所支持的属性:
| Property | Description |
|---|---|
| table | the name of the join table. |
| key | the key column name of the defining model (inside pivot table). Default value is combined from model name and _id suffix, i.e. user_id |
| parentKey | the key column name of the defining model (inside defining model table). Default: id |
| otherKey | the key column name of the related model (inside pivot table). Default value is combined from model name and _id suffix, i.e. role_id |
| relatedKey | the key column name of the related model (inside related model table). Default: id |
| timestamps | if true, the join table should contain created_at and updated_at columns. Default: false |
| detach | if set to false, the related model will be not be detached if the primary model is deleted or relationship is destroyed, default: true. |
| pivot | an array of pivot columns found in the join table, attributes are available via $model->pivot. |
| pivotModel | specify a custom model class to return when accessing the pivot relation. Defaults to October\Rain\Database\Pivot while for polymorphic relation October\Rain\Database\MorphPivot. |
| pivotSortable | specify a sort order column for the pivot table, used in combination with the SortableRelation model trait. |
| pivotKey | specify an incrementing ID column for the pivot table, must be used with a custom pivotModel class with a primary key used on the table. |
has-many-through 关系提供了一种方便的捷径,用于通过中间关系访问远程关系。例如,一个 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 提供了对某个国家的帖子的访问。为了执行此查询,模型会检查中间 users 表上的 country_id。在找到匹配的用户 ID 后,它们被用于查询 posts 表。
现在我们已经研究了该关系的表结构,让我们在Country模型上定义它:
class Country extends Model
{
public $hasManyThrough = [
'posts' => [
\Acme\Blog\Models\Post::class,
'through' => \Acme\Blog\Models\User::class
],
];
}传递给 $hasManyThrough 关系的第一个参数是我们希望访问的最终模型的名称,而 through 参数是中间模型的名称。
在执行关系查询时,将使用典型的外键约定。如果你想自定义关系的键,你可以将它们作为 key、otherKey 和 throughKey 参数传递给 $hasManyThrough 定义。key 参数是中间模型上的外键名称,throughKey 参数是最终模型上的外键名称,而 otherKey 是本地键。
public $hasManyThrough = [
'posts' => [
\Acme\Blog\Models\Post::class,
'key' => 'my_country_id',
'through' => \Acme\Blog\Models\User::class,
'throughKey' => 'my_user_id',
'otherKey' => 'my_id',
'secondOtherKey' => 'my_country_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::class,
'through' => \Acme\Supplies\Model\User::class
],
];
}传递给 $hasOneThrough 属性的第一个数组参数是我们希望访问的最终模型的名称,而 through 键是中间模型的名称。
在执行关系查询时,将采用典型外键约定。如果您想自定义关系的键,您可以将它们作为 key、otherKey 和 throughKey 参数传递给 $hasManyThrough 定义。key 参数是中间模型上的外键名称,throughKey 参数是最终模型上的外键名称,而 otherKey 则是本地键。
public $hasOneThrough = [
'userHistory' => [
\Acme\Supplies\Model\History::class,
'key' => 'supplier_id',
'through' => \Acme\Supplies\Model\User::class,
'throughKey' => 'user_id',
'otherKey' => 'id',
'secondOtherKey' => 'my_country_id'
],
];多态关联允许一个模型在一个单一关联上属于多个其他模型。
一对一多态关系类似于简单的一对一关系;然而,目标模型可以在一个关联上属于多种类型的模型。例如,假设您想要为您的员工和您的产品存储照片。使用多态关系,您可以为这两种情况使用单个 photos 表。首先,让我们检查构建这种关系所需的表结构:
staff
id - integer
name - string
products
id - integer
price - integer
photos
id - integer
path - string
imageable_id - integer
imageable_type - string
值得注意的两个重要列是 photos 表上的 imageable_id 列和 imageable_type 列。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::class, 'name' => 'imageable']
];
}
class Product extends Model
{
public $morphOne = [
'photo' => [\Acme\Blog\Models\Photo::class, 'name' => 'imageable']
];
}一旦你的数据库表和模型已定义,你就可以通过你的模型访问这些关系。例如,要访问某个员工的照片,我们只需使用 photo 动态属性:
$staff = Staff::find(1);
$photo = $staff->photo您还可以通过访问 morphTo 关联的名称,从多态模型中获取多态关联的所有者。在我们的例子中,那就是 Photo 模型上的 imageable 定义。因此,我们将把它作为动态属性来访问:
$photo = Photo::find(1);
$imageable = $photo->imageable;Photo 模型上的 imageable 关系将返回一个 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::class, 'name' => 'commentable']
];
}
class Product extends Model
{
public $morphMany = [
'comments' => [\Acme\Blog\Models\Comment::class, 'name' => 'commentable']
];
}一旦你的数据库表和模型被定义,你就可以通过你的模型访问这些关系。例如,要访问一篇文章的所有评论,我们可以使用 comments 动态属性:
$post = Author\Plugin\Models\Post::find(1);
foreach ($post->comments as $comment) {
//
}您还可以通过访问 morphTo 关系的名称,从多态模型中检索多态关系的拥有者。在我们的例子中,也就是 Comment 模型上的 commentable 定义。因此,我们将把它作为一个动态属性来访问:
$comment = Author\Plugin\Models\Comment::find(1);
$commentable = $comment->commentable;Comment 模型上的 commentable 关系将返回一个 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 模型都将在基模型类的 $morphToMany 属性中定义一个 tags 关系:
class Post extends Model
{
public $morphToMany = [
'tags' => [\Acme\Blog\Models\Tag::class, 'name' => 'taggable']
];
}接下来,在 Tag 模型上,你应该为其每个相关模型定义一个关联关系。因此,对于这个例子,我们将定义一个 posts 关联关系和一个 videos 关联关系:
class Tag extends Model
{
public $morphedByMany = [
'posts' => [\Acme\Blog\Models\Post::class, 'name' => 'taggable'],
'videos' => [\Acme\Blog\Models\Video::class, 'name' => 'taggable']
];
}Once your database table and models are defined, you may access the relationships via your models. For example, to access all of the tags for a post, you can simply use the tags dynamic property:
$post = Post::find(1);
foreach ($post->tags as $tag) {
//
}您还可以通过访问在 $morphedByMany 属性中定义的关系名称,从多态模型中检索多态关系的拥有者。在我们的案例中,这指的是 Tag 模型上的 posts 或 videos 方法。因此,您将把这些关系作为动态属性来访问:
$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 October\Rain\Database\Relations\Relation;
Relation::morphMap([
'staff' => \Acme\Blog\Models\Staff::class,
'product' => \Acme\Blog\Models\Product::class,
]);注册 morphMap 最常见的位置是在 插件注册文件 的 boot 方法中。
由于所有类型的模型关系都可以通过函数调用,您可以调用这些函数来获取关系实例,而无需实际执行关系查询。此外,所有类型的关系也充当 查询构建器,允许您在最终对数据库执行 SQL 之前,继续将约束链式添加到关系查询中。
例如,想象一个博客系统,其中一个 User 模型有许多关联的 Post 模型:
class User extends Model
{
public $hasMany = [
'posts' => \Acme\Blog\Models\Post::class
];
}您可以查询 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) {
// ...
}动态属性是“延迟加载”的,意味着它们只会在你实际访问时才加载其关系数据。因此,开发者通常会使用预加载(eager loading)(见下文)来预加载他们知道在模型加载后会被访问的关系。预加载显著减少了加载模型关联所需的 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 方法来对你的 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();要查询关系是否存在,并为关系查询添加单个条件,使用 whereRelation、orWhereRelation、whereMorphRelation 或 orWhereMorphRelation 方法更方便。
$posts = Post::whereRelation('comments', 'is_approved', false)->get();searchWhereRelation 或 orSearchWhereRelation 方法可用于搜索关系列。类似于 搜索查询,该方法将使用搜索词(第一个参数)、关系名称(第二个参数)和搜索列(第三个参数),通过不区分大小写的 LIKE 查询来添加到查询中。
$posts = Post::searchWhereRelation('foo bar', 'author', ['name', 'bio'])->get();您可以将关系的名称传递给 doesntHave 和 orDoesntHave 方法,以便根据关系的不存在性来限制结果。
$posts = Post::doesntHave('comments')->get();whereDoesntHave 和 orWhereDoesntHave 方法可以为你的 doesntHave 查询添加额外的查询约束。
$posts = Post::whereDoesntHave('comments', function ($query) {
$query->where('content', 'like', 'code%');
})->get();在某些场景下您可能希望计算给定关系定义中找到的相关记录数量。 withCount 方法可用于在选定模型中包含一个 {relation}_count 列。
$users = User::withCount('roles')->get();
foreach ($users as $user) {
echo $user->roles_count;
}withCount 方法支持多个关联以及额外的查询约束。
$posts = Post::withCount(['votes', 'comments' => function ($query) {
$query->where('content', 'like', 'foo%');
}])->get();
echo $posts[0]->votes_count;
echo $posts[0]->comments_count;你可以通过 loadCount 方法惰性加载计数列。
$user = User::first();
$user->loadCount('roles');额外的查询约束也支持。
$user->loadCount(['roles' => function ($query) {
$query->where('clearance', `>`, 5);
}])当以属性形式访问关联关系时,关联关系数据是“惰性加载”的。这意味着直到您首次访问该属性时,关联关系数据才会被实际加载。然而,模型可以在您查询父模型时“预加载”关联关系。预加载缓解了 N + 1 查询问题。为了说明 N + 1 查询问题,请考虑一个与 Author 相关联的 Book 模型。
class Book extends Model
{
public $belongsTo = [
'author' => \Acme\Blog\Models\Author::class
];
}现在让我们检索所有书籍及其作者:
$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');
}如果你需要对预加载查询设置额外的查询约束,你可以向 load 方法传递一个 Closure:
$books->load([
'author' => function ($query) {
$query->orderBy('published_date', 'asc');
}
]);就像你查询一个关联一样,October CMS支持使用方法或动态属性方式定义关联。例如,也许你需要为 Post 模型插入一条新的 Comment。你可以不手动设置 Comment 上的 post_id 属性,而是可以直接从关联中插入 Comment。
October 提供了便捷的方法来向关系中添加新模型。主要地,模型可以被添加到关系中,也可以从关系中移除。在每种情况下,该关系分别被关联或解除关联。
使用 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 模型被更新时, 你可能希望自动 "touch" 拥有它的 Post 模型的 updated_at 时间戳. 只需向子模型添加一个 touches 属性, 其中包含关系名称即可:
class Comment extends Model
{
/**
* All of the relationships to be touched.
*/
protected $touches = ['post'];
/**
* Relations
*/
public $belongsTo = [
'post' => \Acme\Blog\Models\Post::class
];
}现在,当你更新一个 Comment 时,拥有它的 Post 的 updated_at 列也将被更新:
$comment = Comment::find(1);
$comment->text = 'Edit to this comment!';
$comment->save();延迟绑定允许您推迟模型关系绑定,直到主记录提交更改。如果您需要准备一些模型(例如文件上传)并将其关联到尚未存在的另一个模型,这会特别有用。
您可以使用会话键,将任意数量的从属模型关联到一个主模型。当主记录与会话键一起保存时,与从属记录的关系将自动为您更新。延迟绑定在后端表单行为中受到自动支持,但您可能希望在其他地方使用此功能。
会话密钥对于延迟绑定是必需的。你可以将会话密钥视为事务标识符。相同的会话密钥应应用于绑定/解除绑定关系以及保存主模型。你可以使用 PHP 的 uniqid() 函数生成会话密钥。请注意,表单助手 会自动生成一个包含会话密钥的隐藏字段。
$sessionKey = uniqid('session_key', true);下一个示例中的评论不会添加到帖子中,除非帖子被保存。
$comment = new Comment;
$comment->content = "Hello world!";
$comment->save();
$post = new Post;
$post->comments()->add($comment, $sessionKey);$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(['sessionKey' => $sessionKey]);相同的方法也适用于模型的 create 方法:
$post = Post::create(['title' => 'First blog post'], $sessionKey);如果您在保存时无法提供 $sessionKey,您可以使用接下来的代码随时提交绑定:
$post->commitDeferred($sessionKey);销毁所有尚未提交且已超过1天的绑定:
October\Rain\Database\Models\DeferredBinding::cleanUp(1);October CMS 自动销毁超过 5 天的延迟绑定 通过垃圾回收。
有时,您可能需要为给定模型完全禁用延迟绑定,例如,如果您正在从单独的数据库连接加载它。为此,您需要确保在内部保存方法中的预延迟绑定和后延迟绑定钩子运行之前,模型的 sessionKey 属性为 null。为此,您可以绑定到模型的 model.saveInternal 事件。
public function __construct()
{
parent::__construct(...func_get_args());
$this->bindEvent('model.saveInternal', function () {
$this->sessionKey = null;
});
}这将完全禁用你应用此覆盖的任何模型的延迟绑定。