Eloquent: 뮤테이터 & 캐스팅
업데이트됨번역일: 2026년 7월 2일
이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.
- 원문 수정
- 2026년 6월 20일
- 번역 갱신
- 2026년 7월 2일
Eloquent: 뮤테이터 & 캐스팅
소개
접근자(Accessor), 뮤테이터(Mutator), 속성 캐스팅(Attribute Casting)을 활용하면 Eloquent 모델의 속성 값을 읽거나 저장할 때 자동으로 변환할 수 있습니다. 예를 들어, 데이터베이스에는 암호화된 값을 저장하면서 모델에서는 자동으로 복호화된 값을 반환하거나, 데이터베이스의 JSON 문자열을 모델에서 배열로 자연스럽게 다룰 수 있습니다.
- 접근자: 모델에서 속성을 읽을 때 값을 변환합니다.
- 뮤테이터: 모델에 속성을 저장할 때 값을 변환합니다.
- 캐스팅: 속성을 특정 타입으로 자동 변환하는 편리한 방법입니다.
접근자와 뮤테이터
접근자 정의하기
접근자는 Eloquent 속성 값을 읽을 때 변환 로직을 적용합니다. 접근자를 정의하려면 모델에 보호된(protected) 메서드를 만들고, 메서드 이름을 접근할 속성명의 카멜 케이스(camelCase)로 지정합니다. 이 메서드의 반환 타입 힌트는 Illuminate\Database\Eloquent\Casts\Attribute여야 합니다.
아래 예시는 first_name 속성에 대한 접근자를 정의합니다. 이 접근자는 Eloquent가 first_name 속성 값을 읽을 때 자동으로 호출됩니다.
<?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),
);
}
}접근자 메서드는 Attribute 인스턴스를 반환하며, get 인자로 속성이 어떻게 변환될지 정의합니다. 위 예시에서는 ucfirst 함수를 사용해 첫 글자를 대문자로 변환합니다.
정의가 완료되면, 일반 속성처럼 모델 인스턴스에서 접근할 수 있습니다.
use App\Models\User;
$user = User::find(1);
$firstName = $user->first_name;NOTE
변환된 값을 모델의 배열이나 JSON 표현에도 포함하려면 해당 값을 추가(append)해야 합니다.
때로는 여러 속성을 조합해 하나의 "가상" 속성을 만들어야 할 수도 있습니다. 이 경우 접근자의 get 클로저에서 두 번째 인자로 $attributes를 받아 활용할 수 있습니다.
use Illuminate\Database\Eloquent\Casts\Attribute;
/**
* 사용자의 전체 이름을 반환합니다.
*/
protected function fullName(): Attribute
{
return Attribute::make(
get: fn (string $value, array $attributes) => $attributes['first_name'] . ' ' . $attributes['last_name'],
);
}접근자 캐싱
접근자에서 값 객체(Value Object)를 반환할 때, 해당 객체의 변경 사항이 모델에 자동으로 반영됩니다. 이는 Eloquent가 접근자를 통해 반환된 인스턴스를 내부적으로 캐싱하기 때문입니다. 같은 속성에 여러 번 접근해도 동일한 인스턴스가 반환됩니다.
use App\Models\User;
$user = User::find(1);
$user->address->line1 = '새 주소 1번지';
$user->address->line2 = '강남구';하지만 문자열이나 정수 같은 기본 값(primitive)의 경우 캐싱을 활성화하고 싶다면 shouldCache 메서드를 사용할 수 있습니다.
protected function hash(): Attribute
{
return Attribute::make(
get: fn (string $value) => bcrypt(gzuncompress($value)),
)->shouldCache();
}반대로 값 객체의 캐싱을 비활성화하려면 withoutObjectCaching 메서드를 사용합니다.
protected function address(): Attribute
{
return Attribute::make(
get: fn (array $value) => new Address($value),
)->withoutObjectCaching();
}뮤테이터 정의하기
뮤테이터는 Eloquent 속성 값을 저장할 때 변환 로직을 적용합니다. 뮤테이터를 정의하려면 Attribute::make 호출 시 set 인자를 지정합니다.
아래 예시는 first_name 속성에 대한 뮤테이터를 정의합니다. 이 뮤테이터는 모델에 first_name 값을 설정할 때 자동으로 호출됩니다.
<?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),
set: fn (string $value) => strtolower($value),
);
}
}뮤테이터의 set 클로저는 속성에 설정하려는 값을 받아 변환된 값을 반환합니다. 위 예시에서는 값을 소문자로 변환해 저장합니다.
use App\Models\User;
$user = User::find(1);
$user->first_name = 'Sally';위 코드에서 set 클로저는 Sally 값을 받아 strtolower 함수를 적용하고, 결과값(sally)을 모델의 내부 $attributes 배열에 저장합니다.
하나의 뮤테이터에서 여러 속성을 동시에 설정해야 하는 경우, set 클로저에서 배열을 반환할 수 있습니다.
use App\Models\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,
],
);
}Eloquent: 뮤테이터 & 캐스팅
소개
액세서(Accessor), 뮤테이터(Mutator), 그리고 속성 캐스팅(Attribute Casting)을 활용하면 Eloquent 모델 인스턴스에서 속성 값을 읽거나 설정할 때 자동으로 변환 처리를 적용할 수 있습니다.
예를 들어, 데이터베이스에 값을 저장할 때는 Laravel 암호화 기능으로 암호화하고, 모델에서 해당 속성을 읽을 때는 자동으로 복호화되도록 설정할 수 있습니다. 또는 데이터베이스에 JSON 문자열로 저장된 값을 모델에서 접근할 때 자동으로 배열로 변환되도록 만들 수도 있습니다.
액세서와 뮤테이터
액세서 정의하기
액세서(Accessor)는 Eloquent 속성 값을 읽을 때 자동으로 변환을 적용하는 기능입니다. 액세서를 정의하려면 모델에 protected 메서드를 추가하면 됩니다. 메서드 이름은 대상 데이터베이스 컬럼명의 카멜 케이스(camelCase) 형태와 일치해야 합니다.
예를 들어, first_name 컬럼에 대한 액세서를 정의해 보겠습니다. first_name 속성에 접근할 때 Eloquent가 이 액세서를 자동으로 호출합니다. 모든 액세서·뮤테이터 메서드는 반환 타입으로 Illuminate\Database\Eloquent\Casts\Attribute를 선언해야 합니다.
<?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),
);
}
}모든 액세서 메서드는 Attribute 인스턴스를 반환하며, 이 인스턴스 안에서 속성을 어떻게 읽고 쓸지 정의합니다. 위 예시에서는 읽기(get) 동작만 정의했습니다.
get 클로저에는 컬럼의 원본 값이 전달되므로, 이 값을 가공한 뒤 반환하면 됩니다. 액세서 값은 모델 인스턴스에서 속성처럼 바로 접근할 수 있습니다.
use App\Models\User;
$user = User::find(1);
$firstName = $user->first_name;NOTE
액세서로 계산된 값을 모델의 배열·JSON 표현에 포함하려면 해당 값을 직접 추가해야 합니다.
여러 속성으로 값 객체 만들기
때로는 여러 모델 속성을 조합해 하나의 "값 객체(Value Object)"를 반환해야 할 때가 있습니다. 이 경우 get 클로저의 두 번째 인수로 $attributes를 선언하면, 모델의 현재 속성 전체가 배열 형태로 전달됩니다.
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는 해당 인스턴스를 내부적으로 유지합니다. 덕분에 값 객체의 프로퍼티를 수정하면 모델 저장 시 변경 사항이 자동으로 반영됩니다.
use App\Models\User;
$user = User::find(1);
$user->address->lineOne = '새 주소 1행';
$user->address->lineTwo = '새 주소 2행';
$user->save();한편, 문자열·불리언처럼 원시(primitive) 타입을 반환하는 액세서에서 연산 비용이 높은 작업(예: 해시 계산, 압축 해제)을 수행한다면 shouldCache 메서드로 결과값을 캐시할 수 있습니다.
protected function hash(): Attribute
{
return Attribute::make(
get: fn (string $value) => bcrypt(gzuncompress($value)),
)->shouldCache();
}반대로 값 객체의 자동 캐싱을 비활성화하고 싶다면 withoutObjectCaching 메서드를 사용합니다.
/**
* 사용자의 주소를 처리합니다.
*/
protected function address(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes) => new Address(
$attributes['address_line_one'],
$attributes['address_line_two'],
),
)->withoutObjectCaching();
}뮤테이터 정의하기
뮤테이터(Mutator)는 Eloquent 속성 값을 설정할 때 자동으로 변환을 적용하는 기능입니다. 뮤테이터를 정의하려면 Attribute::make() 호출 시 set 인수를 추가하면 됩니다.
아래는 first_name 속성에 대한 액세서와 뮤테이터를 함께 정의하는 예시입니다. first_name에 값을 할당할 때 뮤테이터가 자동으로 호출됩니다.
<?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),
set: fn (string $value) => strtolower($value),
);
}
}set 클로저는 설정하려는 값을 전달받아 가공한 뒤 반환합니다. 반환된 값은 모델 내부의 $attributes 배열에 저장됩니다. 사용 방법은 일반 속성 할당과 동일합니다.
use App\Models\User;
$user = User::find(1);
$user->first_name = 'Sally';이 예시에서 set 클로저는 'Sally'를 전달받아 strtolower를 적용한 뒤 'sally'를 내부 속성 배열에 저장합니다.
여러 속성 한 번에 변환하기
하나의 뮤테이터에서 여러 데이터베이스 컬럼 값을 동시에 설정해야 할 때도 있습니다. 이럴 때는 set 클로저에서 배열을 반환하면 됩니다. 배열의 키는 모델의 실제 컬럼명과 일치해야 합니다.
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 메서드에서 어트리뷰트 이름과 변환할 타입을 배열로 반환하면 됩니다.
지원하는 캐스트 타입 목록은 다음과 같습니다:
arrayAsFluent::classAsStringable::classAsUri::classbooleancollectiondatedatetimeimmutable_dateimmutable_datetimedecimal:<precision>doubleencryptedencrypted:arrayencrypted:collectionencrypted:objectfloathashedintegerobjectrealstringtimestamp
예를 들어, 데이터베이스에 0 또는 1 정수로 저장된 is_admin 어트리뷰트를 불리언으로 캐스팅해 보겠습니다:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 캐스팅할 어트리뷰트를 반환합니다.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'is_admin' => 'boolean',
];
}
}캐스트를 정의하면, 데이터베이스에 정수로 저장된 값이더라도 is_admin 어트리뷰트에 접근할 때 항상 불리언으로 반환됩니다:
$user = App\Models\User::find(1);
if ($user->is_admin) {
// ...
}런타임 중 일시적으로 캐스트를 추가해야 할 경우에는 mergeCasts 메서드를 사용합니다. 이 메서드는 모델에 이미 정의된 캐스트에 새 캐스트를 병합합니다:
$user->mergeCasts([
'is_admin' => 'integer',
'options' => 'object',
]);WARNING
null 값인 어트리뷰트는 캐스팅되지 않습니다. 또한 연관관계(relationship)와 같은 이름의 캐스트를 정의하거나, 모델의 기본 키(primary key)에 캐스트를 지정해서는 안 됩니다.
Stringable 캐스팅
Illuminate\Database\Eloquent\Casts\AsStringable 캐스트 클래스를 사용하면 모델 어트리뷰트를 Illuminate\Support\Stringable 플루언트 객체로 캐스팅할 수 있습니다:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\AsStringable;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 캐스팅할 어트리뷰트를 반환합니다.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'directory' => AsStringable::class,
];
}
}배열 & JSON 캐스팅
array 캐스트는 직렬화된 JSON을 저장하는 컬럼을 다룰 때 특히 유용합니다. 데이터베이스의 JSON 또는 TEXT 필드에 JSON 문자열이 저장되어 있다면, array 캐스트를 지정하는 것만으로 Eloquent 모델에서 접근할 때 자동으로 PHP 배열로 역직렬화됩니다:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 캐스팅할 어트리뷰트를 반환합니다.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'options' => 'array',
];
}
}캐스트를 정의하면 options 어트리뷰트를 읽을 때 JSON이 자동으로 PHP 배열로 변환되며, 값을 저장할 때는 배열이 다시 JSON으로 직렬화됩니다:
use App\Models\User;
$user = User::find(1);
$options = $user->options;
$options['key'] = 'value';
$user->options = $options;
$user->save();JSON 어트리뷰트의 특정 필드만 간결하게 업데이트하려면, 해당 어트리뷰트를 대량 할당 가능하게 설정한 뒤 update 호출 시 -> 연산자를 사용할 수 있습니다:
$user = User::find(1);
$user->update(['options->key' => 'value']);JSON과 유니코드
한국어를 포함한 유니코드 문자를 이스케이프 없이 JSON으로 저장하고 싶다면 json:unicode 캐스트를 사용하세요:
/**
* 캐스팅할 어트리뷰트를 반환합니다.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'options' => 'json:unicode',
];
}NOTE
기본 json 캐스트는 한국어 등 멀티바이트 문자를 \uXXXX 형태로 이스케이프합니다. 데이터베이스에 한글을 원문 그대로 저장하려면 json:unicode를 사용하세요.
ArrayObject 및 Collection 캐스팅
표준 array 캐스트는 PHP의 원시 배열(primitive array)을 반환하기 때문에, 배열의 특정 오프셋을 직접 수정하려 하면 PHP 오류가 발생합니다:
$user = User::find(1);
$user->options['key'] = $value; // PHP 오류 발생이 문제를 해결하기 위해 Laravel은 AsArrayObject 캐스트를 제공합니다. 이 캐스트는 JSON 어트리뷰트를 PHP의 ArrayObject 인스턴스로 변환하며, Laravel의 커스텀 캐스트 구현을 통해 변경된 객체를 지능적으로 캐싱하고 처리합니다:
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
/**
* 캐스팅할 어트리뷰트를 반환합니다.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'options' => AsArrayObject::class,
];
}마찬가지로, JSON 어트리뷰트를 Laravel 컬렉션 인스턴스로 변환하는 AsCollection 캐스트도 제공됩니다:
use Illuminate\Database\Eloquent\Casts\AsCollection;
/**
* 캐스팅할 어트리뷰트를 반환합니다.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'options' => AsCollection::class,
];
}기본 컬렉션 클래스 대신 커스텀 컬렉션 클래스를 사용하고 싶다면, 클래스명을 인수로 전달하세요:
use App\Collections\OptionCollection;
use Illuminate\Database\Eloquent\Casts\AsCollection;
/**
* 캐스팅할 어트리뷰트를 반환합니다.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'options' => AsCollection::using(OptionCollection::class),
];
}of 메서드를 사용하면 컬렉션의 각 항목을 지정한 클래스 인스턴스로 매핑할 수 있습니다. 내부적으로 컬렉션의 mapInto 메서드를 활용합니다:
use App\ValueObjects\Option;
use Illuminate\Database\Eloquent\Casts\AsCollection;
/**
* 캐스팅할 어트리뷰트를 반환합니다.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'options' => AsCollection::of(Option::class)
];
}컬렉션 항목을 객체로 매핑할 때, 해당 객체는 Illuminate\Contracts\Support\Arrayable과 JsonSerializable 인터페이스를 구현해야 JSON으로 올바르게 직렬화됩니다:
<?php
namespace App\ValueObjects;
use Illuminate\Contracts\Support\Arrayable;
use JsonSerializable;
class Option implements Arrayable, JsonSerializable
{
public string $name;
public mixed $value;
public bool $isLocked;
/**
* 새 Option 인스턴스를 생성합니다.
*/
public function __construct(array $data)
{
$this->name = $data['name'];
$this->value = $data['value'];
$this->isLocked = $data['is_locked'];
}
/**
* 인스턴스를 배열로 반환합니다.
*
* @return array{name: string, data: string, is_locked: bool}
*/
public function toArray(): array
{
return [
'name' => $this->name,
'value' => $this->value,
'is_locked' => $this->isLocked,
];
}
/**
* JSON으로 직렬화할 데이터를 반환합니다.
*
* @return array{name: string, data: string, is_locked: bool}
*/
public function jsonSerialize(): array
{
return $this->toArray();
}
}바이너리 캐스팅
모델에 자동 증가 ID 컬럼 외에 바이너리 타입의 uuid 또는 ulid 컬럼이 있는 경우, AsBinary 캐스트를 사용해 값을 바이너리 표현으로 자동 변환할 수 있습니다:
use Illuminate\Database\Eloquent\Casts\AsBinary;
/**
* 캐스팅할 어트리뷰트를 반환합니다.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'uuid' => AsBinary::uuid(),
'ulid' => AsBinary::ulid(),
];
}캐스트를 정의하면 UUID/ULID 어트리뷰트에 객체 인스턴스나 문자열을 할당할 수 있으며, Eloquent가 자동으로 바이너리 형태로 변환해 저장합니다. 어트리뷰트를 읽을 때는 항상 일반 텍스트 문자열로 반환됩니다:
use Illuminate\Support\Str;
$user->uuid = Str::uuid();
return $user->uuid;
// "6e8cdeed-2f32-40bd-b109-1e4405be2140"날짜 캐스팅
기본적으로 Eloquent는 created_at과 updated_at 컬럼을 Carbon 인스턴스로 자동 캐스팅합니다. Carbon은 PHP DateTime 클래스를 확장하여 다양한 날짜 조작 메서드를 제공합니다. 추가 날짜 어트리뷰트는 모델의 casts 메서드에서 datetime 또는 immutable_datetime 타입으로 정의하면 됩니다.
date 또는 datetime 캐스트를 정의할 때 포맷을 함께 지정하면, 모델이 배열이나 JSON으로 직렬화될 때 해당 포맷이 사용됩니다:
/**
* 캐스팅할 어트리뷰트를 반환합니다.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'created_at' => 'datetime:Y-m-d',
];
}날짜로 캐스팅된 컬럼에는 UNIX 타임스탬프, 날짜 문자열(Y-m-d), 날짜-시간 문자열, DateTime/Carbon 인스턴스 등을 모두 할당할 수 있으며, 데이터베이스에 적절히 변환되어 저장됩니다.
모델의 모든 날짜에 적용할 기본 직렬화 포맷을 변경하려면 serializeDate 메서드를 정의하세요. 이 메서드는 직렬화 포맷에만 영향을 주며, 데이터베이스 저장 포맷은 변경하지 않습니다:
/**
* 배열/JSON 직렬화를 위한 날짜 포맷을 반환합니다.
*/
protected function serializeDate(DateTimeInterface $date): string
{
return $date->format('Y-m-d');
}데이터베이스에 실제로 저장되는 날짜 포맷을 지정하려면 모델의 Table 어트리뷰트에 dateFormat 인수를 사용하세요:
use Illuminate\Database\Eloquent\Attributes\Table;
#[Table(dateFormat: 'U')]
class Flight extends Model
{
// ...
}날짜 캐스팅, 직렬화, 그리고 타임존
기본적으로 date와 datetime 캐스트는 애플리케이션의 timezone 설정과 무관하게 UTC ISO-8601 형식(YYYY-MM-DDTHH:MM:SS.uuuuuuZ)으로 직렬화됩니다. 이 형식과 UTC 타임존을 일관되게 사용하는 것을 강력히 권장합니다. UTC를 기준으로 유지하면 PHP와 JavaScript의 다양한 날짜 라이브러리와의 호환성이 최대화됩니다.
date 또는 datetime 캐스트에 datetime:Y-m-d H:i:s와 같이 커스텀 포맷을 지정한 경우, 직렬화 시 Carbon 인스턴스의 내부 타임존이 사용됩니다. 이는 보통 애플리케이션의 timezone 설정값을 따릅니다. 단, created_at, updated_at와 같은 timestamp 컬럼은 애플리케이션 타임존 설정과 무관하게 항상 UTC로 포맷됩니다.
Enum 캐스팅
Eloquent는 어트리뷰트 값을 PHP Enum으로 캐스팅하는 기능도 지원합니다. casts 메서드에서 어트리뷰트와 Enum 클래스를 지정하면 됩니다:
use App\Enums\ServerStatus;
/**
* 캐스팅할 어트리뷰트를 반환합니다.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'status' => ServerStatus::class,
];
}캐스트를 정의하면, 어트리뷰트에 접근하거나 값을 설정할 때 자동으로 Enum 인스턴스로 변환됩니다:
if ($server->status == ServerStatus::Provisioned) {
$server->status = ServerStatus::Ready;
$server->save();
}Enum 배열 캐스팅
하나의 컬럼에 여러 Enum 값을 배열로 저장해야 할 경우, Laravel이 제공하는 AsEnumArrayObject 또는 AsEnumCollection 캐스트를 사용할 수 있습니다:
use App\Enums\ServerStatus;
use Illuminate\Database\Eloquent\Casts\AsEnumCollection;
/**
* 캐스팅할 어트리뷰트를 반환합니다.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'statuses' => AsEnumCollection::of(ServerStatus::class),
];
}암호화 캐스팅
encrypted 캐스트는 Laravel의 내장 암호화 기능을 사용해 어트리뷰트 값을 자동으로 암호화합니다. encrypted:array, encrypted:collection, encrypted:object, AsEncryptedArrayObject, AsEncryptedCollection 캐스트도 일반 버전과 동일하게 동작하되, 데이터베이스에 저장할 때 값이 암호화됩니다.
암호화된 텍스트는 원문보다 길이가 길고 예측 불가하므로, 해당 데이터베이스 컬럼의 타입은 반드시 TEXT 이상으로 설정해야 합니다. 또한 값이 암호화되어 저장되므로, 암호화된 어트리뷰트에 대한 쿼리나 검색은 불가능합니다.
키 교체
Laravel은 app 설정 파일의 key 값(주로 APP_KEY 환경 변수)을 사용해 문자열을 암호화합니다. 암호화 키를 교체해야 한다면 안전한 키 교체 방법을 참고하세요.
쿼리 타임 캐스팅
쿼리 실행 중에 캐스트를 적용해야 할 때가 있습니다. 예를 들어 다음처럼 테이블에서 집계 값을 직접 선택하는 경우를 생각해보겠습니다:
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')
])->get();이 쿼리 결과의 last_posted_at 어트리뷰트는 단순 문자열로 반환됩니다. 쿼리 실행 시 datetime 캐스트를 적용하고 싶다면 withCasts 메서드를 사용하면 됩니다:
$users = User::select([
'users.*',
'last_posted_at' => Post::selectRaw('MAX(created_at)')
->whereColumn('user_id', 'users.id')
])->withCasts([
'last_posted_at' => 'datetime'
])->get();Eloquent: 뮤테이터 & 캐스팅
커스텀 캐스트
Laravel은 다양한 내장 캐스트 타입을 제공하지만, 때로는 직접 캐스트 타입을 정의해야 할 때도 있습니다. make:cast Artisan 명령어를 실행하면 app/Casts 디렉터리에 새 캐스트 클래스가 생성됩니다:
php artisan make:cast AsJson커스텀 캐스트 클래스는 모두 CastsAttributes 인터페이스를 구현해야 합니다. 이 인터페이스는 get과 set 두 메서드를 요구합니다.
get: 데이터베이스의 원시 값을 캐스팅된 값으로 변환합니다.set: 캐스팅된 값을 데이터베이스에 저장할 수 있는 원시 값으로 변환합니다.
아래는 내장 json 캐스트를 커스텀 캐스트로 직접 구현한 예시입니다:
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class AsJson implements CastsAttributes
{
/**
* 값을 캐스팅합니다.
*
* @param array<string, mixed> $attributes
* @return array<string, mixed>
*/
public function get(
Model $model,
string $key,
mixed $value,
array $attributes,
): array {
return json_decode($value, true);
}
/**
* 값을 저장 형태로 변환합니다.
*
* @param array<string, mixed> $attributes
*/
public function set(
Model $model,
string $key,
mixed $value,
array $attributes,
): string {
return json_encode($value);
}
}커스텀 캐스트 타입을 정의했다면, 모델의 casts 메서드에서 해당 클래스명을 지정해 속성에 적용할 수 있습니다:
<?php
namespace App\Models;
use App\Casts\AsJson;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 캐스팅할 속성을 반환합니다.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'options' => AsJson::class,
];
}
}값 객체(Value Object) 캐스팅
캐스팅 대상이 반드시 기본 타입(int, string 등)일 필요는 없습니다. 객체로 캐스팅하는 것도 가능합니다. 방식은 기본 타입 캐스팅과 거의 동일하지만, 값 객체가 여러 데이터베이스 컬럼에 걸쳐 있을 경우 set 메서드가 컬럼명-값 쌍의 배열을 반환해야 합니다. 단일 컬럼만 사용한다면 저장할 값 하나만 반환하면 됩니다.
아래 예시에서는 여러 모델 속성을 하나의 Address 값 객체로 캐스팅하는 커스텀 캐스트를 정의합니다. Address 값 객체는 lineOne, lineTwo 두 개의 공개 프로퍼티를 가진다고 가정합니다:
<?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
{
/**
* 값을 캐스팅합니다.
*
* @param array<string, mixed> $attributes
*/
public function get(
Model $model,
string $key,
mixed $value,
array $attributes,
): Address {
return new Address(
$attributes['address_line_one'],
$attributes['address_line_two']
);
}
/**
* 값을 저장 형태로 변환합니다.
*
* @param array<string, mixed> $attributes
* @return array<string, string>
*/
public function set(
Model $model,
string $key,
mixed $value,
array $attributes,
): array {
if (! $value instanceof Address) {
throw new InvalidArgumentException('주어진 값이 Address 인스턴스가 아닙니다.');
}
return [
'address_line_one' => $value->lineOne,
'address_line_two' => $value->lineTwo,
];
}
}값 객체로 캐스팅된 속성은 모델이 저장될 때 변경 사항이 자동으로 동기화됩니다:
use App\Models\User;
$user = User::find(1);
$user->address->lineOne = '수정된 주소';
$user->save();NOTE
값 객체를 포함한 Eloquent 모델을 JSON이나 배열로 직렬화할 계획이라면, 값 객체에 Illuminate\Contracts\Support\Arrayable 및 JsonSerializable 인터페이스를 구현해야 합니다.
값 객체 캐싱
값 객체로 캐스팅된 속성은 처음 접근 시 Eloquent가 내부적으로 캐싱합니다. 따라서 같은 속성에 다시 접근하면 동일한 객체 인스턴스가 반환됩니다.
이 객체 캐싱 동작을 비활성화하려면, 커스텀 캐스트 클래스에 withoutObjectCaching 공개 프로퍼티를 선언하면 됩니다:
class AsAddress implements CastsAttributes
{
public bool $withoutObjectCaching = true;
// ...
}배열 / JSON 직렬화
Eloquent 모델을 toArray나 toJson으로 변환할 때, 커스텀 캐스트의 값 객체가 Illuminate\Contracts\Support\Arrayable 및 JsonSerializable 인터페이스를 구현하고 있다면 자동으로 직렬화됩니다. 그러나 서드파티 라이브러리에서 제공하는 값 객체는 이러한 인터페이스를 직접 추가하기 어려울 수 있습니다.
이런 경우, 커스텀 캐스트 클래스가 직렬화 책임을 직접 담당하도록 지정할 수 있습니다. Illuminate\Contracts\Database\Eloquent\SerializesCastableAttributes 인터페이스를 구현하고, 값 객체의 직렬화 결과를 반환하는 serialize 메서드를 정의하면 됩니다:
/**
* 값의 직렬화 표현을 반환합니다.
*
* @param array<string, mixed> $attributes
*/
public function serialize(
Model $model,
string $key,
mixed $value,
array $attributes,
): string {
return (string) $value;
}인바운드 캐스팅
값을 모델에 설정(set)할 때만 변환이 필요하고, 조회(get) 시에는 별도의 처리가 필요 없는 경우가 있습니다. 이런 경우를 위해 인바운드 전용 캐스트를 사용합니다.
인바운드 전용 캐스트는 CastsInboundAttributes 인터페이스를 구현하며, set 메서드만 정의하면 됩니다. --inbound 옵션을 사용하면 이 형태의 클래스를 바로 생성할 수 있습니다:
php artisan make:cast AsHash --inbound대표적인 예는 해싱(hashing) 캐스트입니다. 아래는 지정된 알고리즘으로 입력 값을 해싱하는 예시입니다:
<?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,
) {}
/**
* 값을 저장 형태로 변환합니다.
*
* @param array<string, mixed> $attributes
*/
public function set(
Model $model,
string $key,
mixed $value,
array $attributes,
): string {
return is_null($this->algorithm)
? bcrypt($value)
: hash($this->algorithm, $value);
}
}캐스트 파라미터
커스텀 캐스트를 모델에 적용할 때 : 문자로 클래스명과 파라미터를 구분하고, 여러 파라미터는 쉼표(,)로 구분합니다. 지정한 파라미터는 캐스트 클래스의 생성자로 전달됩니다:
/**
* 캐스팅할 속성을 반환합니다.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'secret' => AsHash::class.':sha256',
];
}캐스트 값 비교
두 캐스트 값이 동일한지 비교하는 방식을 직접 정의하고 싶다면, 커스텀 캐스트 클래스에 Illuminate\Contracts\Database\Eloquent\ComparesCastableAttributes 인터페이스를 구현하면 됩니다. 이를 통해 Eloquent가 모델 업데이트 시 어떤 값을 "변경된 것"으로 간주하고 데이터베이스에 저장할지를 세밀하게 제어할 수 있습니다.
이 인터페이스는 두 값이 같다고 판단될 경우 true를 반환하는 compare 메서드를 요구합니다:
/**
* 두 값이 동일한지 판단합니다.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $firstValue
* @param mixed $secondValue
* @return bool
*/
public function compare(
Model $model,
string $key,
mixed $firstValue,
mixed $secondValue
): bool {
return $firstValue === $secondValue;
}Castable
값 객체 클래스 자체가 어떤 캐스트 클래스를 사용할지를 스스로 결정하게 할 수도 있습니다. 모델에 캐스트 클래스를 직접 연결하는 대신, Illuminate\Contracts\Database\Eloquent\Castable 인터페이스를 구현한 값 객체 클래스를 지정합니다:
use App\ValueObjects\Address;
protected function casts(): array
{
return [
'address' => Address::class,
];
}Castable 인터페이스를 구현하는 객체는 castUsing 메서드를 정의해야 하며, 이 메서드는 실제 캐스팅을 담당할 캐스터 클래스의 이름을 반환해야 합니다:
<?php
namespace App\ValueObjects;
use Illuminate\Contracts\Database\Eloquent\Castable;
use App\Casts\AsAddress;
class Address implements Castable
{
/**
* 이 캐스트 대상으로/에서 캐스팅할 때 사용할 캐스터 클래스 이름을 반환합니다.
*
* @param array<string, mixed> $arguments
*/
public static function castUsing(array $arguments): string
{
return AsAddress::class;
}
}Castable 클래스를 사용할 때도 casts 메서드에서 파라미터를 전달할 수 있으며, 전달된 파라미터는 castUsing 메서드로 넘어갑니다:
use App\ValueObjects\Address;
protected function casts(): array
{
return [
'address' => Address::class.':argument',
];
}Castable과 익명 캐스트 클래스
Castable과 PHP의 익명 클래스를 조합하면, 값 객체와 캐스팅 로직을 하나의 파일에서 함께 정의할 수 있습니다. castUsing 메서드에서 CastsAttributes 인터페이스를 구현한 익명 클래스를 반환하면 됩니다:
<?php
namespace App\ValueObjects;
use Illuminate\Contracts\Database\Eloquent\Castable;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class Address implements Castable
{
// ...
/**
* 이 캐스트 대상으로/에서 캐스팅할 때 사용할 캐스터 클래스를 반환합니다.
*
* @param array<string, mixed> $arguments
*/
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,
];
}
};
}
}