ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
<article><h1>Eloquent: 关联</h1><ul><li><a href="#introduction">简介</a></li><li><a href="#defining-relationships">定义关联</a><ul><li><a href="#one-to-one">一对一</a></li><li><a href="#one-to-many">一对多</a></li><li><a href="#one-to-many-inverse">一对多(反向关联)</a></li><li><a href="#many-to-many">多对多</a></li><li><a href="#has-many-through">远层一对多</a></li><li><a href="#polymorphic-relations">多态关联</a></li><li><a href="#many-to-many-polymorphic-relations">多态多对多关联</a></li></ul></li><li><a href="#querying-relations">查找关联</a><ul><li><a href="#relationship-methods-vs-dynamic-properties">关联方法和动态属性</a></li><li><a href="#querying-relationship-existence">查找关联是否存在</a></li><li><a href="#counting-related-models">关联数据计数</a></li></ul></li><li><a href="#eager-loading">预加载</a><ul><li><a href="#constraining-eager-loads">预加载条件限制</a></li><li><a href="#lazy-eager-loading">延迟预加载</a></li></ul></li><li><a href="#inserting-and-updating-related-models">插入 &amp; 更新关联模型</a><ul><li><a href="#the-save-method"><code class=" language-php">save</code> 方法</a></li><li><a href="#the-create-method"><code class=" language-php">create</code> 方法</a></li><li><a href="#updating-belongs-to-relationships">更新「从属」关联</a></li><li><a href="#updating-many-to-many-relationships">多对多关联</a></li></ul></li><li><a href="#touching-parent-timestamps">连动父级时间戳</a></li></ul><p><a name="introduction"></a></p><h2><a href="#introduction">简介</a></h2><p>数据表之间经常会互相进行关联。例如,一篇博客文章可能会有多条评论,或是一张订单可能对应一个下单客户。Eloquent 让管理和处理这些关联变得很容易,同时也支持多种类型的关联:</p><ul><li><a href="#one-to-one">一对一</a></li><li><a href="#one-to-many">一对多</a></li><li><a href="#many-to-many">多对多</a></li><li><a href="#has-many-through">远层一对多</a></li><li><a href="#polymorphic-relations">多态关联</a></li><li><a href="#many-to-many-polymorphic-relations">多态多对多关联</a></li></ul><p><a name="defining-relationships"></a></p><h2><a href="#defining-relationships">定义关联</a></h2><p>你可在 Eloquent 模型类内中,把 Eloquent 关联定义成方法(methods)。因为,关联就像 Eloquent 模型一样,也可以作为强大的 <a href="/docs/5.4/queries">查询语句构造器</a>,定义关联为方法,为其提供了强而有力的链式调用及查找功能。例如,我们可以在 <code class=" language-php">posts</code> 关联的链式调用中附加一个约束条件:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">posts<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">where<span class="token punctuation">(</span></span><span class="token string">'active'</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>不过,在深入了解使用关联之前,先让我们来学习如何定义每个类型:</p><p><a name="one-to-one"></a></p><h3>一对一</h3><p>「一对一」关联是一个非常基本的关联关系。举个例子,一个 <code class=" language-php">User</code> 模型会关联一个 <code class=" language-php">Phone</code> 模型。为了定义这种关联关系,我们需要在 <code class=" language-php">User</code> 模型中写一个 <code class=" language-php">phone</code> 方法。且 <code class=" language-php">phone</code> 方法应该调用 <code class=" language-php">hasOne</code> 方法并返回其结果:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Database<span class="token punctuation">\</span>Eloquent<span class="token punctuation">\</span>Model</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 获取与用户关联的电话号码 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">phone<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">hasOne<span class="token punctuation">(</span></span><span class="token string">'App\Phone'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>第一个传到 <code class=" language-php">hasOne</code> 方法里的参数是关联模型的类名。一旦定义好两者之间关联,我们就可以通过使用 Eloquent 的动态属性来获取关联记录。动态属性允许你访问关联方法,如同他们是定义在模型中的属性:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$phone</span> <span class="token operator">=</span> <span class="token scope">User<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">phone</span><span class="token punctuation">;</span></code></pre><p>Eloquent 会假设对应关联的外键名称是基于模型名称的。在这个例子里,它会自动假设 <code class=" language-php">Phone</code> 模型拥有 <code class=" language-php">user_id</code> 外键。如果你想要重写这个约定,则可以传入第二个参数到 <code class=" language-php">hasOne</code> 方法里。</p><pre class=" language-php"><code class=" language-php"><span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">hasOne<span class="token punctuation">(</span></span><span class="token string">'App\Phone'</span><span class="token punctuation">,</span> <span class="token string">'foreign_key'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>此外,Eloquent 假设外键会和上层模型的 <code class=" language-php">id</code> 字段(或者自定义的 <code class=" language-php"><span class="token variable">$primaryKey</span></code>)的值相匹配。换句话说,Eloquent 会寻找用户的 <code class=" language-php">id</code> 字段与 <code class=" language-php">Phone</code> 模型的 <code class=" language-php">user_id</code> 字段的值相同的纪录。如果你想让关联使用 <code class=" language-php">id</code> 以外的值,则可以传递第三个参数至 <code class=" language-php">hasOne</code> 方法来指定你自定义的键:</p><pre class=" language-php"><code class=" language-php"><span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">hasOne<span class="token punctuation">(</span></span><span class="token string">'App\Phone'</span><span class="token punctuation">,</span> <span class="token string">'foreign_key'</span><span class="token punctuation">,</span> <span class="token string">'local_key'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h4>定义反向关联</h4><p>所以,我们可以从 <code class=" language-php">User</code> 模型访问到 <code class=" language-php">Phone</code> 模型。现在,让我们在 <code class=" language-php">Phone</code> 模型上定义一个关联,此关联能够让我们访问拥有此电话的 <code class=" language-php">User</code> 模型。我们可以定义与 <code class=" language-php">hasOne</code> 关联相对应的 <code class=" language-php">belongsTo</code> 方法:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Database<span class="token punctuation">\</span>Eloquent<span class="token punctuation">\</span>Model</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">Phone</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 获取拥有该电话的用户模型。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">user<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">belongsTo<span class="token punctuation">(</span></span><span class="token string">'App\User'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>在上述例子中,Eloquent 会尝试匹配 <code class=" language-php">Phone</code> 模型的 <code class=" language-php">user_id</code> 至 <code class=" language-php">User</code> 模型的 <code class=" language-php">id</code>。Eloquent 判断的默认外键名称参考自关联模型的方法名称,并会在方法名称后面加上 <code class=" language-php">_id</code>。当然,如果 <code class=" language-php">Phone</code> 模型的外键不是 <code class=" language-php">user_id</code>,则可以传递自定义键名作为 <code class=" language-php">belongsTo</code> 方法的第二个参数:</p><pre class=" language-php"><code class=" language-php"><span class="token comment" spellcheck="true">/** * 获取拥有该电话的用户模型。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">user<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">belongsTo<span class="token punctuation">(</span></span><span class="token string">'App\User'</span><span class="token punctuation">,</span> <span class="token string">'foreign_key'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>如果你的父级模型不是使用 <code class=" language-php">id</code> 作为主键,或是希望以不同的字段来连接下层模型,则可以传递第三个参数至 <code class=" language-php">belongsTo</code> 方法来指定父级数据表的自定义键:</p><pre class=" language-php"><code class=" language-php"><span class="token comment" spellcheck="true">/** * 获取拥有该电话的用户模型。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">user<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">belongsTo<span class="token punctuation">(</span></span><span class="token string">'App\User'</span><span class="token punctuation">,</span> <span class="token string">'foreign_key'</span><span class="token punctuation">,</span> <span class="token string">'other_key'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p><a name="one-to-many"></a></p><h3>一对多</h3><p>一个「一对多」关联用于定义单个模型拥有任意数量的其它关联模型。例如,一篇博客文章可能会有无限多个评论。就像其它的 Eloquent 关联一样,可以通过在 Eloquent 模型中写一个函数来定义一对多关联:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Database<span class="token punctuation">\</span>Eloquent<span class="token punctuation">\</span>Model</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">Post</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 获取这篇博文下的所有评论。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">comments<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">hasMany<span class="token punctuation">(</span></span><span class="token string">'App\Comment'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>切记,Eloquent 会自动判断 <code class=" language-php">Comment</code> 模型上正确的外键字段。按约定来说,Eloquent 会取用自身模型的名称的「Snake Case」,并在后方加上 <code class=" language-php">_id</code>。所以,以此例来说,Eloquent 会假设 <code class=" language-php">Comment</code> 模型的外键是 <code class=" language-php">post_id</code>。</p><p>一旦关联被定义,则可以通过 <code class=" language-php">comments</code> 属性来访问评论的集合。切记,因为 Eloquent 提供了「动态属性」,因此我们可以对关联方法进行访问,就像他们是在模型中定义的属性一样:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$comments</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Post<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">comments</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token variable">$comments</span> <span class="token keyword">as</span> <span class="token variable">$comment</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // </span><span class="token punctuation">}</span></code></pre><p>当然,因为所有的关联也都提供了查询语句构造器的功能,因此你可以对获取到的评论进一步增加条件,通过调用 <code class=" language-php">comments</code> 方法然后在该方法后面链式调用查询条件:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$comments</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Post<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">comments<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">where<span class="token punctuation">(</span></span><span class="token string">'title'</span><span class="token punctuation">,</span> <span class="token string">'foo'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">first<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>就像 <code class=" language-php">hasOne</code> 方法,你也可以通过传递额外的参数至 <code class=" language-php">hasMany</code> 方法来重写外键与本地键:</p><pre class=" language-php"><code class=" language-php"><span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">hasMany<span class="token punctuation">(</span></span><span class="token string">'App\Comment'</span><span class="token punctuation">,</span> <span class="token string">'foreign_key'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">hasMany<span class="token punctuation">(</span></span><span class="token string">'App\Comment'</span><span class="token punctuation">,</span> <span class="token string">'foreign_key'</span><span class="token punctuation">,</span> <span class="token string">'local_key'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p><a name="one-to-many-inverse"></a></p><h3>一对多(反向关联)</h3><p>现在我们已经能访问到所有文章的评论,让我们来接着定义一个通过评论访问所属文章的关联。若要定义相对于 <code class=" language-php">hasMany</code> 的关联,可在子级模型定义一个叫做 <code class=" language-php">belongsTo</code> 方法的关联函数:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Database<span class="token punctuation">\</span>Eloquent<span class="token punctuation">\</span>Model</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">Comment</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 获取该评论所属的文章模型。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">post<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">belongsTo<span class="token punctuation">(</span></span><span class="token string">'App\Post'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>一旦关联被定义之后,则可以通过 <code class=" language-php">post</code> 的「动态属性」来获取 <code class=" language-php">Comment</code> 模型相对应的 <code class=" language-php">Post</code> 模型:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$comment</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Comment<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">echo</span> <span class="token variable">$comment</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">post</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">title</span><span class="token punctuation">;</span></code></pre><p>在上述例子中,Eloquent 会尝试将 <code class=" language-php">Comment</code> 模型的 <code class=" language-php">post_id</code> 与 <code class=" language-php">Post</code> 模型的 <code class=" language-php">id</code> 进行匹配。Eloquent 判断的默认外键名称参考自关联模型的方法,并在方法名称后面加上 <code class=" language-php">_id</code>。当然,如果 <code class=" language-php">Comment</code> 模型的外键不是 <code class=" language-php">post_id</code>,则可以传递自定义键名作为 <code class=" language-php">belongsTo</code> 方法的第二个参数:</p><pre class=" language-php"><code class=" language-php"><span class="token comment" spellcheck="true">/** * 获取该评论所属的文章模型。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">post<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">belongsTo<span class="token punctuation">(</span></span><span class="token string">'App\Post'</span><span class="token punctuation">,</span> <span class="token string">'foreign_key'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>如果你的父级模型不是使用 <code class=" language-php">id</code> 作为主键,或是你希望以不同的字段来连接下层模型,则可以传递第三个参数给 <code class=" language-php">belongsTo</code> 方法来指定上层数据表的自定义键:</p><pre class=" language-php"><code class=" language-php"><span class="token comment" spellcheck="true">/** * 获取该评论所属的文章模型。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">post<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">belongsTo<span class="token punctuation">(</span></span><span class="token string">'App\Post'</span><span class="token punctuation">,</span> <span class="token string">'foreign_key'</span><span class="token punctuation">,</span> <span class="token string">'other_key'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p><a name="many-to-many"></a></p><h3>多对多</h3><p>「多对多」关联要稍微比 <code class=" language-php">hasOne</code> 及 <code class=" language-php">hasMany</code> 关联复杂。如,一个用户可能拥有多种身份,而一种身份能同时被多个用户拥有。举例来说,很多用户都拥有「管理员」的身份。要定义这种关联,需要使用三个数据表:<code class=" language-php">users</code>、<code class=" language-php">roles</code> 和 <code class=" language-php">role_user</code>。<code class=" language-php">role_user</code> 表命名是以相关联的两个模型数据表来依照字母顺序命名,并包含了 <code class=" language-php">user_id</code> 和 <code class=" language-php">role_id</code> 字段。</p><p>多对多关联通过编写一个在自身 Eloquent 类调用的 <code class=" language-php">belongsToMany</code> 的方法来定义。举个例子,让我们在 <code class=" language-php">User</code> 模型中定义 <code class=" language-php">roles</code> 方法:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Database<span class="token punctuation">\</span>Eloquent<span class="token punctuation">\</span>Model</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 属于该用户的身份。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">roles<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">belongsToMany<span class="token punctuation">(</span></span><span class="token string">'App\Role'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>一旦关联被定义,则可以使用 <code class=" language-php">roles</code> 动态属性来访问用户的身份:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$user</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>User<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">roles</span> <span class="token keyword">as</span> <span class="token variable">$role</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // </span><span class="token punctuation">}</span></code></pre><p>当然,就如所有其它的关联类型一样,你也可以调用 <code class=" language-php">roles</code> 方法并在该关联之后链式调用查询条件:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$roles</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>User<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">roles<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">orderBy<span class="token punctuation">(</span></span><span class="token string">'name'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>如前文提到那样,Eloquent 会合并两个关联模型的名称并依照字母顺序命名。当然你也可以随意重写这个约定。可通过传递第二个参数至 <code class=" language-php">belongsToMany</code> 方法来实现:</p><pre class=" language-php"><code class=" language-php"><span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">belongsToMany<span class="token punctuation">(</span></span><span class="token string">'App\Role'</span><span class="token punctuation">,</span> <span class="token string">'role_user'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>除了自定义合并数据表的名称,你也可以通过传递额外参数至 <code class=" language-php">belongsToMany</code> 方法来自定义数据表里的键的字段名称。第三个参数是你定义在关联中的模型外键名称,而第四个参数则是你要合并的模型外键名称:</p><pre class=" language-php"><code class=" language-php"><span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">belongsToMany<span class="token punctuation">(</span></span><span class="token string">'App\Role'</span><span class="token punctuation">,</span> <span class="token string">'role_user'</span><span class="token punctuation">,</span> <span class="token string">'user_id'</span><span class="token punctuation">,</span> <span class="token string">'role_id'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h4>定义相对的关联</h4><p>要定义相对于多对多的关联,只需简单的放置另一个名为 <code class=" language-php">belongsToMany</code> 的方法到你关联的模型上。让我们接着以用户身份为例,在 <code class=" language-php">Role</code> 模型中定义 <code class=" language-php">users</code> 方法:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Database<span class="token punctuation">\</span>Eloquent<span class="token punctuation">\</span>Model</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">Role</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 属于该身份的用户。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">users<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">belongsToMany<span class="token punctuation">(</span></span><span class="token string">'App\User'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>如你所见,此定义除了简单的参考 <code class=" language-php">App\<span class="token package">User</span></code> 模型外,与 <code class=" language-php">User</code> 的对应完全相同。因为我们重复使用了 <code class=" language-php">belongsToMany</code> 方法,当定义相对于多对多的关联时,所有常用的自定义数据表与键的选项都是可用的。</p><h4>获取中间表字段</h4><p>正如你所知,要操作多对多关联需要一个中间数据表。Eloquent 提供了一些有用的方法来和这张表进行交互。例如,假设 <code class=" language-php">User</code> 对象关联到很多的 <code class=" language-php">Role</code> 对象。访问这些关联对象时,我们可以在模型中使用 <code class=" language-php">pivot</code> 属性来访问中间数据表的数据:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$user</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>User<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">roles</span> <span class="token keyword">as</span> <span class="token variable">$role</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">echo</span> <span class="token variable">$role</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">pivot</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">created_at</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>注意我们取出的每个 <code class=" language-php">Role</code> 模型对象,都会被自动赋予 <code class=" language-php">pivot</code> 属性。此属性代表中间表的模型,它可以像其它的 Eloquent 模型一样被使用。</p><p>默认情况下,<code class=" language-php">pivot</code> 对象只提供模型的键。如果你的 pivot 数据表包含了其它的属性,则可以在定义关联方法时指定那些字段:</p><pre class=" language-php"><code class=" language-php"><span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">belongsToMany<span class="token punctuation">(</span></span><span class="token string">'App\Role'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">withPivot<span class="token punctuation">(</span></span><span class="token string">'column1'</span><span class="token punctuation">,</span> <span class="token string">'column2'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>如果你想要中间表自动维护 <code class=" language-php">created_at</code> 和 <code class=" language-php">updated_at</code> 时间戳,可在定义关联方法时加上 <code class=" language-php">withTimestamps</code> 方法:</p><pre class=" language-php"><code class=" language-php"><span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">belongsToMany<span class="token punctuation">(</span></span><span class="token string">'App\Role'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">withTimestamps<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h4>使用中间表来过滤关联数据</h4><p>你可以使用 <code class=" language-php">wherePivot</code> 和 <code class=" language-php">wherePivotIn</code> 来增加中间件表过滤条件:</p><pre class=" language-php"><code class=" language-php"><span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">belongsToMany<span class="token punctuation">(</span></span><span class="token string">'App\Role'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">wherePivot<span class="token punctuation">(</span></span><span class="token string">'approved'</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">belongsToMany<span class="token punctuation">(</span></span><span class="token string">'App\Role'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">wherePivotIn<span class="token punctuation">(</span></span><span class="token string">'priority'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h4>定义自定义中间表模型</h4><p>如果你想定义一个自定义模型来表示你中间表的关联,则可以在定义关联时调用 <code class=" language-php">using</code> 方法。所有用来表示中间表关联的自定义模型必须扩展自 <code class=" language-php">Illuminate\<span class="token package">Database<span class="token punctuation">\</span>Eloquent<span class="token punctuation">\</span>Relations<span class="token punctuation">\</span>Pivot</span></code> 类:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Database<span class="token punctuation">\</span>Eloquent<span class="token punctuation">\</span>Model</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">Role</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 属于该身份的用户。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">users<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">belongsToMany<span class="token punctuation">(</span></span><span class="token string">'App\User'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">using<span class="token punctuation">(</span></span><span class="token string">'App\UserRole'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p><a name="has-many-through"></a></p><h3>远层一对多</h3><p>「远层一对多」提供了方便简短的方法来通过中间的关联获取远层的关联。例如,一个 <code class=" language-php">Country</code> 模型可能通过中间的 <code class=" language-php">Users</code> 模型关联到多个 <code class=" language-php">Posts</code> 模型。让我们来看看定义此种关联的数据表:</p><pre class=" language-php"><code class=" language-php">countries id <span class="token operator">-</span> integer name <span class="token operator">-</span> string users id <span class="token operator">-</span> integer country_id <span class="token operator">-</span> integer name <span class="token operator">-</span> string posts id <span class="token operator">-</span> integer user_id <span class="token operator">-</span> integer title <span class="token operator">-</span> string</code></pre><p>虽然 <code class=" language-php">posts</code> 本身不包含 <code class=" language-php">country_id</code> 字段,但 <code class=" language-php">hasManyThrough</code> 关联通过 <code class=" language-php"><span class="token variable">$country</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">posts</span></code> 来让我们可以访问一个国家的文章。若运行此查找,则 Eloquent 会检查中间表 <code class=" language-php">users</code> 的 <code class=" language-php">country_id</code>。在找到匹配的用户 ID 后,就会在 <code class=" language-php">posts</code> 数据表中使用它们来进行查找。</p><p>现在我们已经检查完了关联的数据表结构,让我们来接着在 <code class=" language-php">Country</code> 模型中定义它:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Database<span class="token punctuation">\</span>Eloquent<span class="token punctuation">\</span>Model</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">Country</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 获取该国家的所有文章。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">posts<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">hasManyThrough<span class="token punctuation">(</span></span><span class="token string">'App\Post'</span><span class="token punctuation">,</span> <span class="token string">'App\User'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p><code class=" language-php">hasManyThrough</code> 方法的第一个参数为我们希望最终访问的模型名称,而第二个参数为中间模型的名称。</p><p>当运行关联查找时,通常会使用 Eloquent 的外键约定。如果你想要自定义关联的键,则可以将它们传递至 <code class=" language-php">hasManyThrough</code> 方法的第三与第四个参数。第三个参数为中间模型的外键名称,而第四个参数为最终模型的外键名称,第五个参数则为本地键。</p><pre class=" language-php"><code class=" language-php"><span class="token keyword">class</span> <span class="token class-name">Country</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">posts<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">hasManyThrough<span class="token punctuation">(</span></span> <span class="token string">'App\Post'</span><span class="token punctuation">,</span> <span class="token string">'App\User'</span><span class="token punctuation">,</span> <span class="token string">'country_id'</span><span class="token punctuation">,</span> <span class="token string">'user_id'</span><span class="token punctuation">,</span> <span class="token string">'id'</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p><a name="polymorphic-relations"></a></p><h3>多态关联</h3><h4>数据表结构</h4><p>多态关联允许一个模型在单个关联中从属一个以上其它模型。举个例子,想象一下使用你应用的用户可以「评论」文章和视频。使用多态关联关系,您可以使用一个 <code class=" language-php">comments</code> 数据表就可以同时满足两个使用场景。首先,让我们观察一下用来创建这关联的数据表结构:</p><pre class=" language-php"><code class=" language-php">posts id <span class="token operator">-</span> integer title <span class="token operator">-</span> string body <span class="token operator">-</span> text videos id <span class="token operator">-</span> integer title <span class="token operator">-</span> string url <span class="token operator">-</span> string comments id <span class="token operator">-</span> integer body <span class="token operator">-</span> text commentable_id <span class="token operator">-</span> integer commentable_type <span class="token operator">-</span> string</code></pre><p>有两个需要注意的字段是 <code class=" language-php">comments</code> 表中的 <code class=" language-php">commentable_id</code> 和 <code class=" language-php">commentable_type</code>。其中, <code class=" language-php">commentable_id</code> 用于存放文章或者视频的 id ,而 <code class=" language-php">commentable_type</code> 用于存放所属模型的类名。注意的是, <code class=" language-php">commentable_type</code> 是当我们访问 <code class=" language-php">commentable</code> 关联时, ORM 用于判断所属的模型是哪个「类型」。</p><h4>模型结构</h4><p>接着,让我们来查看创建这种关联所需的模型定义:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Database<span class="token punctuation">\</span>Eloquent<span class="token punctuation">\</span>Model</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">Comment</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 获取所有拥有的 commentable 模型。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">commentable<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">morphTo<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">Post</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 获取所有文章的评论。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">comments<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">morphMany<span class="token punctuation">(</span></span><span class="token string">'App\Comment'</span><span class="token punctuation">,</span> <span class="token string">'commentable'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">Video</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 获取所有视频的评论。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">comments<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">morphMany<span class="token punctuation">(</span></span><span class="token string">'App\Comment'</span><span class="token punctuation">,</span> <span class="token string">'commentable'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><h4>获取多态关联</h4><p>一旦你的数据表及模型被定义,则可以通过模型来访问关联。例如,若要访问谋篇文章的所有评论,则可以简单的使用 <code class=" language-php">comments</code> 动态属性:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$post</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Post<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token variable">$post</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">comments</span> <span class="token keyword">as</span> <span class="token variable">$comment</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // </span><span class="token punctuation">}</span></code></pre><p>你也可以从多态模型的多态关联中,通过访问调用 <code class=" language-php">morphTo</code> 的方法名称来获取拥有者,也就是此例子中 <code class=" language-php">Comment</code> 模型的 <code class=" language-php">commentable</code> 方法。所以,我们可以使用动态属性来访问这个方法:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$comment</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Comment<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$commentable</span> <span class="token operator">=</span> <span class="token variable">$comment</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">commentable</span><span class="token punctuation">;</span></code></pre><p><code class=" language-php">Comment</code> 模型的 <code class=" language-php">commentable</code> 关联会返回 <code class=" language-php">Post</code> 或 <code class=" language-php">Video</code> 实例,这取决于评论所属模型的类型。</p><h4>自定义多态关联的类型字段</h4><p>默认情况下,Laravel 会使用「包含命名空间的类名」作为多态表的类型区分,例如,<code class=" language-php">Comment</code> 属于 <code class=" language-php">Post</code> 或者 <code class=" language-php">Video</code> , <code class=" language-php">commentable_type</code> 的默认值可以分别是 <code class=" language-php">App\<span class="token package">Post</span></code> 或者 <code class=" language-php">App\<span class="token package">Video</span></code> 。然而,你也许会想使用应用中的内部结构来对数据库进行解耦。在这个例子中,你可以定义一个「多态对照表」来指引 Eloquent 对各个模型使用自定义名称而非类名:</p><pre class=" language-php"><code class=" language-php"><span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Database<span class="token punctuation">\</span>Eloquent<span class="token punctuation">\</span>Relations<span class="token punctuation">\</span>Relation</span><span class="token punctuation">;</span> <span class="token scope">Relation<span class="token punctuation">::</span></span><span class="token function">morphMap<span class="token punctuation">(</span></span><span class="token punctuation">[</span> <span class="token string">'posts'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token scope">App<span class="token punctuation">\</span>Post<span class="token punctuation">::</span></span><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token string">'videos'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token scope">App<span class="token punctuation">\</span>Video<span class="token punctuation">::</span></span><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>你需要在你自己的 <code class=" language-php">AppServiceProvider</code> 中的 <code class=" language-php">boot</code> 函数注册这个 <code class=" language-php">morphMap</code> ,或者创建一个独立且满足你要求的服务提供者。</p><p><a name="many-to-many-polymorphic-relations"></a></p><h3>多态多对多关联</h3><h4>数据表结构</h4><p>除了一般的多态关联,你也可以定义「多对多」的多态关联。例如,博客的 <code class=" language-php">Post</code> 和 <code class=" language-php">Video</code> 模型可以共用多态关联至 <code class=" language-php">Tag</code> 模型。使用多对多的多态关联能够让你的博客文章及图片共用独立标签的单个列表。首先,让我们先来查看数据表结构:</p><pre class=" language-php"><code class=" language-php">posts id <span class="token operator">-</span> integer name <span class="token operator">-</span> string videos id <span class="token operator">-</span> integer name <span class="token operator">-</span> string tags id <span class="token operator">-</span> integer name <span class="token operator">-</span> string taggables tag_id <span class="token operator">-</span> integer taggable_id <span class="token operator">-</span> integer taggable_type <span class="token operator">-</span> string</code></pre><h4>模型结构</h4><p>接着,我们已经准备好定义模型的关联。<code class=" language-php">Post</code> 及 <code class=" language-php">Video</code> 模型都会拥有 <code class=" language-php">tags</code> 方法,并在该方法内调用自身 Eloquent 类的 <code class=" language-php">morphToMany</code> 方法:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Database<span class="token punctuation">\</span>Eloquent<span class="token punctuation">\</span>Model</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">Post</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 获取该文章的所有标签。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">tags<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">morphToMany<span class="token punctuation">(</span></span><span class="token string">'App\Tag'</span><span class="token punctuation">,</span> <span class="token string">'taggable'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><h4>定义相对的关联</h4><p>然后,在 <code class=" language-php">Tag</code> 模型上,你必须为每个要关联的模型定义一个方法。因此,在这个例子中,我们需要定义一个 <code class=" language-php">posts</code> 方法及一个 <code class=" language-php">videos</code> 方法:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Database<span class="token punctuation">\</span>Eloquent<span class="token punctuation">\</span>Model</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">Tag</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 获取所有被赋予该标签的文章。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">posts<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">morphedByMany<span class="token punctuation">(</span></span><span class="token string">'App\Post'</span><span class="token punctuation">,</span> <span class="token string">'taggable'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment" spellcheck="true">/** * 获取所有被赋予该标签的图片。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">videos<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">morphedByMany<span class="token punctuation">(</span></span><span class="token string">'App\Video'</span><span class="token punctuation">,</span> <span class="token string">'taggable'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><h4>获取关联</h4><p>一旦你的数据表及模型被定义,则可以通过你的模型来访问关联。例如,你可以简单的使用 <code class=" language-php">tags</code> 动态属性来访问文章的所有标签:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$post</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Post<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token variable">$post</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">tags</span> <span class="token keyword">as</span> <span class="token variable">$tag</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // </span><span class="token punctuation">}</span></code></pre><p>你也可以从多态模型的多态关联中,通过访问运行调用 <code class=" language-php">morphedByMany</code> 的方法名称来获取拥有者。在此例子中,就是 <code class=" language-php">Tag</code> 模型的 <code class=" language-php">posts</code> 或 <code class=" language-php">videos</code> 方法。因此,你可以通过访问使用动态属性来访问这个方法:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$tag</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Tag<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token variable">$tag</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">videos</span> <span class="token keyword">as</span> <span class="token variable">$video</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // </span><span class="token punctuation">}</span></code></pre><p><a name="querying-relations"></a></p><h2><a href="#querying-relations">查找关联</a></h2><p>所有类型的 Eloquent 关联都是通过方法来定义的,你可以通过调用这些方法来获得关联的一个实例,而不需要实际运行关联的查找。此外,所有类型的 Eloquent 关联也提供了 <a href="/docs/5.4/queries">查询语句构造器</a> 的功能,让你能够在数据库运行该 SQL 前,在关联查找后面链式调用条件。</p><p>例如,假设有一个博客系统,其中 <code class=" language-php">User</code> 模型拥有许多关联的 <code class=" language-php">Post</code> 模型:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Database<span class="token punctuation">\</span>Eloquent<span class="token punctuation">\</span>Model</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 获取该用户的所有文章。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">posts<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">hasMany<span class="token punctuation">(</span></span><span class="token string">'App\Post'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>你可以查找 <code class=" language-php">posts</code> 关联并增加额外的条件至关联,像这样:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$user</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>User<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">posts<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">where<span class="token punctuation">(</span></span><span class="token string">'active'</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>你可以在关联中使用 <a href="/docs/5.4/queries">查询语句构造器</a> 中的任意方法,所以,欢迎查阅查询语句构造器的相关文档以便了解那些有助于你实现的方法。</p><p><a name="relationship-methods-vs-dynamic-properties"></a></p><h3>关联方法与动态属性</h3><p>如果你不需要增加额外的条件至 Eloquent 的关联查找,则可以简单的像访问属性一样来访问关联。例如我们刚刚的 <code class=" language-php">User</code> 及 <code class=" language-php">Post</code> 模型示例,我们可以像这样来访问所有用户的文章:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$user</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>User<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">posts</span> <span class="token keyword">as</span> <span class="token variable">$post</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true"> // </span><span class="token punctuation">}</span></code></pre><p>动态属性是「延迟加载」的,意味着它们只会在被访问的时候才加载关联数据。正因为如此,开发者通常需要使用 <a href="#eager-loading">预加载</a> 来预先加载关联数据,关联数据将会在模型加载后被访问。预加载能有效减少你的 SQL 查询语句。</p><p><a name="querying-relationship-existence"></a></p><h3>查找关联是否存在</h3><p>当访问模型的纪录时,你可能希望根据关联的存在来对结果进行限制。比方说你想获取博客中那些至少拥有一条评论的文章。则可以通过传递名称至关联的 <code class=" language-php">has</code> 方法来实现:</p><pre class=" language-php"><code class=" language-php"><span class="token comment" spellcheck="true">// 获取那些至少拥有一条评论的文章... </span><span class="token variable">$posts</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Post<span class="token punctuation">::</span></span><span class="token function">has<span class="token punctuation">(</span></span><span class="token string">'comments'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>你也可以制定运算符及数量来进一步自定义查找:</p><pre class=" language-php"><code class=" language-php"><span class="token comment" spellcheck="true">// 获取所有至少有三条评论的文章... </span><span class="token variable">$posts</span> <span class="token operator">=</span> <span class="token scope">Post<span class="token punctuation">::</span></span><span class="token function">has<span class="token punctuation">(</span></span><span class="token string">'comments'</span><span class="token punctuation">,</span> <span class="token string">'&gt;='</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>也可以使用「点」符号来构造嵌套的 has 语句。例如,你可能想获取那些至少有一条评论被投票的文章:</p><pre class=" language-php"><code class=" language-php"><span class="token comment" spellcheck="true">// 获取所有至少有一条评论被评分的文章... </span><span class="token variable">$posts</span> <span class="token operator">=</span> <span class="token scope">Post<span class="token punctuation">::</span></span><span class="token function">has<span class="token punctuation">(</span></span><span class="token string">'comments.votes'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>如果你想要更高级的用法,则可以使用 <code class=" language-php">whereHas</code> 和 <code class=" language-php">orWhereHas</code> 方法,在 has 查找里设置「where」条件。此方法可以让你增加自定义条件至关联条件中,例如对评论内容进行检查:</p><pre class=" language-php"><code class=" language-php"><span class="token comment" spellcheck="true">// 获取那些至少有一条评论包含 foo 的文章 </span><span class="token variable">$posts</span> <span class="token operator">=</span> <span class="token scope">Post<span class="token punctuation">::</span></span><span class="token function">whereHas<span class="token punctuation">(</span></span><span class="token string">'comments'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token variable">$query</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token variable">$query</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">where<span class="token punctuation">(</span></span><span class="token string">'content'</span><span class="token punctuation">,</span> <span class="token string">'like'</span><span class="token punctuation">,</span> <span class="token string">'foo%'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p><a name="counting-related-models"></a></p><h3>关联数据计数</h3><p>如果你想对关联数据进行计数但又不想再发起单独的 SQL 请求,你可以使用 <code class=" language-php">withCount</code> 方法,此方法会在你的结果集中增加一个 <code class=" language-php"><span class="token punctuation">{</span>relation<span class="token punctuation">}</span>_count</code> 字段:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$posts</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Post<span class="token punctuation">::</span></span><span class="token function">withCount<span class="token punctuation">(</span></span><span class="token string">'comments'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token variable">$posts</span> <span class="token keyword">as</span> <span class="token variable">$post</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">echo</span> <span class="token variable">$post</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">comments_count</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>你还可以像在查询语句中添加约束一样,获取多重关联的「计数」。</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$posts</span> <span class="token operator">=</span> <span class="token scope">Post<span class="token punctuation">::</span></span><span class="token function">withCount<span class="token punctuation">(</span></span><span class="token punctuation">[</span><span class="token string">'votes'</span><span class="token punctuation">,</span> <span class="token string">'comments'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token variable">$query</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token variable">$query</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">where<span class="token punctuation">(</span></span><span class="token string">'content'</span><span class="token punctuation">,</span> <span class="token string">'like'</span><span class="token punctuation">,</span> <span class="token string">'foo%'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">echo</span> <span class="token variable">$posts</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">votes_count</span><span class="token punctuation">;</span> <span class="token keyword">echo</span> <span class="token variable">$posts</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">comments_count</span><span class="token punctuation">;</span></code></pre><p><a name="eager-loading"></a></p><h2><a href="#eager-loading">预加载</a></h2><p>当通过属性访问 Eloquent 关联时,该关联数据会被「延迟加载」。意味着该关联数据只有在你使用属性访问它时才会被加载。不过,Eloquent 可以在你查找上层模型时「预加载」关联数据。预加载避免了 N + 1 查找的问题。要说明 N + 1 查找的问题,可试想一个关联到 <code class=" language-php">Author</code> 的 <code class=" language-php">Book</code> 模型,如下所示:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Database<span class="token punctuation">\</span>Eloquent<span class="token punctuation">\</span>Model</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">Book</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 获取编写该书的作者。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">author<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">belongsTo<span class="token punctuation">(</span></span><span class="token string">'App\Author'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>现在,让我们来获取所有书籍及其作者的数据:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$books</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Book<span class="token punctuation">::</span></span><span class="token function">all<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token variable">$books</span> <span class="token keyword">as</span> <span class="token variable">$book</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">echo</span> <span class="token variable">$book</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">author</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">name</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>上方的循环会运行一次查找并取回所有数据表上的书籍,接着每本书会运行一次查找作者的操作。因此,若存在着 25 本书,则循环就会执行 26 次查找:1 次是查找所有书籍,其它 25 次则是在查找每本书的作者。</p><p>很幸运地,我们可以使用预加载来将查找的操作减少至 2 次。可在查找时使用 with 方法来指定想要预加载的关联数据:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$books</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Book<span class="token punctuation">::</span></span><span class="token function">with<span class="token punctuation">(</span></span><span class="token string">'author'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token variable">$books</span> <span class="token keyword">as</span> <span class="token variable">$book</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">echo</span> <span class="token variable">$book</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">author</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">name</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>对于该操作则只会执行两条 SQL 语句:</p><pre class=" language-php"><code class=" language-php">select <span class="token operator">*</span> from books select <span class="token operator">*</span> from authors where id in <span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">,</span> <span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">)</span></code></pre><h4>预加载多种关联</h4><p>有时你可能想要在单次操作中预加载多种不同的关联。要这么做,只需传递额外的参数至 <code class=" language-php">with</code> 方法即可:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$books</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Book<span class="token punctuation">::</span></span><span class="token function">with<span class="token punctuation">(</span></span><span class="token string">'author'</span><span class="token punctuation">,</span> <span class="token string">'publisher'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h4>嵌套预加载</h4><p>若要预加载嵌套关联,则可以使用「点」语法。例如,让我们在一个 Eloquent 语法中,预加载所有书籍的作者,及所有作者的个人联系方式:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$books</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Book<span class="token punctuation">::</span></span><span class="token function">with<span class="token punctuation">(</span></span><span class="token string">'author.contacts'</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p><a name="constraining-eager-loads"></a></p><h3>预加载条件限制</h3><p>有时你可能想要预加载关联,并且指定预加载查询的额外条件。如下所示:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$users</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>User<span class="token punctuation">::</span></span><span class="token function">with<span class="token punctuation">(</span></span><span class="token punctuation">[</span><span class="token string">'posts'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token variable">$query</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token variable">$query</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">where<span class="token punctuation">(</span></span><span class="token string">'title'</span><span class="token punctuation">,</span> <span class="token string">'like'</span><span class="token punctuation">,</span> <span class="token string">'%first%'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>在这个例子里,Eloquent 只会预加载标题包含 <code class=" language-php">first</code> 的文章。当然,你也可以调用其它的 <a href="/docs/5.4/queries">查询语句构造器</a> 来进一步自定义预加载的操作:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$users</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>User<span class="token punctuation">::</span></span><span class="token function">with<span class="token punctuation">(</span></span><span class="token punctuation">[</span><span class="token string">'posts'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token variable">$query</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token variable">$query</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">orderBy<span class="token punctuation">(</span></span><span class="token string">'created_at'</span><span class="token punctuation">,</span> <span class="token string">'desc'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">get<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p><a name="lazy-eager-loading"></a></p><h3>延迟预加载</h3><p>有时你可能需要在上层模型被获取后才预加载关联。当你需要来动态决定是否加载关联模型时会很有帮助,如下所示:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$books</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Book<span class="token punctuation">::</span></span><span class="token function">all<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token variable">$someCondition</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token variable">$books</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">load<span class="token punctuation">(</span></span><span class="token string">'author'</span><span class="token punctuation">,</span> <span class="token string">'publisher'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre><p>如果你想设置预加载查询的额外条件,则可以传递一个键值为你想要的关联的数组至 <code class=" language-php">load</code> 方法。这个数组的值应是用于接收查询实例的 <code class=" language-php">闭包</code> 实例:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$books</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">load<span class="token punctuation">(</span></span><span class="token punctuation">[</span><span class="token string">'author'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token variable">$query</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token variable">$query</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">orderBy<span class="token punctuation">(</span></span><span class="token string">'published_date'</span><span class="token punctuation">,</span> <span class="token string">'asc'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p><a name="inserting-and-updating-related-models"></a></p><h2><a href="#inserting-and-updating-related-models">写入关联模型</a></h2><p><a name="the-save-method"></a></p><h3>Save 方法</h3><p>Eloquent 提供了便捷的方法来将新的模型增加至关联中。例如,将新的 <code class=" language-php">Comment</code> 写入至 <code class=" language-php">Post</code> 模型。除了手动设置 <code class=" language-php">Comment</code> 的 <code class=" language-php">post_id</code> 属性外,你也可以直接使用关联的 <code class=" language-php">save</code> 方法来写入 <code class=" language-php">Comment</code>:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$comment</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">App<span class="token punctuation">\</span>Comment</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">'message'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'A new comment.'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$post</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Post<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$post</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">comments<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">save<span class="token punctuation">(</span></span><span class="token variable">$comment</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>注意我们并没有使用动态属性来访问 <code class=" language-php">comments</code> 关联。相反地,我们调用了 comments 方法来获取关联的实例。save 方法会自动在新的 <code class=" language-php">Comment</code> 模型中增加正确的 <code class=" language-php">post_id</code> 值。</p><p>如果你需要保存多个关联模型,则可以使用 <code class=" language-php">saveMany</code> 方法:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$post</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Post<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$post</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">comments<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">saveMany<span class="token punctuation">(</span></span><span class="token punctuation">[</span> <span class="token keyword">new</span> <span class="token class-name">App<span class="token punctuation">\</span>Comment</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">'message'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'A new comment.'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">App<span class="token punctuation">\</span>Comment</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">'message'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'Another comment.'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p><a name="the-create-method"></a></p><h3>Create 方法</h3><p>除了 <code class=" language-php">save</code> 与 <code class=" language-php">saveMany</code> 方法外,你也可以使用 <code class=" language-php">create</code> 方法,该方法允许传入属性的数组来建立模型并写入数据库。<code class=" language-php">save</code> 与 <code class=" language-php">create</code> 的不同之处在于,<code class=" language-php">save</code> 允许传入一个完整的 Eloquent 模型实例,但 <code class=" language-php">create</code> 只允许传入原始的 PHP 数组:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$post</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Post<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$comment</span> <span class="token operator">=</span> <span class="token variable">$post</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">comments<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">create<span class="token punctuation">(</span></span><span class="token punctuation">[</span> <span class="token string">'message'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token string">'A new comment.'</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>在使用 create 方法前,请确认你已浏览了文档的 <a href="/docs/5.4/eloquent#mass-assignment">批量赋值</a> 章节。</p><p><a name="updating-belongs-to-relationships"></a></p><h3>更新「从属」关联</h3><p>当更新一个 <code class=" language-php">belongsTo</code> 关联时,可以使用 <code class=" language-php">associate</code> 方法。此方法会将外键设置到下层模型:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$account</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Account<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">account<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">associate<span class="token punctuation">(</span></span><span class="token variable">$account</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">save<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>当删除一个 <code class=" language-php">belongsTo</code> 关联时,你可以使用 <code class=" language-php">dissociate</code> 方法。此方法会置该关联的外键为空 (null) :</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">account<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">dissociate<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">save<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p><a name="updating-many-to-many-relationships"></a></p><h3>多对多关联</h3><h4>附加与卸除</h4><p>当使用多对多关联时,Eloquent 提供了一些额外的辅助函数让操作关联模型更加方便。例如,让我们假设一个用户可以拥有多个身份,且每个身份都可以被多个用户拥有。要附加一个规则至一个用户,并连接模型以及将记录写入至中间表,则可以使用 <code class=" language-php">attach</code> 方法:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$user</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>User<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">roles<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">attach<span class="token punctuation">(</span></span><span class="token variable">$roleId</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>当附加一个关联至模型时,你也可以传递一个需被写入至中间表的额外数据数组:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">roles<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">attach<span class="token punctuation">(</span></span><span class="token variable">$roleId</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">'expires'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$expires</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>当然,我们有时也需要来移除用户的身份。要移除一个多对多的纪录,可使用 <code class=" language-php">detach</code> 方法。<code class=" language-php">detach</code> 方法会从中间表中移除正确的纪录;当然,这两个模型依然会存在于数据库中:</p><pre class=" language-php"><code class=" language-php"><span class="token comment" spellcheck="true">// 移除用户身上某一身份... </span><span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">roles<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">detach<span class="token punctuation">(</span></span><span class="token variable">$roleId</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true"> // 移除用户身上所有身份... </span><span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">roles<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">detach<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>为了方便,attach 与 detach 都允许传入 ID 数组:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$user</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>User<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">roles<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">detach<span class="token punctuation">(</span></span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">roles<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">attach<span class="token punctuation">(</span></span><span class="token punctuation">[</span><span class="token number">1</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span><span class="token string">'expires'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$expires</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h4>更新关联</h4><p>你也可以使用 <code class=" language-php">sync</code> 方法去创建一个多对多的关联。 <code class=" language-php">sync</code> 方法可以用数组形式的 IDs 插入中间的数据表。任何一个不存在于给定数组的 IDs 将会在中间表内被删除。所以,操作完成之后,只有那些在给定数组内的 IDs 会被保留在中间表中。</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">roles<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">sync<span class="token punctuation">(</span></span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>你也可以通过 IDs 传递其他的附加中间表值:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">roles<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">sync<span class="token punctuation">(</span></span><span class="token punctuation">[</span><span class="token number">1</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token punctuation">[</span><span class="token string">'expires'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token boolean">true</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>如果你不想分离现有的 IDs ,你可以 <code class=" language-php">syncWithoutDetaching</code> 方法:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">roles<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">syncWithoutDetaching<span class="token punctuation">(</span></span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h4>在中间表上保存额外数据</h4><p>处理多对多关联时, <code class=" language-php">save</code> 方法接收额外中间表属性数组作为第二个参数:</p><pre class=" language-php"><code class=" language-php"><span class="token scope">App<span class="token punctuation">\</span>User<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">roles<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">save<span class="token punctuation">(</span></span><span class="token variable">$role</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token string">'expires'</span> <span class="token operator">=</span><span class="token operator">&gt;</span> <span class="token variable">$expires</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h4>更新中间表记录</h4><p>如果你需要更新中间表已存在的记录,可以使用 <code class=" language-php">updateExistingPivot</code> 方法。这个方法接收中间记录的外键和属性数组进行更新:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$user</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>User<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$user</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">roles<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">updateExistingPivot<span class="token punctuation">(</span></span><span class="token variable">$roleId</span><span class="token punctuation">,</span> <span class="token variable">$attributes</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p><a name="touching-parent-timestamps"></a></p><h2><a href="#touching-parent-timestamps">连动父级时间戳</a></h2><p>当一个模型 <code class=" language-php">belongsTo</code> 或 <code class=" language-php">belongsToMany</code> 另一个模型时,像是一个 <code class=" language-php">Comment</code> 属于一个 <code class=" language-php">Post</code>。这对于子级模型被更新时,要更新父级的时间戳相当有帮助。举例来说,当一个 <code class=" language-php">Comment</code> 模型被更新时,你可能想要「连动」更新 <code class=" language-php">Post</code> 所属的 <code class=" language-php">updated_at</code> 时间戳。Eloquent 使得此事相当容易。只要在关联的下层模型中增加一个包含名称的 <code class=" language-php">touches</code> 属性即可:</p><pre class=" language-php"><code class=" language-php"><span class="token delimiter">&lt;?php</span> <span class="token keyword">namespace</span> <span class="token package">App</span><span class="token punctuation">;</span> <span class="token keyword">use</span> <span class="token package">Illuminate<span class="token punctuation">\</span>Database<span class="token punctuation">\</span>Eloquent<span class="token punctuation">\</span>Model</span><span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">Comment</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span> <span class="token comment" spellcheck="true">/** * 所有的关联将会被连动。 * * @var array */</span> <span class="token keyword">protected</span> <span class="token variable">$touches</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">'post'</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment" spellcheck="true">/** * 获取拥有此评论的文章。 */</span> <span class="token keyword">public</span> <span class="token keyword">function</span> <span class="token function">post<span class="token punctuation">(</span></span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token this">$this</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">belongsTo<span class="token punctuation">(</span></span><span class="token string">'App\Post'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre><p>现在,当你更新一个 Comment 时,它所属的 Post 拥有的 <code class=" language-php">updated_at</code> 字段也会被同时更新,使其更方便的得知何时让一个 <code class=" language-php">Post</code> 模型的缓存失效:</p><pre class=" language-php"><code class=" language-php"><span class="token variable">$comment</span> <span class="token operator">=</span> <span class="token scope">App<span class="token punctuation">\</span>Comment<span class="token punctuation">::</span></span><span class="token function">find<span class="token punctuation">(</span></span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token variable">$comment</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token property">text</span> <span class="token operator">=</span> <span class="token string">'Edit to this comment!'</span><span class="token punctuation">;</span> <span class="token variable">$comment</span><span class="token operator">-</span><span class="token operator">&gt;</span><span class="token function">save<span class="token punctuation">(</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h2>译者署名</h2><table><thead><tr><th>用户名</th><th>头像</th><th>职能</th><th>签名</th></tr></thead><tbody><tr><td><a href="https://laravel-china.org/users/79">@skyverd</a></td><td><img class="avatar-66 rm-style" src="https://dn-phphub.qbox.me/uploads/avatars/79_1427370664.jpeg?imageView2/1/w/100/h/100"></td><td>翻译</td><td>全桟工程师,<a href="https://skyverd.com">时光博客</a></td></tr></tbody></table></article>