Eloquent关系

在关系数据库模型中,表与表之前保持相应关系,Eloquent提供简单强大的工具处理表之间的关系比以往都简单。

本章的许多例子都会集中在用户的联系人身上,这是一种常见情况。在Eloquent的ORM中这称之为一对多关系。一个用户有许多联系人。

如果是CRM,联系人被分配给用户,这将是多对多关系。多个用户与单个联系人关联,每个用户与多个联系人关联,用户拥有并属于多个联系人

如果每个联系人都有许多电话号码,而且用户想要一个每个电话号码的数据库用于他们的CRM,你会说用户通过联系人有很多电话号码 - 也就是说,用户有很多联系人,而且联系人有很多电话号码 数字,所以联系人是一种中间人

如果每个联系人都有一个地址,但您只想跟踪一个地址呢?您可以在联系人上包含所有地址字段,但也可以创建一个地址模型,表示联系人只有一个地址。

最后,如果您希望能够加入明星(最喜欢的)联系人,还有活动,该怎么办? 这将是一种多态关系,其中用户有很多明星,但有些可能是联系人,有些可能是事件

那么让我们深入如何去定义这些关系。

一对一

让我们看个例子,一个联系人has one电话号,这种关系定义在示例5-42

Example 5-42. Defining a one-to-one relationship
class Contact extends Model {
    public function phoneNumber() {
        return $this->hasOne(PhoneNumber::class); 
    }

如示,在Eloquent模型上定义了关系方法(($this->hasOne()),在实例上采用完全限定类名定义他们之间的关系。

如何在数据库上定义?因为我们定义一个联系人对应一个电话号,Eloquent期望支持PhoneNumber类的表(可能是phone_numbers)有一个叫contact_id的字段,如果你将其命名为不同的东西(例如,owner_id),你需要更改你的定义。

return $this->hasOne(PhoneNumber::class, 'owner_id');

如下是如何获取联系人的电话号

$contact = Contact::first();
$contactPhone = $contact->phoneNumber;

注意,我们在示例5-42定义了phoneNumber()方法,然后我们使用->phoneNumber来访问。这是个魔法,你也可以通过->phone_number来访问,它将返回关联PhoneNumber记录的完整Eloquent实例。

但是,如果我想从PhoneNumber获取联系人怎么办?如下是解决办法,查看示例5-43

然后通过同样的方式访问.

$contact = $phoneNumber->contact;

插入关系项

每种关系类型都有自己的特性来关联模型,但是核心工作原理是:传递实例给save()或者传递实例数组给saveMany(),你还可以传递属性给createMany()或者是create(),它们将为你生成新的实例.

createMany()方法只在Laravel5.4及之后版本可用

一对多

一对多关系是很常见的,让我们看看如何定义用户拥有多个联系人,如示例5-44

同样,联系人模型对应的表(可能是contacts)有一个user_id字段,如果没有通过给hasMany()传递正确字段名覆盖.

所以我们可以按照如下方式获取用户的联系人

如同一对一一样,我们将方法当做属性来调用,但是这个方法返回的是集合而不是实例,它是一个Eloquent集合,所以我们再其上面各种玩.

就像一对一一样,你也可以定义反向获取,如示例5-45

就像一对一一样,我们可以从联系人获取用户

$userName = $contact->user->name;

从附加项附加和分离相关项

大多数情况下,我们通过在父项上运行save()并传入相关项来附加项目,如$user->contacts()->save($contact)。 但是,如果要在附加(“子”)项上执行这些行为,可以对返回belongsTo关系的方法使用associate()和dissociate()

将关系用作查询器

到目前为止,我们把方法名(例如contacts())作为属性(例如$user->contacts)调用。如果我们按照方法调用,会发生什么?它将返回一个预作用域查询器,而不是处理关系。

因此,如果您有用户1,并且您调用了它的contacts()方法,那么现在您将有一个查询器预处理为“所有具有字段user_id且值为1的联系人”。然后您可以从中构建一个函数查询。

$donors = $user->contacts()->where('status', 'donor')->get();

选择具有相关项的记录

你可以使用has()选择相关项的记录

$postsWithComments = Post::has('comments')->get();

可以调整条件

$postsWithManyComments = Post::has('comments', '>=', 5)->get();

可以嵌套条件

$usersWithPhoneBooks = User::has('contacts.phoneNumbers')->get();

最后,你可以编写自定义查询

Has many through

hasManyThrough()实际上是关系方法,想象一下之前给的例子,一个用户有多个联系人,每个联系人有多个手机号,如果你想获取一个用户联系人的手机号应该怎么办?这称为has-many-through关系

这种结构假设联系人表有一个user_id关联联系人与用户,phone_numbers表有一个contact_id关联它到联系人,然后我们在User上定义关系,如示例5-46

你可以使用$user->phone_numbers访问此关系,您可以自定义中间模型上的关系键(用hasmanythrough()的第三个参数)和远程模型上的关系键(第四个参数)

Has one through

hasOneThrough()与hasManyThrough()类似,但不是通过中间项访问许多相关项,而是通过单个中间项访问单个相关项

如果用户属于一个公司,公司只有一个电话号,而你希望通过提取用户公司的电话号码来获取该用户的电话号码,那该怎么办?这是hasOneThrough()该干的事

多对多

现在变的复杂起来了,让我们考虑一种CRM情况,一个用户对应多个联系人,每个联系人又关联着多个用户.

如示例5-48,我们在用户上定义关系

反向类似5-49

由于单个联系人不能拥有user_id列,并且单个用户不能拥有contact_id列,因此多对多关系依赖于连接两者的数据中枢表。 这个表的常规命名是通过将两个单个表名放在一起,按字母顺序排序,并用下划线分隔它们来完成。

因此,由于我们要链接用户和联系人,我们的数据中枢表应该命名为contacts_users(如果您想自定义表名,请将其作为第二个参数传递给belongsToMany()方法)。 它需要两列:contact_id和user_id

就像hasmany()一样,我们可以访问相关项的集合,但这一次它是来自双方的(示例5-50)

从中枢表获取数据

多对多关系有一个中枢表,中枢表内字段越少越好,但是有些时候需要在中枢表内存储信息,比如有时候你希望在中枢表内存放created_at字段,来查看关系何时被创建的。

为了存储这些字段,你需要将它们添加到关系定义中,如示例5-51,你可以用withPivot()定义特殊字段,也可以用withTimestamps()添加created_at 和 updated_at 时间戳

当您通过关系获取模型实例时,它上面会有一个Pivot属性,它表示在中枢表中的位置.如示例5-52

你可以用as()自定义中枢键,如示例5-53

附加和分离多对多关系项目

由于您的数据透视表可以拥有自己的属性,因此您需要能够在附加多对多相关项时设置这些属性。 您可以通过将数组作为第二个参数传递来保存

此外,您可以使用attach()和detach(),而不是传递相关项的实例,您只需传递一个ID即可。它们的工作原理与save()相同,但也可以接受一个ID数组,而无需重命名像attachmany()。

如果您的目标不是附加或分离,而是颠倒当前的附加状态,那么您需要toggle()方法。当使用toggle()时,如果给定的ID当前已附加,将被分离;如果当前已分离,将附加:

$user->contacts()->toggle([1, 2, 3]); 还可以使用updateExistingPivot()仅对中枢记录进行更改:

如果要替换当前关系,有效地分离所有以前的关系并附加新关系,可以将数组传递给sync()。

多态性

请记住,我们的多态关系是我们有多个对应于相同关系的Eloquent类。 我们现在要使用Stars(如收藏夹)。 用户可以为联系人和事件加注星标,这称多态的来源:有多个类型的对象的单个接口

所以,我们需要三张表三个模型,Star,Contact,Event,contacts与events表保持正常,starts表包含id,starrable_id和starrable_type字段,对于每个星星,我们将定义哪种“类型”(例如,联系人或事件)以及该类型的ID(例如,1)

让我们创建模型,如示例5-54

所以我们如何创建一个Star?

非常简单,联系人现在被标星了.

为了找到给定联系人上的所有星,我们调用stars()方法,如示例5-55所示

如果我们有一个Star的实例,我们可以通过调用我们morphTo关系的方法来获得它的目标,在这个上下文中它是starrable(),如示例5-56

最后,你可能想知道,“如果我想知道是谁星标这个联系人怎么办?”这是一个很好的问题。 这就像在星星表中添加user_id一样简单,然后设置用户有多个星星和星星属于一个用户 - 一对多的关系(例5-57)。 星表几乎成为用户与您的联系人和活动之间的数据中枢表

就这样!现在可以运行$star->user或$user->stars来查找用户的星列表或从星中查找星标的用户。另外,当你创建一个新的星时,可以传递用户

多对多多态

关系类型中最复杂和最不常见的是,多对多的多形态关系就像多态关系

这种关系类型最常见的例子是标签,我会保证它的安全,并以此为例。 让我们假设您希望能够标记联系人和事件。 多对多多态的唯一性在于它是多对多的:每个标签可以应用于多个项目,每个标记的项目可能有多个标签。 除此之外,它还是多态的:标签可以与几种不同类型的项目相关联。 对于数据库,我们将从多态关系的正常结构开始,并且添加一个数据中枢表

这意味着我们需要一个联系人表,一个事件表和一个标签表,所有表格都像普通的ID和你想要的任何属性,以及一个新的taggables表,它将包含tag_id,taggable_id和taggable_type字段。 taggables表中的每个条目都将标记与其中一个可标记内容类型相关联

如示例5-58,我们将在模型上定义关系

然后创建第一个标签

然后我们像平时一样获取关系结果,如示例5-59

Last updated

Was this helpful?