> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 自定义 Pivot 模型与 chaperone

> 讲解如何把 belongsToMany 的中间表当作自定义 Pivot 模型使用，以及 Laravel 13 新增的 chaperone 自动预加载。

## 什么是自定义 Pivot 模型

[belongsToMany（多对多）](/zh-CN/eloquent-relationships#belongstomany多对多)的中间表默认会作为原生的 `Illuminate\Database\Eloquent\Relations\Pivot` 实例来处理。当需要在中间表增加额外列（如审批时间、角色种类），或添加访问器、修改器、自定义方法时，可以创建继承自 `Pivot` 的自定义模型。

```php theme={null}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Relations\Pivot;

class RoleUser extends Pivot
{
    protected function casts(): array
    {
        return [
            'approved' => 'boolean',
        ];
    }
}
```

在 `belongsToMany` 的定义中调用 `using()`，告诉关联使用该自定义模型。

```php theme={null}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Role extends Model
{
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class)
            ->using(RoleUser::class);
    }
}
```

<Info>
  保存自定义 Pivot 模型时，模型名请务必按**字母顺序的单数形式**命名（应写为 `RoleUser` 而非 `UserRole`）。这只是一种命名约定，实际类名可以自由选择。
</Info>

## 通过 `as()` 指定想要访问的属性名

默认情况下，中间表的值通过 `pivot` 属性访问。使用 `as()` 方法可以修改这个名字。

```php theme={null}
return $this->belongsToMany(Role::class)
    ->using(RoleUser::class)
    ->as('membership')
    ->withTimestamps()
    ->withPivot('approved');
```

```php theme={null}
foreach ($user->roles as $role) {
    echo $role->membership->approved;
    echo $role->membership->created_at;
}
```

## 附加列与时间戳

中间表中若存在如 `approved` 这样的额外列，需要使用 `withPivot()` 明确将其纳入取回范围。若要维护 `created_at` / `updated_at`，请调用 `withTimestamps()`。

```php theme={null}
return $this->belongsToMany(Role::class)
    ->using(RoleUser::class)
    ->withPivot('approved')
    ->withTimestamps();
```

<Warning>
  只有当 Pivot 模型通过 `using()` 明确指定时，Eloquent 才会自动更新中间表的 `updated_at`。即便直接使用默认的 `Pivot` 类，`withTimestamps()` 也能正常工作，但通过 `using()` 指定自定义模型才能使用自定义事件与自定义类型转换等特性。
</Warning>

## 从 Pivot 模型反向引用

自定义 Pivot 模型可以自由定义指向声明方模型和关联方模型的 `belongsTo` 关联。

```php theme={null}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\Pivot;

class RoleUser extends Pivot
{
    public function role(): BelongsTo
    {
        return $this->belongsTo(Role::class);
    }

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}
```

这样，即便单独取回 Pivot 模型，也能通过 `$roleUser->role` 或 `$roleUser->user` 访问关联模型。但若想在执行父查询时自动预加载这些关联，可以使用下面介绍的 `chaperone()` 方法。

## 通过 `chaperone()` 自动预加载（Laravel 13）

Laravel 13 新增了 `chaperone()` 方法，可以在执行 `belongsToMany` 查询时，把 Pivot 模型上定义的 `role()` / `user()` 等 `belongsTo` 关联自动完成 hydrate（相当于预加载后的关联绑定）。

```php theme={null}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Role extends Model
{
    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class)
            ->using(RoleUser::class)
            ->chaperone();
    }
}
```

调用 `chaperone()` 后，Eloquent 会自动推测 Pivot 模型（`RoleUser`）中 `belongsTo` 关联的名称。像 `Role::with('users')` 那样取回集合时，会在无需额外查询的情况下为每个 Pivot 挂接声明方模型和关联方模型的引用。

```php theme={null}
$role = Role::with('users')->first();

foreach ($role->users as $user) {
    // 无需额外查询，即可通过 pivot 访问父模型
    echo $user->pivot->role->name;
}
```

### 使用非标准关联名称的情况

若 Pivot 模型的 `belongsTo` 关联方法名称与标准命名（声明方和关联方模型名的驼峰单数形式）不同，可以通过 `chaperone()` 的参数显式指定。

```php theme={null}
return $this->belongsToMany(User::class)
    ->using(RoleUser::class)
    ->chaperone(declaring: 'role', related: 'user');
```

<Tip>
  `chaperone()` 是一种「消除通过中间表引用时的 N+1 问题」的机制。对于经常从 Pivot 模型访问父模型信息（例如同时展示审批时间和用户名）的应用，与常规的 `with()` 预加载结合使用会非常有效。
</Tip>

## 下一步

<Card title="关联" icon="link" href="/zh-CN/eloquent-relationships">
  回顾包含 belongsToMany 在内的基本关联定义方式。
</Card>

<Card title="Eloquent Observer 与模型事件" icon="bolt" href="/zh-CN/advanced/eloquent-observers">
  学习使用模型事件来 hook Pivot 模型的保存与更新。
</Card>


## Related topics

- [Eloquent 关联入门](/zh-CN/eloquent-relationships.md)
- [Laravel 12 升级到 13 指南](/zh-CN/blog/upgrade-12-to-13.md)
- [Eloquent 自定义类型转换](/zh-CN/advanced/eloquent-casts.md)
- [实现自定义认证 Guard](/zh-CN/advanced/custom-auth-guard.md)
- [Eloquent 入门](/zh-CN/eloquent.md)
