> ## 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의 커스텀 캐스트

> CastsAttributes 인터페이스를 구현하여 커스텀 캐스트를 작성하는 방법을 해설합니다. Value Object 패턴, 인바운드 캐스트, Castables 등 상급 패턴도 소개합니다.

## 캐스트란

Eloquent의 캐스트는, 데이터베이스에서 취득한 생의 값을 PHP의 데이터 타입으로 변환하고, 저장 시에는 그 역변환을 하는 구조입니다. `casts` 메서드로 정의합니다.

```php theme={null}
protected function casts(): array
{
    return [
        'is_admin'   => 'boolean',
        'settings'   => 'array',
        'created_at' => 'datetime',
    ];
}
```

## 내장 캐스트의 종류

Laravel이 표준으로 제공하는 캐스트 목록입니다.

| 캐스트                    | 설명                         |
| ---------------------- | -------------------------- |
| `integer` / `int`      | 정수로 변환                     |
| `float` / `double`     | 부동소수점수로 변환                 |
| `string`               | 문자열로 변환                    |
| `boolean` / `bool`     | 진위값으로 변환(`0`/`1` 포함)       |
| `array`                | JSON 문자열 ↔ PHP 배열          |
| `collection`           | JSON 문자열 ↔ Collection 인스턴스 |
| `object`               | JSON 문자열 ↔ stdObject 인스턴스  |
| `datetime`             | 문자열 ↔ Carbon 인스턴스          |
| `immutable_datetime`   | 문자열 ↔ CarbonImmutable 인스턴스 |
| `date`                 | 문자열 ↔ Carbon(시각 없음)        |
| `timestamp`            | 문자열 ↔ UNIX 타임스탬프           |
| `encrypted`            | 저장 시에 암호화, 취득 시에 복호화       |
| `hashed`               | 저장 시에 해시화(읽기 전용 캐스트와 조합)   |
| `AsStringable::class`  | 문자열 ↔ Stringable 오브젝트      |
| `AsArrayObject::class` | JSON ↔ ArrayObject 인스턴스    |
| `AsCollection::class`  | JSON ↔ Collection 인스턴스     |

<Info>
  `AsArrayObject`나 `AsCollection`은, 배열의 특정 오프셋을 직접 변경할 수 있도록, Laravel 내부에서 커스텀 캐스트로서 구현되어 있습니다.
</Info>

## 커스텀 캐스트 클래스의 작성

내장 캐스트로는 대응할 수 없는 변환이 필요한 경우, `CastsAttributes` 인터페이스를 구현한 커스텀 캐스트를 작성합니다.

### 인터페이스의 정의

프레임워크 본체의 컨트랙트는 다음과 같이 정의되어 있습니다.

```php theme={null}
// src/Illuminate/Contracts/Database/Eloquent/CastsAttributes.php

interface CastsAttributes
{
    /**
     * DBの生の値をPHPの値に変換する（読み取り時）
     *
     * @param  array<string, mixed>  $attributes  モデルのすべての属性値
     */
    public function get(Model $model, string $key, mixed $value, array $attributes);

    /**
     * PHPの値をDBに保存できる形に変換する（書き込み時）
     *
     * @param  array<string, mixed>  $attributes  モデルのすべての属性値
     */
    public function set(Model $model, string $key, mixed $value, array $attributes);
}
```

`$attributes` 인수에는 모델의 전 속성이 들어 있으므로, 여러 컬럼을 걸친 변환도 가능합니다(후술의 Value Object 패턴 참조).

### 기본적인 커스텀 캐스트의 구현

`make:cast` 커맨드로 뼈대를 생성합니다.

```bash theme={null}
php artisan make:cast AsMoney
```

`app/Casts/AsMoney.php`가 생성됩니다. 예로 금액(정수로 저장)을 `Money` Value Object로 변환하는 캐스트를 구현합니다.

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

namespace App\Casts;

use App\ValueObjects\Money;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;

class AsMoney implements CastsAttributes
{
    /**
     * DBの整数値（円単位）を Money オブジェクトに変換する
     */
    public function get(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): Money {
        return new Money((int) $value);
    }

    /**
     * Money オブジェクトをDBに保存できる整数値に変換する
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): int {
        if ($value instanceof Money) {
            return $value->amount;
        }

        return (int) $value;
    }
}
```

모델에 캐스트를 적용합니다.

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

namespace App\Models;

use App\Casts\AsMoney;
use Illuminate\Database\Eloquent\Model;

class Order extends Model
{
    protected function casts(): array
    {
        return [
            'price' => AsMoney::class,
        ];
    }
}
```

이것으로 `$order->price`는 `Money` 인스턴스를 반환합니다.

## Value Object 캐스트

여러 DB 컬럼을 정리하여 하나의 Value Object로서 다루는 패턴입니다.

### 구현 예: 주소 캐스트

`address_line_one`과 `address_line_two`의 2컬럼을 `Address` Value Object로 정리합니다.

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

namespace App\ValueObjects;

use Illuminate\Contracts\Support\Arrayable;

class Address implements Arrayable, \JsonSerializable
{
    public function __construct(
        public readonly string $lineOne,
        public readonly string $lineTwo,
    ) {}

    public function toArray(): array
    {
        return [
            'line_one' => $this->lineOne,
            'line_two' => $this->lineTwo,
        ];
    }

    public function jsonSerialize(): array
    {
        return $this->toArray();
    }
}
```

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

namespace App\Casts;

use App\ValueObjects\Address;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
use InvalidArgumentException;

class AsAddress implements CastsAttributes
{
    /**
     * 複数カラムから Address オブジェクトを組み立てる
     */
    public function get(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): Address {
        return new Address(
            $attributes['address_line_one'],
            $attributes['address_line_two'],
        );
    }

    /**
     * Address オブジェクトをカラムごとの配列に分解して返す
     *
     * @return array<string, string>
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): array {
        if (! $value instanceof Address) {
            throw new InvalidArgumentException('The given value is not an Address instance.');
        }

        return [
            'address_line_one' => $value->lineOne,
            'address_line_two' => $value->lineTwo,
        ];
    }
}
```

<Info>
  `set` 메서드에서 배열을 반환하면, Eloquent는 키를 컬럼명으로 하고, 값을 각각의 컬럼에 저장합니다. 단일 컬럼의 캐스트에서는 문자열이나 정수를 반환합니다.
</Info>

모델에의 적용과 사용법은 다음과 같습니다.

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

namespace App\Models;

use App\Casts\AsAddress;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected function casts(): array
    {
        return [
            'address' => AsAddress::class,
        ];
    }
}
```

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

// Address オブジェクトとして取得できる
echo $user->address->lineOne;

// Value Object を変更すると保存時に自動的にDBに反映される
$user->address = new Address('123 Main St', 'Apt 4B');
$user->save();
```

### Value Object의 캐시

Value Object로 변환된 속성값은 Eloquent에 의해 캐시됩니다. 같은 속성에 2번 접근해도, 같은 오브젝트 인스턴스가 반환됩니다.

캐시를 무효화하고 싶은 경우에는 `$withoutObjectCaching` 프로퍼티를 캐스트 클래스에 추가합니다.

```php theme={null}
class AsAddress implements CastsAttributes
{
    public bool $withoutObjectCaching = true;

    // ...
}
```

## 인바운드 캐스트(쓰기 전용)

DB에의 쓰기 시에만 변환을 하고, 읽기 시에는 변환하지 않는 캐스트입니다. `CastsInboundAttributes` 인터페이스를 구현합니다.

전형적인 용도는 해시화입니다. 비밀번호나 비밀값을 저장할 때만 변환하고, 읽기 시에는 해시값을 그대로 반환합니다.

```bash theme={null}
php artisan make:cast AsHash --inbound
```

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

namespace App\Casts;

use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
use Illuminate\Database\Eloquent\Model;

class AsHash implements CastsInboundAttributes
{
    public function __construct(
        protected string|null $algorithm = null,
    ) {}

    /**
     * 保存時にハッシュ化する
     */
    public function set(
        Model $model,
        string $key,
        mixed $value,
        array $attributes,
    ): string {
        return is_null($this->algorithm)
            ? bcrypt($value)
            : hash($this->algorithm, $value);
    }
}
```

## 캐스트 파라미터

커스텀 캐스트에 파라미터를 전달하는 경우에는, 클래스명 뒤에 콜론 구분으로 지정합니다. 여러 파라미터는 콤마 구분입니다.

```php theme={null}
protected function casts(): array
{
    return [
        'secret' => AsHash::class.':sha256',
        'data'   => AsHash::class.':sha512',
    ];
}
```

파라미터는 캐스트 클래스의 컨스트럭터에 전달됩니다.

```php theme={null}
class AsHash implements CastsInboundAttributes
{
    public function __construct(
        protected string|null $algorithm = null, // ':sha256' が渡される
    ) {}
}
```

## Castables: Value Object 측에 캐스트 로직을 갖게 하기

`Castable` 인터페이스를 구현한 Value Object는, 자신의 캐스트 클래스를 반환하는 `castUsing` 메서드를 갖습니다. 모델 측에서 캐스트 클래스를 몰라도 되므로, 도메인 로직이 정리됩니다.

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

namespace App\ValueObjects;

use App\Casts\AsAddress;
use Illuminate\Contracts\Database\Eloquent\Castable;

class Address implements Castable
{
    /**
     * このオブジェクトのキャストに使うクラスを返す
     *
     * @param  array<string, mixed>  $arguments
     */
    public static function castUsing(array $arguments): string
    {
        return AsAddress::class;
    }
}
```

모델 측은 캐스트 클래스 대신 Value Object 클래스를 지정합니다.

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

<Tip>
  `Castable`과 무명 클래스를 조합하면, Value Object와 캐스트 로직을 하나의 파일에 정리할 수 있습니다.

  ```php theme={null}
  class Address implements Castable
  {
      public static function castUsing(array $arguments): CastsAttributes
      {
          return new class implements CastsAttributes
          {
              public function get(Model $model, string $key, mixed $value, array $attributes): Address
              {
                  return new Address(
                      $attributes['address_line_one'],
                      $attributes['address_line_two'],
                  );
              }

              public function set(Model $model, string $key, mixed $value, array $attributes): array
              {
                  return [
                      'address_line_one' => $value->lineOne,
                      'address_line_two' => $value->lineTwo,
                  ];
              }
          };
      }
  }
  ```
</Tip>

## `$appends`와 `$hidden`과의 상호작용

캐스트와 `$appends`, `$hidden`은 독립된 구조이지만, 조합할 때 주의가 필요합니다.

```php theme={null}
class User extends Model
{
    protected function casts(): array
    {
        return [
            'address' => AsAddress::class,
        ];
    }

    // toArray() / toJson() 時に address を除外する
    protected $hidden = ['address_line_one', 'address_line_two'];

    // キャスト済みの address をシリアライズ結果に含める
    protected $appends = ['address'];
}
```

<Warning>
  `$hidden`에 지정하는 것은 DB의 컬럼명입니다. 캐스트를 통해 만들어지는 속성명(`address`)이 아니라, 원래의 컬럼명(`address_line_one`, `address_line_two`)을 지정합니다.
</Warning>

## 실행 시 캐스트의 추가

특정 쿼리나 요청만 캐스트를 추가하고 싶을 때는 `mergeCasts` 메서드를 사용합니다.

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

$user->mergeCasts([
    'extra_data' => 'array',
]);
```

## 다음 단계

<Card title="Eloquent Observer와 모델 이벤트" icon="bell" href="/ko/advanced/eloquent-observers">
  모델의 저장·삭제 등의 라이프사이클 이벤트에 훅을 걸어 처리를 추가하는 방법을 배웁니다.
</Card>


## Related topics

- [Eloquent 액세서·뮤테이터·캐스트](/ko/eloquent-mutators.md)
- [Eloquent의 스코프](/ko/advanced/eloquent-scopes.md)
- [AI SDK의 커스텀 프로바이더 만들기](/ko/advanced/ai-sdk-custom-provider.md)
- [Boost의 커스텀 에이전트 만들기](/ko/advanced/boost-custom-agent.md)
- [커스텀 에이전트](/ko/packages/laravel-copilot-sdk/custom-agents.md)
