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

# カスタムピボットモデルとchaperone

> belongsToManyの中間テーブルをカスタムPivotモデルとして扱う方法と、Laravel 13で追加されたchaperoneによる自動Eagerロードを解説します。

## カスタムピボットモデルとは

[belongsToMany（多対多）](/jp/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>
  カスタムピボットモデルを保存するときは、モデル名を必ず**アルファベット順の単数形**で命名してください（`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>
  Eloquentが中間テーブルの `updated_at` を自動更新するのは、そのピボットモデルが `using()` で明示的に指定されている場合のみです。デフォルトの `Pivot` クラスをそのまま使う場合でも `withTimestamps()` は動作しますが、`using()` でカスタムモデルを指定するとカスタムイベント・カスタムキャストなどが利用できるようになります。
</Warning>

## ピボットモデルからの逆参照

カスタムピボットモデルには、宣言元モデル・関連先モデルへの `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);
    }
}
```

これにより、ピボットモデル単体を取得した場合でも `$roleUser->role` や `$roleUser->user` で関連モデルにアクセスできます。ただし、これらのリレーションを親クエリ実行時に自動的にEagerロードしたい場合は、以下の `chaperone()` を使う方法があります。

## `chaperone()`による自動Eagerロード（Laravel 13）

Laravel 13で、ピボットモデルに定義した `role()` / `user()` のような `belongsTo` リレーションを、`belongsToMany` クエリの実行時に自動でハイドレート（Eagerロード相当の紐付け）できる `chaperone()` メソッドが追加されました。

```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はピボットモデル（`RoleUser`）が持つ `belongsTo` リレーションの名前を自動的に推測し、`Role::with('users')` のようにコレクションを取得した際、各ピボットに紐づく宣言元モデル・関連先モデルへの参照を追加のクエリなしで設定します。

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

foreach ($role->users as $user) {
    // 追加クエリなしで、ピボット経由の親モデルにアクセスできる
    echo $user->pivot->role->name;
}
```

### 非標準なリレーション名を使う場合

ピボットモデルの `belongsTo` リレーションのメソッド名が標準的な命名（宣言元・関連先モデル名のキャメルケース単数形）と異なる場合は、`chaperone()` の引数で明示的に指定します。

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

<Tip>
  `chaperone()` は「N+1問題」を中間テーブル経由の参照についても解消してくれる仕組みです。ピボットモデルから親モデルの情報（承認日時とユーザー名を一緒に表示する、など）を頻繁に参照するアプリケーションでは、通常の `with()` によるEagerロードと組み合わせて活用すると効果的です。
</Tip>

## 次のステップ

<Card title="リレーションシップ" icon="link" href="/jp/eloquent-relationships">
  belongsToManyを含む基本的なリレーションの定義方法に戻って復習します。
</Card>

<Card title="Eloquent Observers とモデルイベント" icon="bolt" href="/jp/advanced/eloquent-observers">
  モデルイベントを使ってピボットモデルの保存・更新をフックする方法を学びます。
</Card>


## Related topics

- [Eloquentリレーション入門](/jp/eloquent-relationships.md)
- [Laravel 12 から 13 へのアップグレードガイド](/jp/blog/upgrade-12-to-13.md)
- [ボットチュートリアル - Laravel Bluesky](/jp/packages/laravel-bluesky/bot-tutorial.md)
- [パスワードリセット](/jp/passwords.md)
- [Eloquentのカスタムキャスト](/jp/advanced/eloquent-casts.md)
