Eloquent 뮤테이터 & 캐스팅
번역일: 2026년 6월 25일
Eloquent 뮤테이터 & 캐스팅
소개
액세서(Accessor), 뮤테이터(Mutator), 속성 캐스팅을 활용하면 모델 인스턴스에서 속성 값을 읽거나 쓸 때 원하는 방식으로 변환할 수 있습니다. 예를 들어, 데이터베이스에 저장할 때는 Laravel 암호화로 값을 암호화하고, Eloquent 모델에서 읽을 때는 자동으로 복호화할 수 있습니다. 또는 데이터베이스에 JSON 문자열로 저장된 값을 모델에서 접근할 때 자동으로 PHP 배열로 변환할 수도 있습니다.
액세서와 뮤테이터
액세서 정의
액세서는 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 직렬화 결과에 포함하려면 별도로 추가 설정이 필요합니다.
여러 속성으로 값 객체 만들기
액세서가 여러 모델 속성을 조합해 하나의 "값 객체"를 반환해야 할 때도 있습니다. 이 경우 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();문자열이나 불리언처럼 단순한 원시 타입 값도 연산 비용이 클 경우 캐싱을 활성화할 수 있습니다. 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();
}뮤테이터 정의
뮤테이터는 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를 적용한 결과를 저장합니다.
여러 속성 동시에 변환하기
뮤테이터가 여러 데이터베이스 컬럼에 동시에 값을 써야 할 때는 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 메서드에 속성명과 변환할 타입을 배열로 반환하면 됩니다.
지원하는 캐스트 타입 목록은 다음과 같습니다.
arrayAsStringable::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 캐스트를 사용하면 속성 값을 fluent 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']);ArrayObject 및 Collection 캐스팅
표준 array 캐스트는 PHP의 원시 배열 타입을 반환하기 때문에, 배열의 특정 오프셋을 직접 수정하면 PHP 오류가 발생합니다.
$user = User::find(1);
$user->options['key'] = $value; // PHP 오류 발생이를 해결하기 위해 Laravel은 JSON 속성을 PHP의 ArrayObject 클래스로 캐스팅하는 AsArrayObject를 제공합니다. 개별 오프셋을 수정해도 오류 없이 동작합니다.
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,
];
}기본 컬렉션 클래스 대신 커스텀 컬렉션 클래스를 사용하고 싶다면, using 메서드로 클래스명을 지정할 수 있습니다.
use App\Collections\OptionCollection;
use Illuminate\Database\Eloquent\Casts\AsCollection;
/**
* 캐스팅할 속성을 반환합니다.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'options' => AsCollection::using(OptionCollection::class),
];
}날짜 캐스팅
기본적으로 Eloquent는 created_at과 updated_at 컬럼을 Carbon 인스턴스로 캐스팅합니다. Carbon은 PHP DateTime 클래스를 확장해 다양한 날짜 처리 메서드를 제공합니다. 다른 날짜 속성도 casts 메서드에 datetime 또는 immutable_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');
}데이터베이스에 날짜를 저장할 때 사용할 포맷은 모델에 $dateFormat 프로퍼티로 지정합니다.
/**
* 모델 날짜 컬럼의 저장 포맷
*
* @var string
*/
protected $dateFormat = 'U';날짜 캐스팅, 직렬화, 타임존
기본적으로 date 및 datetime 캐스트는 애플리케이션의 timezone 설정과 무관하게 UTC ISO-8601 형식(YYYY-MM-DDTHH:MM:SS.uuuuuuZ)으로 날짜를 직렬화합니다. 데이터베이스 저장 시에도 UTC를 사용하고, 애플리케이션의 timezone 설정을 기본값인 UTC로 유지하는 것을 강력히 권장합니다. UTC를 일관되게 사용하면 PHP와 JavaScript의 다양한 날짜 라이브러리와의 호환성이 극대화됩니다.
datetime:Y-m-d H:i:s처럼 커스텀 포맷을 지정하면 Carbon 인스턴스 내부의 타임존이 직렬화에 사용되며, 이는 보통 애플리케이션의 timezone 설정값을 따릅니다. 단, created_at, updated_at 같은 timestamp 컬럼은 이 규칙에서 제외되어 항상 UTC 기준으로 포맷됩니다.
Enum 캐스팅
Eloquent는 PHP의 Backed 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 값의 배열을 저장해야 할 때는 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 속성은 단순 문자열로 반환됩니다. withCasts 메서드를 사용하면 쿼리 실행 시 캐스트를 지정할 수 있습니다.
$users = User::select([
'users.*',
'last_posted_at' => Post::selectRaw('MAX(created_at)')
->whereColumn('user_id', 'users.id')
])->withCasts([
'last_posted_at' => 'datetime'
])->get();커스텀 캐스트
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
{
/**
* 캐스팅할 속성을 반환합니다.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'options' => Json::class,
];
}
}