Eloquent: 뮤테이터 & 캐스팅
번역일: 2026년 7월 2일
Eloquent: 뮤테이터 & 캐스팅
소개
액세서(Accessor), 뮤테이터(Mutator), 어트리뷰트 캐스팅(Attribute Casting)을 활용하면 Eloquent 모델의 어트리뷰트 값을 읽거나 저장할 때 원하는 형태로 자동 변환할 수 있습니다.
예를 들어, 데이터베이스에는 암호화된 값을 저장하면서 모델에서 읽을 때는 자동으로 복호화하거나, 문자열로 저장된 JSON을 조회 시 배열로 변환하는 작업을 모델 안에서 깔끔하게 처리할 수 있습니다.
액세서와 뮤테이터
액세서 정의하기
액세서는 모델 어트리뷰트에 접근할 때 값을 변환합니다. 액세서를 정의하려면 모델에 Illuminate\Database\Eloquent\Casts\Attribute 타입을 반환하는 protected 메서드를 추가합니다. 메서드 이름은 해당 어트리뷰트의 camelCase 형태로 작성합니다.
아래 예시에서는 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),
);
}
}액세서의 get 콜백은 데이터베이스에 저장된 원시 값을 받아 변환된 값을 반환합니다. 위 예시에서 $user->first_name으로 접근하면 자동으로 ucfirst()가 적용된 값이 반환됩니다.
use App\Models\User;
$user = User::find(1);
$firstName = $user->first_name;액세서는 단일 어트리뷰트뿐 아니라 여러 어트리뷰트를 조합해 새로운 값을 만들어낼 수도 있습니다.
/**
* 사용자의 전체 이름을 가져옵니다.
*/
protected function fullName(): Attribute
{
return Attribute::make(
get: fn () => "{$this->first_name} {$this->last_name}",
);
}NOTE
이렇게 계산된 값을 모델의 배열/JSON 표현에도 포함하려면 해당 값을 append 목록에 추가해야 합니다.
액세서 캐싱
액세서에서 값 객체(Value Object)를 반환하는 경우, 해당 객체를 변경하면 저장 전에 Eloquent가 변경 사항을 자동으로 감지할 수 있도록 Eloquent가 객체를 캐싱합니다. 즉, 같은 어트리뷰트에 여러 번 접근해도 동일한 인스턴스를 반환합니다.
use App\Models\User;
$user = User::find(1);
$user->address->lineOne = '서울시 강남구';
$user->address->lineTwo = '테헤란로 123';단순한 스칼라 값을 반환하는 액세서도 캐싱하고 싶다면, Attribute 인스턴스를 만들 때 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();
}뮤테이터 정의하기
뮤테이터는 모델 어트리뷰트에 값을 설정할 때 값을 변환합니다. 뮤테이터를 정의하려면 Attribute::make를 호출할 때 set 인자를 함께 제공합니다.
아래 예시에서는 first_name 어트리뷰트에 대한 뮤테이터를 정의합니다. 이 뮤테이터는 $user->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'를 반환하고, 이 값이 실제 모델의 first_name 어트리뷰트에 저장됩니다.
하나의 뮤테이터로 여러 어트리뷰트를 동시에 설정할 수도 있습니다. 이때 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 암호화 기능으로 암호화하고, Eloquent 모델을 통해 해당 속성에 접근할 때는 자동으로 복호화되도록 할 수 있습니다. 또는 데이터베이스에 JSON 문자열로 저장된 값을 모델에서 읽을 때 자동으로 배열로 변환할 수도 있습니다.
Eloquent: 뮤테이터 & 캐스팅
액세서와 뮤테이터
액세서 정의하기
액세서(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 클로저는 DB에 저장된 원본 값을 인자로 받아, 원하는 형태로 가공한 값을 반환합니다. 액세서를 사용하려면 모델 인스턴스에서 속성에 접근하기만 하면 됩니다.
use App\Models\User;
$user = User::find(1);
$firstName = $user->first_name;NOTE
이렇게 계산된 값을 모델의 배열이나 JSON 표현에 포함시키려면 별도로 추가(append) 설정을 해야 합니다.
여러 속성으로 값 객체 만들기
때로는 여러 모델 속성을 조합해 하나의 "값 객체(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 = '서울시 강남구 테헤란로 123';
$user->address->lineTwo = '4층 401호';
$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 클로저는 속성에 설정하려는 값을 인자로 받아, 가공 후 반환합니다. 이 뮤테이터를 사용하려면 그냥 속성에 값을 할당하면 됩니다.
use App\Models\User;
$user = User::find(1);
$user->first_name = 'Sally';위 예시에서 set 콜백은 'Sally'를 받아 strtolower를 적용한 뒤, 결과값을 모델 내부의 $attributes 배열에 저장합니다.
여러 속성을 한 번에 변환하기
하나의 뮤테이터가 여러 DB 컬럼 값을 동시에 설정해야 할 때는, set 클로저에서 배열을 반환하면 됩니다. 배열의 각 키는 모델의 속성명(DB 컬럼명)과 일치해야 합니다.
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,
],
);
}어트리뷰트 캐스팅
캐스팅(Casting)은 액세서나 뮤테이터를 별도로 정의하지 않고도, 모델 어트리뷰트를 원하는 데이터 타입으로 자동 변환해주는 기능입니다. 모델의 $casts 프로퍼티에 어트리뷰트 이름과 변환할 타입을 배열로 정의하면 됩니다.
지원하는 캐스트 타입은 다음과 같습니다:
arrayAsStringable::classbooleancollectiondatedatetimeimmutable_dateimmutable_datetimedecimal:<precision>doubleencryptedencrypted:arrayencrypted:collectionencrypted:objectfloathashedintegerobjectrealstringtimestamp
예를 들어, 데이터베이스에 0 또는 1로 저장된 is_admin 컬럼을 PHP의 boolean 타입으로 자동 변환하려면 다음과 같이 정의합니다:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 캐스팅할 어트리뷰트 목록
*
* @var array
*/
protected $casts = [
'is_admin' => 'boolean',
];
}이렇게 정의하면 데이터베이스에는 정수로 저장되어 있더라도, 모델을 통해 접근할 때는 항상 boolean 값으로 반환됩니다:
$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
{
/**
* 캐스팅할 어트리뷰트 목록
*
* @var array
*/
protected $casts = [
'directory' => AsStringable::class,
];
}배열 & JSON 캐스팅
array 캐스트는 JSON 형태로 직렬화된 컬럼을 다룰 때 특히 유용합니다. 데이터베이스의 JSON 또는 TEXT 컬럼에 JSON 문자열이 저장되어 있다면, array 캐스트를 지정하는 것만으로 어트리뷰트에 접근할 때 자동으로 PHP 배열로 역직렬화됩니다:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 캐스팅할 어트리뷰트 목록
*
* @var array
*/
protected $casts = [
'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']);ArrayObject & Collection 캐스팅
일반 array 캐스트는 대부분의 상황에서 잘 동작하지만, 배열의 특정 오프셋을 직접 수정하려 하면 PHP 에러가 발생한다는 단점이 있습니다:
$user = User::find(1);
// PHP 에러 발생!
$user->options['key'] = $value;이 문제를 해결하기 위해 Laravel은 AsArrayObject 캐스트를 제공합니다. 이 캐스트는 JSON 어트리뷰트를 PHP의 ArrayObject 클래스 인스턴스로 변환하며, Laravel의 커스텀 캐스트 구현 덕분에 개별 오프셋을 안전하게 수정할 수 있습니다:
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
/**
* 캐스팅할 어트리뷰트 목록
*
* @var array
*/
protected $casts = [
'options' => AsArrayObject::class,
];마찬가지로 AsCollection 캐스트를 사용하면 JSON 어트리뷰트를 Laravel 컬렉션 인스턴스로 변환할 수 있습니다:
use Illuminate\Database\Eloquent\Casts\AsCollection;
/**
* 캐스팅할 어트리뷰트 목록
*
* @var array
*/
protected $casts = [
'options' => AsCollection::class,
];기본 컬렉션 클래스 대신 커스텀 컬렉션 클래스를 사용하고 싶다면, 캐스트 인수로 클래스명을 전달하면 됩니다:
use App\Collections\OptionCollection;
use Illuminate\Database\Eloquent\Casts\AsCollection;
/**
* 캐스팅할 어트리뷰트 목록
*
* @var array
*/
protected $casts = [
'options' => AsCollection::class.':'.OptionCollection::class,
];날짜 캐스팅
Eloquent는 기본적으로 created_at과 updated_at 컬럼을 Carbon 인스턴스로 변환합니다. Carbon은 PHP DateTime 클래스를 확장하며 다양한 날짜 조작 메서드를 제공합니다. $casts 프로퍼티에 추가 날짜 어트리뷰트를 지정해 동일하게 변환할 수 있으며, 일반적으로 datetime 또는 immutable_datetime 캐스트 타입을 사용합니다.
date나 datetime 캐스트를 정의할 때 날짜 포맷을 함께 지정할 수 있습니다. 이 포맷은 모델이 배열이나 JSON으로 직렬화될 때 사용됩니다:
/**
* 캐스팅할 어트리뷰트 목록
*
* @var array
*/
protected $casts = [
'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');
}데이터베이스에 실제로 저장될 날짜 포맷을 지정하려면 모델에 $dateFormat 프로퍼티를 정의하세요:
/**
* 모델의 날짜 컬럼 저장 포맷
*
* @var string
*/
protected $dateFormat = 'U';날짜 캐스팅, 직렬화, 그리고 타임존
기본적으로 date와 datetime 캐스트는 애플리케이션의 timezone 설정과 관계없이 날짜를 UTC ISO-8601 형식(YYYY-MM-DDTHH:MM:SS.uuuuuuZ)으로 직렬화합니다. 애플리케이션의 타임존 설정을 기본값인 UTC에서 변경하지 않고, 항상 UTC로 날짜를 저장하는 방식을 강력히 권장합니다. UTC를 일관되게 사용하면 PHP와 JavaScript의 다양한 날짜 라이브러리와의 호환성이 극대화됩니다.
NOTE
한국 서비스를 개발할 때 APP_TIMEZONE=Asia/Seoul로 설정하고 싶을 수 있지만, 데이터베이스는 UTC로 유지하고 표시 시점에만 변환하는 패턴이 유지보수 측면에서 훨씬 안전합니다.
datetime:Y-m-d H:i:s처럼 커스텀 포맷이 적용된 경우에는, 직렬화 시 Carbon 인스턴스 내부의 타임존(일반적으로 애플리케이션의 timezone 설정값)이 사용됩니다.
Enum 캐스팅
Eloquent는 PHP Enum으로의 캐스팅도 지원합니다. $casts 프로퍼티에 어트리뷰트와 해당 Enum 클래스를 지정하면 됩니다:
use App\Enums\ServerStatus;
/**
* 캐스팅할 어트리뷰트 목록
*
* @var array
*/
protected $casts = [
'status' => ServerStatus::class,
];캐스트를 정의하면 어트리뷰트에 접근하거나 값을 설정할 때 자동으로 Enum으로 변환됩니다:
if ($server->status == ServerStatus::Provisioned) {
$server->status = ServerStatus::Ready;
$server->save();
}Enum 배열 캐스팅
하나의 컬럼에 여러 Enum 값을 배열로 저장해야 할 때는 AsEnumArrayObject 또는 AsEnumCollection 캐스트를 사용하세요:
use App\Enums\ServerStatus;
use Illuminate\Database\Eloquent\Casts\AsEnumCollection;
/**
* 캐스팅할 어트리뷰트 목록
*
* @var array
*/
protected $casts = [
'statuses' => AsEnumCollection::class.':'.ServerStatus::class,
];암호화 캐스팅
encrypted 캐스트를 사용하면 Laravel의 내장 암호화 기능을 통해 어트리뷰트 값을 자동으로 암호화하여 저장하고, 접근 시 자동으로 복호화합니다. encrypted:array, encrypted:collection, encrypted:object, AsEncryptedArrayObject, AsEncryptedCollection 캐스트도 동일하게 동작하며, 데이터베이스에는 암호화된 값이 저장됩니다.
암호화된 텍스트는 원본보다 길이가 길고 예측할 수 없으므로, 해당 컬럼은 반드시 TEXT 타입 이상으로 설정해야 합니다. 또한 암호화된 값은 데이터베이스에서 직접 쿼리하거나 검색할 수 없다는 점을 유의하세요.
키 교체(Key Rotation)
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 Json모든 커스텀 캐스트 클래스는 CastsAttributes 인터페이스를 구현해야 합니다. 이 인터페이스를 구현하는 클래스는 반드시 get과 set 메서드를 정의해야 합니다.
get: 데이터베이스의 원시 값을 캐스팅된 값으로 변환합니다.set: 캐스팅된 값을 데이터베이스에 저장 가능한 원시 값으로 변환합니다.
아래 예시는 내장 json 캐스트 타입과 동일한 동작을 하는 커스텀 캐스트입니다:
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class Json 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\Json;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* 캐스팅할 속성 목록
*
* @var array
*/
protected $casts = [
'options' => Json::class,
];
}값 객체 캐스팅
캐스팅 대상이 반드시 기본 타입(string, int 등)일 필요는 없습니다. 값 객체(Value Object)로도 캐스팅할 수 있습니다. 기본 타입 캐스팅과 방식은 비슷하지만, set 메서드는 모델에 저장될 원시 값의 키/값 배열을 반환해야 한다는 차이가 있습니다.
아래 예시에서는 여러 모델 컬럼을 하나의 Address 값 객체로 캐스팅하는 커스텀 캐스트를 정의합니다. Address 값 객체는 lineOne과 lineTwo 두 개의 공개 프로퍼티를 가진다고 가정합니다:
<?php
namespace App\Casts;
use App\ValueObjects\Address as AddressValueObject;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
use InvalidArgumentException;
class Address implements CastsAttributes
{
/**
* 데이터베이스 값을 값 객체로 캐스팅합니다.
*
* @param array<string, mixed> $attributes
*/
public function get(Model $model, string $key, mixed $value, array $attributes): AddressValueObject
{
return new AddressValueObject(
$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 AddressValueObject) {
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 Address 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;
}인바운드 캐스팅
때로는 모델에 값을 저장할 때만 변환을 적용하고, 읽어올 때는 아무런 처리도 하지 않아도 되는 경우가 있습니다. 이럴 때 인바운드 전용 캐스트를 사용합니다.
인바운드 전용 캐스트는 CastsInboundAttributes 인터페이스를 구현하며, set 메서드만 정의하면 됩니다. make:cast 명령어에 --inbound 옵션을 붙이면 인바운드 전용 캐스트 클래스를 생성할 수 있습니다:
php artisan make:cast Hash --inbound인바운드 캐스트의 대표적인 예는 해시 처리입니다. 아래 예시는 지정한 알고리즘으로 입력값을 해싱하는 캐스트입니다:
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
use Illuminate\Database\Eloquent\Model;
class Hash 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);
}
}캐스트 파라미터
커스텀 캐스트를 모델에 연결할 때, 클래스명 뒤에 :로 구분하여 파라미터를 전달할 수 있습니다. 파라미터가 여러 개인 경우 쉼표로 구분하며, 이 파라미터들은 캐스트 클래스의 생성자로 전달됩니다:
/**
* 캐스팅할 속성 목록
*
* @var array
*/
protected $casts = [
'secret' => Hash::class.':sha256',
];Castable
값 객체가 자신에게 맞는 캐스트 클래스를 스스로 정의하도록 만들 수도 있습니다. 모델의 $casts에 커스텀 캐스트 클래스 대신, Illuminate\Contracts\Database\Eloquent\Castable 인터페이스를 구현한 값 객체 클래스를 직접 지정하는 방식입니다:
use App\ValueObjects\Address;
protected $casts = [
'address' => Address::class,
];Castable 인터페이스를 구현하는 객체는, 실제 캐스팅을 담당할 캐스터 클래스명을 반환하는 castUsing 정적 메서드를 정의해야 합니다:
<?php
namespace App\ValueObjects;
use Illuminate\Contracts\Database\Eloquent\Castable;
use App\Casts\Address as AddressCast;
class Address implements Castable
{
/**
* 이 클래스를 캐스팅할 때 사용할 캐스터 클래스명을 반환합니다.
*
* @param array<string, mixed> $arguments
*/
public static function castUsing(array $arguments): string
{
return AddressCast::class;
}
}Castable 클래스를 사용할 때도 $casts 정의에 파라미터를 전달할 수 있습니다. 전달된 파라미터는 castUsing 메서드로 넘겨집니다:
use App\ValueObjects\Address;
protected $casts = [
'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,
];
}
};
}
}