> ## 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(다대다)](/ko/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="/ko/eloquent-relationships">
  belongsToMany를 포함한 기본적인 관계의 정의 방법으로 돌아가 복습합니다.
</Card>

<Card title="Eloquent Observers와 모델 이벤트" icon="bolt" href="/ko/advanced/eloquent-observers">
  모델 이벤트를 사용해 피벗 모델의 저장·갱신을 훅하는 방법을 배웁니다.
</Card>


## Related topics

- [Eloquent 관계 입문](/ko/eloquent-relationships.md)
- [Laravel 12에서 13으로의 업그레이드 가이드](/ko/blog/upgrade-12-to-13.md)
- [커스텀 인증 가드 구현](/ko/advanced/custom-auth-guard.md)
- [Eloquent의 커스텀 캐스트](/ko/advanced/eloquent-casts.md)
- [AI SDK의 커스텀 프로바이더 만들기](/ko/advanced/ai-sdk-custom-provider.md)
