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

# Eloquent 액세서·뮤테이터·캐스트

> Eloquent 모델의 속성값을 취득·저장 시에 변환하는 구조를 설명합니다. 액세서·뮤테이터의 정의 방법과 내장 캐스트의 사용법을 실전적으로 소개합니다.

## 시작하기

**액세서**, **뮤테이터**, **속성 캐스트**는 Eloquent 모델의 속성값을 모델 인스턴스에서 취득·설정할 때 변환하는 구조입니다.

* **액세서** — DB에서 취득한 원시 값을 가공해 애플리케이션에 전달
* **뮤테이터** — 애플리케이션에서 세팅된 값을 가공해 DB에 저장
* **캐스트** — 추가 메서드 없이 속성의 타입 변환을 선언적으로 정의

```mermaid theme={null}
flowchart LR
  DB["데이터베이스<br>(원시 값)"] -->|"취득 시<br>액세서/캐스트"| APP["애플리케이션<br>(변환 후의 값)"]
  APP -->|"저장 시<br>뮤테이터/캐스트"| DB
```

## 액세서 정의

액세서를 정의하려면 모델에 `protected` 메서드를 추가합니다. 메서드 이름은 카멜케이스, 반환값 타입은 `Illuminate\Database\Eloquent\Casts\Attribute`로 합니다.

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

namespace App\Models;

use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * 사용자의 퍼스트 네임을 취득합니다.
     */
    protected function firstName(): Attribute
    {
        return Attribute::make(
            get: fn (string $value) => ucfirst($value),
        );
    }
}
```

`get` 클로저에 DB의 원시 값이 전달됩니다. 모델 인스턴스에서 `first_name` 프로퍼티로 접근할 수 있습니다.

```php theme={null}
$user = User::find(1);

$firstName = $user->first_name; // ucfirst() 가 적용된 값
```

<Info>
  액세서로 계산한 값을 JSON/배열에 포함시키고 싶은 경우, 모델의 `$appends` 프로퍼티에 스네이크 케이스로 추가해 주십시오.
</Info>

### 여러 속성으로부터 값 객체를 생성

`get` 클로저는 두 번째 인수로 `$attributes`(모델의 모든 속성)를 받을 수 있습니다. 여러 컬럼을 조합해 하나의 값 객체를 반환할 수 있습니다.

```php theme={null}
use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;

protected function address(): Attribute
{
    return Attribute::make(
        get: fn (mixed $value, array $attributes) => new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        ),
    );
}
```

### 액세서 캐시

값 객체를 반환하는 액세서는 Eloquent가 같은 인스턴스를 반환하도록 자동으로 캐시합니다. 문자열이나 숫자 등 기본 타입에서도 캐시하고 싶은 경우 `shouldCache()`를 호출합니다.

```php theme={null}
protected function hash(): Attribute
{
    return Attribute::make(
        get: fn (string $value) => bcrypt(gzuncompress($value)),
    )->shouldCache();
}
```

객체의 캐시를 무효로 하고 싶은 경우 `withoutObjectCaching()`을 사용합니다.

```php theme={null}
protected function address(): Attribute
{
    return Attribute::make(
        get: fn (mixed $value, array $attributes) => new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        ),
    )->withoutObjectCaching();
}
```

## 뮤테이터 정의

뮤테이터는 `Attribute::make()`의 `set` 인수로 정의합니다. 액세서와 같은 메서드에 함께 정리할 수 있습니다.

```php theme={null}
protected function firstName(): Attribute
{
    return Attribute::make(
        get: fn (string $value) => ucfirst($value),
        set: fn (string $value) => strtolower($value),
    );
}
```

모델에 값을 세팅하면 `set` 클로저가 호출됩니다.

```php theme={null}
$user = User::find(1);
$user->first_name = 'SALLY'; // strtolower() 가 적용되어 'sally' 가 저장됨
```

### 여러 속성에 쓰기

`set` 클로저에서 배열을 반환하면, 여러 컬럼을 함께 업데이트할 수 있습니다.

```php theme={null}
use App\Support\Address;
use Illuminate\Database\Eloquent\Casts\Attribute;

protected function address(): Attribute
{
    return Attribute::make(
        get: fn (mixed $value, array $attributes) => new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        ),
        set: fn (Address $value) => [
            'address_line_one' => $value->lineOne,
            'address_line_two' => $value->lineTwo,
        ],
    );
}
```

## 속성 캐스트

**캐스트**는 액세서·뮤테이터를 쓰지 않아도 속성의 타입 변환을 선언할 수 있는 간편한 방법입니다. 모델의 `casts()` 메서드에서 배열을 반환합니다.

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

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected function casts(): array
    {
        return [
            'is_admin'   => 'boolean',
            'score'      => 'float',
            'settings'   => 'array',
            'created_at' => 'datetime',
        ];
    }
}
```

### 내장 캐스트 목록

| 캐스트                         | 설명              |
| --------------------------- | --------------- |
| `integer` / `int`           | 정수              |
| `float` / `double` / `real` | 부동소수점 수         |
| `decimal:<자릿수>`             | 지정 자릿수의 소수      |
| `string`                    | 문자열             |
| `boolean` / `bool`          | 진위값(`0`/`1` 포함) |
| `array`                     | JSON을 배열로 상호 변환 |
| `object`                    | JSON을 객체로 상호 변환 |
| `collection`                | JSON을 컬렉션으로 변환  |
| `date`                      | 날짜(Carbon)      |
| `datetime`                  | 일시(Carbon)      |
| `immutable_date`            | 불변 날짜           |
| `immutable_datetime`        | 불변 일시           |
| `timestamp`                 | UNIX 타임스탬프      |
| `hashed`                    | 저장 시에 해시화       |
| `encrypted`                 | 저장 시에 암호화       |

<Warning>
  `null` 속성은 캐스트되지 않습니다. 또한 릴레이션 이름과 같은 이름의 캐스트나, 주 키에의 캐스트는 정의하지 마십시오.
</Warning>

### Stringable 캐스트

`AsStringable`을 사용하면 속성을 `Illuminate\Support\Stringable` 객체로 다룰 수 있습니다.

```php theme={null}
use Illuminate\Database\Eloquent\Casts\AsStringable;

protected function casts(): array
{
    return [
        'bio' => AsStringable::class,
    ];
}
```

## 배열·JSON 캐스트

JSON/TEXT 컬럼을 PHP 배열로 투과적으로 다룰 수 있습니다.

```php theme={null}
protected function casts(): array
{
    return [
        'options' => 'array',
    ];
}
```

```php theme={null}
$user = User::find(1);

// 자동으로 PHP 배열로 취득
$options = $user->options;

// 변경해서 저장하면 자동으로 JSON 직렬화됨
$user->options = array_merge($options, ['theme' => 'dark']);
$user->save();
```

`->` 연산자로 JSON의 특정 키만 업데이트할 수도 있습니다.

```php theme={null}
$user->update(['options->theme' => 'dark']);
```

### AsArrayObject / AsCollection 캐스트

표준 `array` 캐스트는 배열의 특정 오프셋을 직접 변경하려고 하면 오류가 발생합니다. `AsArrayObject`나 `AsCollection`을 사용하면 이 문제를 회피할 수 있습니다.

```php theme={null}
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
use Illuminate\Database\Eloquent\Casts\AsCollection;

protected function casts(): array
{
    return [
        'options' => AsArrayObject::class,   // ArrayObject 로 다룸
        'tags'    => AsCollection::class,    // Collection 으로 다룸
    ];
}
```

커스텀 컬렉션 클래스를 사용하고 싶은 경우 `using()`을 지정합니다.

```php theme={null}
use App\Collections\TagCollection;
use Illuminate\Database\Eloquent\Casts\AsCollection;

protected function casts(): array
{
    return [
        'tags' => AsCollection::using(TagCollection::class),
    ];
}
```

## 일시 캐스트

`created_at` / `updated_at`은 기본적으로 Carbon으로 캐스트됩니다. 추가의 일시 컬럼도 동일하게 정의할 수 있습니다.

```php theme={null}
protected function casts(): array
{
    return [
        'published_at' => 'datetime',
        'expires_at'   => 'immutable_datetime',
    ];
}
```

포맷을 지정하면 JSON 직렬화 시에 그 포맷이 사용됩니다.

```php theme={null}
protected function casts(): array
{
    return [
        'published_at' => 'datetime:Y-m-d',
    ];
}
```

모든 날짜의 기본 직렬화 포맷을 바꾸고 싶은 경우 `serializeDate()`를 오버라이드합니다(DB 저장 포맷에는 영향을 주지 않습니다).

```php theme={null}
use DateTimeInterface;

protected function serializeDate(DateTimeInterface $date): string
{
    return $date->format('Y-m-d');
}
```

<Tip>
  `immutable_datetime`을 사용하면 Carbon 대신 CarbonImmutable이 반환됩니다. 원래 인스턴스를 변경하지 않고 일시 조작을 할 수 있어 부작용이 없는 코드를 쓰기 쉬워집니다.
</Tip>

## Enum 캐스트

PHP 8.1 이후의 Backed Enum을 캐스트로 지정할 수 있습니다.

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

namespace App\Enums;

enum ServerStatus: string
{
    case Provisioned = 'provisioned';
    case Ready       = 'ready';
    case Archived    = 'archived';
}
```

```php theme={null}
use App\Enums\ServerStatus;

protected function casts(): array
{
    return [
        'status' => ServerStatus::class,
    ];
}
```

DB에는 Enum의 백킹 값(`string` 또는 `int`)이 저장되고, 취득 시에는 Enum 인스턴스로 변환됩니다.

```php theme={null}
$server = Server::find(1);

if ($server->status === ServerStatus::Provisioned) {
    $server->status = ServerStatus::Ready;
    $server->save();
}
```

### Enum 의 배열 캐스트

여러 Enum 값을 1개의 컬럼에 배열로 저장하고 싶은 경우 `AsEnumCollection`을 사용합니다.

```php theme={null}
use App\Enums\ServerStatus;
use Illuminate\Database\Eloquent\Casts\AsEnumCollection;

protected function casts(): array
{
    return [
        'statuses' => AsEnumCollection::of(ServerStatus::class),
    ];
}
```

## 쿼리 시의 캐스트

쿼리 실행 시에 동적으로 캐스트를 적용하려면 `withCasts()`를 사용합니다.

```php theme={null}
use App\Models\Post;
use App\Models\User;

$users = User::select([
    'users.*',
    'last_posted_at' => Post::selectRaw('MAX(created_at)')
        ->whereColumn('user_id', 'users.id'),
])->withCasts([
    'last_posted_at' => 'datetime',
])->get();
```

## 커스텀 캐스트

독자적인 캐스트 클래스를 작성할 수도 있습니다. `CastsAttributes` 인터페이스를 구현하고 `get`과 `set` 메서드를 정의합니다.

```shell theme={null}
php artisan make:cast AsJson
```

상세한 구현 방법(Value Object 패턴, 인바운드 캐스트, Castables 등)은 다음의 상급 페이지를 참조하십시오.

<Card title="커스텀 캐스트 상세 설명" icon="book" href="/ko/advanced/eloquent-casts">
  CastsAttributes 인터페이스의 구현 방법이나, Value Object 패턴·Castables 등 상급의 커스텀 캐스트를 설명합니다.
</Card>

## 관련 페이지

<Card title="Eloquent API 리소스" icon="link" href="/ko/eloquent-resources">
  모델을 일관된 JSON API 응답으로 변환하는 리소스 클래스의 사용법을 확인합니다.
</Card>


## Related topics

- [Eloquent 시리얼라이제이션](/ko/eloquent-serialization.md)
- [Eloquent의 커스텀 캐스트](/ko/advanced/eloquent-casts.md)
- [InteractsWithData 트레이트](/ko/advanced/interacts-with-data.md)
- [Laravel AI SDK](/ko/ai-sdk.md)
- [HTTP 테스트](/ko/http-tests.md)
