深入理解 Laravel Eloquent 模型关系
模型及其相互之间的关系是 Laravel Eloquent 的核心组成部分。如果您在理解它们时遇到困难,或者难以找到简单、友好的完整指南,那么本文将是您的理想起点!
作为一名技术文章的作者,我很容易营造一种平台所提供的专业知识和声望的假象。但说实话,我自己在 学习 Laravel 的过程中也经历了一段艰难的时光,这主要是因为它是我的第一个全栈框架。原因之一是我当时没有在工作中使用它,只是出于好奇心去探索;所以,我每次尝试学习,遇到困惑点,就会放弃,最终忘得一干二净。我大概重复了五六次这样的过程,才开始真正理解它(当然,官方文档对此也没有提供太多帮助)。
其中最让我困惑的就是 Eloquent,或者更准确地说,是模型之间的关系(因为 Eloquent 的内容非常广泛,很难完全掌握)。那些用作者和博客文章作为示例的教程简直是开玩笑,因为真实的项目要复杂得多;不幸的是,官方文档也使用了类似(或完全相同)的例子。此外,即使我找到了一些有用的文章和资源,解释也往往很糟糕或者严重缺失,导致完全没有实际帮助。
(顺便说一句,我之前因为批评官方文档而遭到攻击,所以如果你也有类似想法,这是我的标准回复:先看看 Django 的文档,然后再来和我讨论。)
最终,经过不断地学习和实践,我逐渐掌握了它,并且开始能够正确地为项目建模,并能熟练地使用这些模型。后来,我偶然发现了一些巧妙的集合处理技巧,这使得这项工作更加愉快。在这篇文章中,我将从最基础的知识开始,逐步涵盖您在实际项目中可能遇到的所有用例。
为什么 Eloquent 模型关系难以掌握?
很遗憾,我遇到过太多 Laravel 开发人员无法正确理解模型。
但究竟是什么原因呢?
即使如今,关于 Laravel 的课程、文章和视频数量呈爆炸式增长,但整体理解水平仍然很差。我认为这是一个值得我们深思的重要问题。
如果问我,我会说 Eloquent 模型关系本身并不难。至少从“难度”的定义来看是这样的。实时模式迁移很困难;编写一个新的模板引擎很难;为 Laravel 的核心代码做贡献很难。与这些相比,学习和使用 ORM… 额,真的不难!🤭🤭
实际上,学习 Laravel 的 PHP 开发人员往往觉得 Eloquent 很难。这才是真正根本的问题。在我看来,这有几个因素导致了这种情况(以下观点可能比较尖锐,请注意!):
- 在 Laravel 出现之前,大多数 PHP 开发人员接触到的一个框架是 CodeIgniter(它现在仍然 活跃,即使它变得更像 Laravel/CakePHP)。在较老的 CodeIgniter 社区中(如果还存在的话),“最佳实践”是在需要的地方直接粘贴 SQL 查询。尽管现在有了新的 CodeIgniter,但这些习惯仍然保留了下来。因此,当学习 Laravel 时,ORM 的概念对 PHP 开发人员来说是一个全新的概念。
- 除了极少数接触过 Yii、CakePHP 等框架的 PHP 开发人员之外,其余的 PHP 开发人员习惯在核心 PHP 或 WordPress 等环境中工作。同样,那里也不存在基于 OOP 的思维方式,因此框架、服务容器、设计模式和 ORM … 这些都是他们不熟悉的概念。
- 在 PHP 世界中,很少有持续学习的习惯。普通的 PHP 开发人员满足于使用关系数据库并以字符串形式编写查询来处理单服务器设置。异步编程、Web 套接字、HTTP 2/3、Linux(更别提 Docker 了)、单元测试、领域驱动设计——对绝大多数 PHP 开发人员来说,这些都是陌生的想法。因此,当他们接触到 Eloquent 时,很难去学习新的、具有挑战性的知识,直到自己觉得舒适。
- 对数据库和建模的整体理解也很差。由于数据库设计与 Eloquent 模型直接且不可分割地相关联,因此这增加了学习的难度。
我并不是想进行苛刻的概括和批评——实际上也有许多优秀的 PHP 开发人员,但他们的总体比例确实不高。
如果你正在阅读这篇文章,这意味着你已经克服了这些障碍,接触到了 Laravel,并且正在努力掌握 Eloquent。
恭喜你!👏
你已经快成功了!所有的构建模块都已准备就绪,我们只需要按照正确的顺序和细节来组合它们。换句话说,让我们从数据库层面开始探讨。
数据库模型:关系与基数
为了简单起见,我们假设在本文中只使用关系数据库。原因之一是 ORM 最初是为关系数据库开发的;另一个原因是 RDBMS 仍然非常流行。
数据模型
首先,让我们更好地理解数据模型。模型(或更准确地说是数据模型)的概念来源于数据库。没有数据库,就没有数据,因此也就没有数据模型。那么,什么是数据模型呢?很简单,它就是您决定如何存储/构建数据的方式。例如,在电子商务商店中,您可能会将所有内容都存储在一个巨大的表中(这是一种糟糕的做法,但在 PHP 世界中却并不少见);这就是您的数据模型。您也可以将数据拆分为 20 个主表和 16 个连接表;这也是一个数据模型。
另外请注意,数据在数据库中的组织方式不一定要 100% 与其在框架的 ORM 中的组织方式相匹配。然而,尽量让它们保持一致是一个好的做法,这样我们在开发时就不会有太多额外的负担。
基数
让我们快速了解一下“基数”这个术语。它通常指“计数”,所以 1、2、3…等等,都可以是某个事物的基数。故事到此结束,让我们继续前进!
关系
现在,无论何时我们将数据存储在任何类型的系统中,数据点之间都可以通过多种方式相互关联。我知道这听起来既抽象又枯燥,但请耐心听下去。不同数据项的连接方式称为关系。让我们先看一些非数据库的例子,以便我们确信我们完全理解了这个概念。
- 如果我们将所有内容都存储在一个数组中,一种可能的关系是:下一个数据项位于比前一个索引大 1 的索引处。
- 如果我们将数据存储在二叉树中,一种可能的关系是左子树的值总是小于父节点的值(如果我们选择以这种方式维护树)。
- 如果我们将数据存储为等长数组的数组,我们可以模拟一个矩阵,然后它的属性就成为我们数据的关系。
因此,我们可以看到“关系”一词在数据的上下文中没有固定的含义。事实上,如果两个人正在查看相同的数据,他们可能会识别出两种截然不同的数据关系(你好,统计学!),并且它们可能都是有效的。
关系数据库
基于我们迄今为止讨论的所有术语,我们终于可以谈论与 Web 框架(Laravel)中的模型有直接联系的东西——关系数据库。对我们大多数人来说,使用的主要数据库是 MySQL、MariaDB、PostgreSQL、MSSQL、SQL Server、SQLite 或类似的数据库。我们可能也模糊地知道它们被称为 RDBMS,但我们大多数人都忘记了它的实际含义以及为什么它如此重要。
当然,RDBMS 中的 “R” 代表关系。这并非随意选择的术语;通过这一点,我们强调了这些数据库系统旨在高效地处理存储的数据之间的关系。事实上,这里的“关系”具有严格的数学含义。尽管开发人员不需要为此操心,但了解这些类型的数据库背后存在严格的数学 基础 是有益的。
浏览这些资源以学习 SQL 和 NoSQL。
好的,根据经验我们知道 RDBMS 中的数据存储为表。那么,关系在哪里呢?
RDBMS 中的关系类型
这可能是 Laravel 和模型关系整个主题中最重要的部分。如果你不理解这一点,Eloquent 将永远不会变得清晰。所以接下来的几分钟请注意(这实际上并不难)。
RDBMS 允许我们在数据库级别建立数据之间的关系。这意味着这些关系不是虚构的、想象的或主观的,不同的人创建或推断出的结果应该是一致的。
同时,RDBMS 中有一些功能/工具允许我们创建和实施这些关系,例如:
- 主键
- 外键
- 约束条件
我不希望本文变成数据库课程,因此我假设您了解这些概念的含义。如果没有,或者您对此感到不太自信,我推荐这个友好的视频(可以随意探索整个系列):
碰巧的是,这些 RDBMS 风格的关系也是现实世界应用程序中最常见的关系(尽管并非总是如此,例如社交网络最好建模为图而不是表的集合)。因此,让我们逐一了解它们,并尝试理解它们在哪些地方可能有用。
一对一关系
在几乎每个 Web 应用程序中,都有用户帐户。此外,关于用户和帐户的信息(通常情况下)如下:
- 一个用户只能有一个帐户。
- 一个帐户只能由一个用户拥有。
是的,我们可以争辩说一个人可以使用另一个电子邮件注册,从而创建两个帐户,但从 Web 应用程序的角度来看,那是两个不同的人,拥有两个不同的帐户。例如,该应用程序不会在一个帐户中显示另一个帐户的数据。
所有这些意味着——如果您的应用程序中存在这种情况并且您使用的是关系数据库,那么您需要将其设计为一对一关系。请注意,没有人为地强迫您这样做——业务领域存在明确的情况,而且您恰好在使用关系数据库。只有当这两个条件都满足时,您才能建立一对一关系。
对于这个例子(用户和账户),以下是我们如何在创建模式时实现这种关系:
CREATE TABLE users( id INT NOT NULL AUTO_INCREMENT, email VARCHAR(100) NOT NULL, password VARCHAR(100) NOT NULL, PRIMARY KEY(id) ); CREATE TABLE accounts( id INT NOT NULL AUTO_INCREMENT, role VARCHAR(50) NOT NULL, PRIMARY KEY(id), FOREIGN KEY(id) REFERENCES users(id) );
注意到这里的技巧了吗?在通常构建应用程序时很少见,但在 accounts 表中,我们将 id 字段同时设置为主键和外键!外键属性将它链接到 users 表(当然🙄),而主键属性使 id 列具有唯一性——这才是真正的一对一关系!
当然,这种关系的忠实度不能得到完全保证。例如,没有什么可以阻止我添加 200 个新用户,而不向 accounts 表添加一个条目。如果我这样做,最终我会得到一对零的关系!🤭🤭但在纯粹的结构范围内,这是我们能做的最好的。如果我们想防止添加没有帐户的用户,我们需要从某种编程逻辑中寻求帮助,无论是数据库触发器的形式还是 Laravel 强制执行的验证。
如果你开始感到压力很大,我有一些建议:
- 慢慢来。尽可能慢地学习。与其试图完成这篇文章以及你今天收藏的其他 15 篇文章,不如专注于这篇文章。花上 3、4、5 天,如果需要的话——你的目标应该是彻底消除 Eloquent 模型关系给您带来的困扰。你过去从一篇文章跳到另一篇文章,浪费了数百个小时,但没有任何帮助。所以,这次做一些不同的事情。😇
- 尽管这篇文章是关于 Laravel Eloquent 的,但所有的内容都来得太晚了。这一切的基础是数据库模式,所以我们的重点应该放在首先把它做好。如果您不能纯粹在数据库级别上工作(假设世界上没有框架),那么模型和关系将永远没有意义。因此,暂时忘掉 Laravel。彻底地忘记。我们现在只是在讨论和进行数据库设计。是的,我会时不时地提到 Laravel,但如果它们让情况变得复杂,您的工作就是完全忽略它们。
- 之后,阅读更多关于数据库及其提供的内容。MongoDB 中的索引、性能、触发器、底层数据结构及其行为、缓存、关系… 您可以涵盖的任何相关内容都会对您作为工程师有所帮助。请记住,框架模型只是一个外壳;平台的真正功能来自其底层数据库。
一对多关系
我不确定您是否意识到这一点,但这是我们在日常工作中直觉地建立的关系类型。例如,当我们创建一个订单表时,并将外键存储到用户表中,我们就创建了用户和订单之间的一对多关系。这是为什么呢?好吧,让我们从谁可以拥有多少的角度来看一下:允许一个用户拥有多个订单,这几乎是所有电子商务的运作方式。反过来看,这个关系说明一个订单只能属于一个用户,这也很有道理。
在数据建模、RDBMS 书籍和系统文档中,这种情况用图表表示如下:
注意到构成三叉戟的三条线了吗?这是“多”的符号,因此此图表示一个用户可以有多个订单。
顺便说一下,我们反复遇到的这些“多”和“一”的计数就是所谓的关系的基数(还记得上一节中的这个词吗?)。同样,对于本文,这个术语没有特别的用途,但它有助于理解这个概念,以防它在面试或进一步阅读中出现。
简单吧?而在实际 SQL 方面,创建这种关系也很简单。事实上,它比一对一关系的情况要简单得多!
CREATE TABLE users( id INT NOT NULL AUTO_INCREMENT, email VARCHAR(100) NOT NULL, password VARCHAR(100) NOT NULL, PRIMARY KEY(id) ); CREATE TABLE orders( id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, description VARCHAR(50) NOT NULL, PRIMARY KEY(id), FOREIGN KEY(user_id) REFERENCES users(id) );
orders 表存储每个订单的 user_id。由于没有约束(限制) orders 表中的 user_id 必须是唯一的,这意味着我们可以多次重复单个 user_id。这正是创建一对多关系的原因,而不是隐藏在背后的什么神秘魔法。user_id 在 orders 表中被存储,SQL 本身没有任何关于一对多、一对一等概念。但是一旦我们以这种方式存储了数据,我们可以认为存在一对多的关系。
希望现在您能理解了。或者至少,比以前更理解了。😀请记住,就像其他任何事情一样,这只是练习的问题。一旦您在现实世界中这样做过 4-5 次,您就不必再去想它了。
多对多关系
实践中出现的下一种关系是所谓的多对多关系。再次强调,在担心框架甚至深入研究数据库之前,让我们想想现实世界的例子:书籍和作者。想想你最喜欢的作家;他们写了不止一本书,对吧?同时,看到几位作者合作写一本书(至少在非小说类作品中)是很常见的。所以,一位作者可以写很多书,而一本书也可以由很多作者合著。这在两个实体(书籍和作者)之间形成了多对多关系。
现在,假设您不太可能创建一个涉及图书馆或书籍和作者的真实应用程序,那么让我们考虑更多示例。在 B2B 设置中,制造商从供应商处订购商品,然后收到发票。发票将包含多个项目,每个项目都列出供应的数量和项目;例如,5 英寸管件 x 200 等。在这种情况下,物品和发票是多对多的关系(试着说服自己)。在车队管理系统中,车辆和司机之间也会有类似的关系。在电子商务网站中,如果我们考虑收藏夹或愿望清单等功能,用户和产品可以具有多对多关系。
好吧,现在如何在 SQL 中创建这种多对多关系呢?基于我们对一对多关系如何工作的理解,可能很容易认为我们应该在两个表中的另一个表中存储外键。但是,如果我们尝试这样做,我们会遇到重大问题。看一下这个例子,其中书籍和作者应该具有多对多关系:
乍一看,一切看起来都很好——书籍以多对多的方式映射到作者。但仔细查看 authors 表的数据:book id 为 12 和 13 的书都是由 Peter M.(author id 为 2)撰写的,因此我们别无选择,只能重复这些条目。不仅 authors 表现在存在数据完整性问题(规范化以及其他问题),id 列中的值现在也重复了。这意味着在我们选择的设计中,不能有主键列(因为主键不能有重复值),那么一切都会崩溃。
显然,我们需要一种新的方法来解决这个问题。幸运的是,这个问题已经有了解决方案。由于直接将外键存储到两个表中会导致问题,因此在 RDBMS 中创建多对多关系的正确方法是创建一个所谓的“连接表”。这个想法基本上是保持两个原始表不受干扰,并创建一个第三个表来表示多对多的映射。
让我们重新做一下失败的示例,并包含一个连接表:
请注意,发生了很大的变化:
- authors 表中的列数减少了。
- books 表中的列数减少了。
- 由于不再需要重复,authors 表中的行数也减少了。
- 出现了一个名为 authors_books 的新表,其中包含关于哪个 author id 与哪个 book id 相关联的信息。我们可以将连接表命名为任何名称,但按照惯例,它只是使用下划线连接它所代表的两个表的结果。
连接表没有主键,在大多数情况下只包含两列——来自两个表的外键。就好像我们从前面的例子中删除了外键列,并将它们粘贴到了这个新表中。由于没有主键,因此可以根据需要重复所有关系记录。
现在,我们可以用肉眼看到连接表是如何清晰地展示关系的,但是我们如何在我们的应用程序中访问它们呢?这里的秘密与“连接表”这个名称相关联。这不是关于 SQL 查询的课程,所以我不会深入研究它,但我的想法是,如果您想在一个高效的查询中获取某个特定作者的所有书籍,您可以使用 SQL 连接表:authors、authors_books 和 books。authors 和 authors_books 表分别通过 id 和 author_id 列连接,而 authors_books 和 books 表分别通过 book_id 和 id 列连接。
让人筋疲力尽,没错。但让我们看看好的一面——我们已经完成了在处理 Eloquent 模型之前需要做的一切必要的理论/基础工作。让我提醒你,所有这些都不是可选项!不理解数据库设计会让你永远深陷 Eloquent 的困境。此外,无论 Eloquent 做什么或试图做什么,它都完美地反映了这些数据库层面的细节,因此很容易理解,为什么试图在回避 RDBMS 的同时学习 Eloquent 是徒劳的。
在 Laravel Eloquent 中创建模型关系
经过一番大约 70000 英里的弯路之后,我们终于可以讨论 Eloquent、它的模型以及如何创建/使用它们了。现在,我们在文章的前面部分了解到,一切都始于数据库以及如何对数据进行建模。这让我意识到我应该使用一个完整的例子来开始一个新的项目。同时,我希望这个例子是真实的,而不是关于博客和作者或书籍和书架(它们也是真实世界,但已经用得太多了)。
让我们想象一家销售毛绒玩具的商店。我们还假设我们已经获得了需求文档,从中我们可以识别出系统中的四个实体:用户、订单、发票、项目、类别、子类别和交易。是的,可能还会涉及到更多复杂的问题,但让我们把那些放在一边,专注于我们如何从文档到应用程序。
一旦我们确定了系统中的主要实体,我们就需要根据我们目前讨论的数据库关系来考虑它们之间的关系。以下是我能想到的:
- 用户和订单:一对多。
- 订单和发票:一对一。我意识到这不是一成不变的,根据您的业务领域,可能存在一对多、多对一或多对多的关系。但是对于普通的小型电子商务商店,一个订单只会产生一张发票,反之亦然。
- 订单和项目:多对多。
- 项目和类别:多对一。同样,大型电子商务网站并非如此,但我们的业务规模很小。
- 类别和子类别:一对多。同样,您会发现大多数真实世界的例子都与此相反,但是,嘿,Eloquent 本身就够难了,所以我们不要让数据建模变得更难!
- 订单和交易:一对多。我还要说明我选择这两个关系的原因:1) 我们也可以在交易和发票之间添加关系,这只是一个数据建模的决定。 2) 为什么这里是一对多?好吧,由于某些原因,订单付款失败,然后下次成功,这很常见。在这种情况下,我们为该订单创建了两次交易。我们是否希望显示那些失败的交易是一个业务决定,但捕获有价值的数据始终是一个好主意。
还有其他关系吗?好吧,还有更多的关系是可能的,但它们并不实用。例如,我们可以说一个用户有很多交易,那么它们之间应该存在关系。这里需要意识到的是,已经存在一个间接关系:用户 -> 订单 -> 交易。一般来说,这已经足够好了,因为 RDBMS 是连接表的强大工具。其次,创建这种关系意味着向 transactions 表添加 user_id 列。如果我们对每个可能的直接关系都这样做,那么我们会给数据库带来更多的负担(以更多存储的形式,尤其是在使用 UUID 和维护索引的情况下),从而使整个系统更加复杂。当然,如果业务表明他们需要交易数据,并且需要在 1.5 秒内获取,那么我们可能会决定添加这种关系并加快速度(权衡,权衡…)。
现在,女士们先生们,是时候编写实际代码了!
Laravel 模型关系——带有代码的真实示例
本文的下一阶段是关于实践操作——但要以一种有用的方式进行。我们将选择与早期电子商务示例中相同的数据库实体,我们将直接从安装 Laravel 开始,了解 Laravel 中的模型是如何创建和连接的!
当然,我假设您已经设置了开发环境,并且知道如何安装和使用 Composer 来管理依赖项。
$ composer global require laravel/installer -W $ laravel new model-relationships-study
这两个控制台命令安装 Laravel 安装程序(-W 部分用于升级,因为我已经安装了旧版本)。如果你好奇,在撰写本文时,安装的 Laravel 版本是 8.5.9。您应该感到恐慌并升级吗?我不建议这样做,因为我不希望在我们的应用程序上下文中 Laravel 5 和 Laravel 8 之间存在任何重大变化。有些事情发生了变化,并且会影响本文(例如模型工厂),但我认为您能够将代码移植过去。
由于我们已经仔细考虑了数据模型及其关系,因此创建模型的部分将变得微不足道。您还会看到(我现在听起来像是一张坏唱片!)它是如何反映数据库模式的,因为它 100% 依赖于它!
换句话说,我们需要首先为所有将应用于数据库的模型创建迁移(和模型文件)。稍后,我们可以处理模型并处理关系。
那么,我们从哪个模型开始呢?当然是最简单也是连接最少的一个。在我们的例子中,这意味着用户模型。由于 Laravel 附带了这个模型(没有它就无法工作🤣),让我们修改迁移文件,并清理模型以满足我们的简单需求。
这是迁移类:
class CreateUsersTable extends Migration { public function up() { Schema::create('users', function (Blueprint $table) { $table->id(); $table->string('name'); }); } }
因为我们实际上不是在构建一个项目,所以我们不需要输入密码、is_active 等等。我们的 users 表将只有两列:用户的 id 和 name。
接下来,让我们为 categories 创建一个迁移。由于 Laravel 允许我们在单个命令中方便地生成模型,我们将利用它,尽管我们现在不会触及模型文件。
$ php artisan make:model Category -m Model created successfully. Created Migration: 2021_01_26_093326_create_categories_table
这是迁移类:
class CreateCategoriesTable extends Migration { public function up() { Schema::create('categories', function (Blueprint $table) { $table->id(); $table->string('name'); }); } }
如果您对缺少 down() 函数感到惊讶,请不要这样;实际上,您很少最终会使用它,因为删除列或表或更改列类型会导致无法恢复的数据丢失。在开发中,您会发现自己删除了整个数据库,然后重新运行迁移。但是我们跑题了,让我们回过头来处理下一个实体。由于子类别与类别直接相关,我认为下一步这样做是个好主意。
$ php artisan make:model SubCategory -m Model created successfully. Created Migration: 2021_01_26_140845_create_sub_categories_table
好的,现在让我们填写迁移文件:
class CreateSubCategoriesTable extends Migration { public function up() { Schema::create('sub_categories', function (Blueprint $table) { $table->id(); $table->string('name'); $table->unsignedBigInteger('category_id'); $table->foreign('category_id') ->references('id') ->on('categories') ->onDelete('cascade'); }); } }
如您所见,我们在这里添加了一个单独的列,称为 category_id,它将存储 categories 表中的 id。毋庸置疑,这将在数据库级别创建一对多关系。
现在轮到 items 了:
$ php artisan make:model Item -m Model created successfully. Created Migration: 2021_01_26_141421_create_items_table
以及迁移:
class CreateItemsTable extends Migration { public function up() { Schema::create('items', function (Blueprint $table) { $table->id(); $table->string('name'); $table->text('description'); $table->string('type'); $table->unsignedInteger('price'); $table->unsignedInteger('quantity_in_stock'); $table->unsignedBigInteger('sub_category_id'); $table->foreign('sub_category_id') ->references('id') ->on('sub_categories') ->onDelete('cascade'); }); } }
如果您觉得事情应该以不同的方式进行,那也很好。两个人很少会想出完全相同的模式和架构。请注意一件事,这是一种最佳实践:我将 price 存储为整数。
为什么呢?
好吧,人们意识到在数据库端处理浮点除法和所有操作既丑陋又容易出错,因此他们开始以最小货币单位存储 price。例如,如果我们以美元操作,这里的 price 字段将代表美分。在整个系统中,值和计算将以美分为单位;只有在需要向用户显示或通过电子邮件发送 PDF 时,我们才会除以 100 并四舍五入。很聪明,不是吗?
无论如何,请注意一个 item 通过多对一的关系链接到 sub_category。它也链接到 category… 间接地通过 sub_category。我们将看到所有这些练习的切实演示,但现在,我们需要理解这些概念并确保我们 100% 清楚。
接下来是 Order 模型及其迁移:
$ php artisan make:model Order -m Model created successfully. Created Migration: 2021_01_26_144157_create_orders_table
为了简洁起见,我将只包含迁移中的一些重要字段。我的意思是,订单的详细信息可以包含很多内容,但我们将它们限制为少数几个,以便我们可以专注于模型关系的概念。
class CreateOrdersTable extends Migration { public function up() { Schema::create('orders', function (Blueprint $table) { $table->id(); $table->string('status'); $table->unsignedInteger('total_value'); $table->unsignedInteger('taxes'); $table->unsignedInteger('shipping_charges'); $table->unsignedBigInteger('user_id'); $table->foreign('user_id') ->references('id') ->on('users') ->onDelete('cascade'); }); } }
看起来不错,但是,等一下!此订单中的 items 在哪里?正如我们之前所确定的,orders 和 items 之间存在多对多关系,因此简单的外键不起作用。解决方案是使用所谓的“连接表”或“中间表”。换句话说,我们需要一个连接表来存储 orders 和 items 之间的多对多映射。现在,在 Laravel 的世界中,我们遵循一个内置的约定来节省时间:如果我们使用两个表名的单数形式创建一个新表,按照字典顺序排列它们,并用下划线连接,Laravel 将自动将其识别为连接表。
在我们的例子中,连接表将被命名为 item_order(在字典中,“item”一词在 “order” 之前)。此外,如前所述,此连接表通常仅包含两列,即每个表的外键。
我们可以在这里创建一个模型 + 迁移,但该模型永远不会被使用,因为它更像是一个元数据。因此,我们在 Laravel 中创建一个新的迁移,并告诉它情况:
$ php artisan make:migration create_item_order_table --create="item_order" Created Migration: 2021_01_27_093127_create_item_order_table
这将生成一个新的迁移,我们将对其进行如下更改:
class CreateItemOrderTable extends Migration { public function up() { Schema::create('item_order', function (Blueprint $table) { $table->unsignedBigInteger('order_id'); $table->foreign('order_id') ->references('id') ->on('orders') ->onDelete('cascade'); $table->unsignedBigInteger('item_id'); $table->foreign('item_id') ->references('id') ->on('items') ->onDelete('cascade'); }); } }</