본문 바로가기

Eloquent: 뮤테이터 & 캐스팅

업데이트됨

번역일: 2026년 9월 23일

이 페이지는 원문이 업데이트되어 번역이 갱신되었습니다.

원문 수정
2026년 9월 23일
번역 갱신
2026년 9월 23일

Eloquent: 뮤테이터 & 캐스팅

소개

액세서(accessor)와 뮤테이터(mutator), 그리고 속성 캐스팅(attribute casting)을 사용하면 Eloquent 모델 인스턴스에서 속성 값을 가져오거나 설정할 때 원하는 형태로 자유롭게 변환할 수 있습니다.

예를 들어 Laravel의 암호화 기능을 이용해 데이터베이스에 저장하기 전에 값을 암호화하고, Eloquent 모델을 통해 접근할 때 자동으로 복호화하도록 만들 수 있습니다. 또는 데이터베이스에 저장된 JSON 문자열을 모델 속성으로 접근할 때 자동으로 배열로 변환하고 싶을 수도 있습니다. 이런 작업들을 매번 직접 처리할 필요 없이, Eloquent가 이를 자동으로 처리하도록 설정할 수 있습니다.

NOTE

이 문서를 읽기 전에 먼저 Eloquent 모델과 그 기본 개념에 익숙해지는 것이 좋습니다. 아직 익숙하지 않다면 Eloquent 시작하기 문서를 먼저 살펴보시기 바랍니다.

이후 이어지는 섹션에서 액세서, 뮤테이터, 그리고 속성 캐스팅을 각각 어떻게 정의하고 사용하는지 자세히 살펴보겠습니다.

접근자 & 뮤테이터

접근자 정의하기

접근자(Accessor)는 Eloquent 속성값을 조회할 때 그 값을 가공해줍니다. 접근자를 정의하려면, 모델에 접근할 속성을 나타내는 protected 메서드를 만들면 됩니다. 이 메서드의 이름은 실제로 대응하는 모델 속성(프로퍼티)의 "카멜 케이스" 표현과 일치해야 합니다. 물론, 실제로 이런 규칙이 적용 가능한 속성명이 아닌 경우도 있습니다.

예를 들어, 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 { /** * 사용자의 이름(First name)을 조회합니다. */ protected function firstName(): Attribute { return Attribute::make( get: fn (string $value) => ucfirst($value), ); } }

위 예제와 같이, 모든 접근자 메서드는 속성값을 조회하거나 설정할 때 사용될 로직을 정의하는 Attribute 인스턴스를 반환합니다. 위 예제에서는 속성값을 조회하는 로직만 정의했습니다. 그렇게 하려면 Attribute 클래스의 생성자에 get 인자를 전달하면 됩니다.

이렇게 정의하고 나면, first_name 속성에 접근할 때마다 자동으로 접근자 메서드가 실행됩니다.

use App\Models\User; $user = User::find(1); $firstName = $user->first_name;

NOTE

이렇게 계산된 값들을 모델의 배열 / JSON 표현에 추가하고 싶다면, 속성을 배열에 추가하기를 참고하시기 바랍니다.

여러 속성을 이용해 값 객체 만들기

경우에 따라서는 접근자가 하나의 모델 속성 값을 가공하는 것이 아니라, 여러 모델 속성을 조합하여 하나의 "값 객체"로 변환해야 할 수도 있습니다. 이런 경우 get 클로저는 두 번째 인자로 $attributes를 받을 수 있는데, 이 인자는 모델이 가진 모든 현재 속성값이 담긴 배열로 자동 전달됩니다.

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'], ), ); }

접근자 캐싱

접근자가 값 객체를 반환하는 경우, 이 객체에 변경사항이 생기면 모델이 저장되기 전에 자동으로 그 변경사항이 모델에 동기화되도록 하고 싶을 수 있습니다. 이는 접근자에서 반환된 객체에 대한 참조를 Eloquent가 유지함으로써, 접근자가 호출될 때마다 매번 새 값 객체를 생성하지 않고 계속 같은 인스턴스를 반환할 수 있도록 해주면 가능합니다. 이렇게 하려면 접근자를 정의할 때 shouldCache 메서드를 호출하면 됩니다.

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

이와 다르게, 원시(primitive) 값을 계산하는 접근자에 대해서도, 반복 호출 시 값 변환에 고비용의 연산이 필요한 경우에는 자동으로 캐싱을 원할 수 있습니다. 이럴 때는 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 (mixed $value, array $attributes) => new Address( $attributes['address_line_one'], $attributes['address_line_two'], ), )->withoutObjectCaching(); }

뮤테이터 정의하기

뮤테이터(Mutator)는 Eloquent 속성값이 설정될 때 그 값을 가공합니다. 뮤테이터를 정의하려면, 속성을 정의할 때 set 인자를 함께 지정하면 됩니다. 예를 들어, first_name 속성에 대한 뮤테이터를 정의해보겠습니다. 이 뮤테이터는 모델에서 first_name 속성값을 설정하려고 할 때 자동으로 호출됩니다.

<?php namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; class User extends Model { /** * 사용자의 이름(First name)을 상호작용합니다. */ protected function firstName(): Attribute { return Attribute::make( get: fn (string $value) => ucfirst($value), set: fn (string $value) => strtolower($value), ); } }

뮤테이터 클로저는 설정하려는 값을 인자로 받아, 그 값을 원하는 형태로 가공한 뒤 반환합니다. 아래 예제에서는 first_name 속성값을 설정할 때 사용할 뮤테이터를 정의해보겠습니다.

use App\Models\User; $user = User::find(1); $user->first_name = 'Sally';

위 예제에서 set 콜백은 Sally라는 값을 인자로 받아, 그 값에 strtolower 함수를 적용한 후 결과값을 모델 내부의 $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, ], ); }

접근자와 뮤테이터

접근자 정의하기

접근자(Accessor)는 Eloquent 속성 값을 가져올 때 그 값을 변형해주는 기능입니다. 접근자를 정의하려면, 접근하고자 하는 속성을 나타내는 protected 메서드를 모델에 작성하면 됩니다. 이때 메서드 이름은 실제 데이터베이스 컬럼(또는 모델 속성)을 "카멜 케이스"로 표현한 이름과 일치해야 합니다.

예를 들어 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 { /** * Get the user's first name. */ protected function firstName(): Attribute { return Attribute::make( get: fn (string $value) => ucfirst($value), ); } }

접근자 메서드는 Attribute 인스턴스를 반환하며, 이 인스턴스는 속성을 어떻게 가져올지, 그리고 필요하다면 어떻게 저장할지를 정의합니다. 위 예제에서는 값을 가져오는 방법만 정의했으므로, Attribute 클래스의 생성자에 get 인자만 전달했습니다.

보시다시피 컬럼의 원본 값이 접근자로 전달되므로, 이 값을 원하는 대로 가공해서 반환할 수 있습니다. 접근자로 가공된 값을 사용하려면, 모델 인스턴스에서 first_name 속성에 접근하기만 하면 됩니다.

use App\Models\User; $user = User::find(1); $firstName = $user->first_name;

NOTE

이렇게 계산된 값을 모델의 배열/JSON 표현에도 포함시키고 싶다면 값을 추가로 append해주어야 합니다.

여러 속성을 조합해 값 객체 만들기

경우에 따라 접근자가 여러 모델 속성을 하나의 "값 객체(Value Object)"로 변환해야 할 때가 있습니다. 이럴 때는 get 클로저에 두 번째 인자로 $attributes를 받을 수 있는데, Eloquent가 이 인자에 모델의 현재 속성값을 모두 담은 배열을 자동으로 전달해줍니다.

use App\Support\Address; use Illuminate\Database\Eloquent\Casts\Attribute; /** * Interact with the user's address. */ 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 = 'Updated Address Line 1 Value'; $user->address->lineTwo = 'Updated Address Line 2 Value'; $user->save();

한편, 문자열이나 불리언 같은 원시 타입 값에 대해서도 캐싱을 적용하고 싶을 때가 있습니다. 특히 계산 비용이 큰 연산이라면 캐싱이 유용합니다. 이런 경우 접근자를 정의할 때 shouldCache 메서드를 호출하면 됩니다.

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

반대로 속성의 객체 캐싱 동작을 비활성화하고 싶다면, 속성을 정의할 때 withoutObjectCaching 메서드를 호출하면 됩니다.

/** * Interact with the user's address. */ 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 속성 값을 저장할 때 그 값을 변형해주는 기능입니다. 뮤테이터를 정의하려면 속성을 정의할 때 set 인자를 함께 지정하면 됩니다. first_name 속성에 대한 뮤테이터를 정의해보겠습니다. 이 뮤테이터는 모델의 first_name 속성값을 설정하려고 할 때 자동으로 호출됩니다.

<?php namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; class User extends Model { /** * Interact with the user's first name. */ protected function firstName(): Attribute { return Attribute::make( get: fn (string $value) => ucfirst($value), set: fn (string $value) => strtolower($value), ); } }

뮤테이터 클로저는 속성에 설정하려는 값을 전달받으며, 이 값을 원하는 대로 가공한 뒤 반환할 수 있습니다. 정의한 뮤테이터를 사용하려면 Eloquent 모델의 first_name 속성에 값을 설정하기만 하면 됩니다.

use App\Models\User; $user = User::find(1); $user->first_name = 'Sally';

이 예제에서 set 콜백은 Sally라는 값을 전달받습니다. 뮤테이터는 이 이름에 strtolower 함수를 적용한 뒤, 그 결과값을 모델 내부의 $attributes 배열에 저장합니다.

여러 속성 한 번에 변경하기

경우에 따라 뮤테이터가 하나의 값을 여러 속성에 나누어 저장해야 할 때가 있습니다. 이런 경우 set 클로저에서 배열을 반환하면 됩니다. 이때 배열의 각 키는 모델과 연관된 실제 속성 / 데이터베이스 컬럼 이름과 일치해야 합니다.

use App\Support\Address; use Illuminate\Database\Eloquent\Casts\Attribute; /** * Interact with the user's address. */ 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 뮤테이터

속성 캐스팅

속성 캐스팅(Attribute Casting)을 사용하면 접근자나 뮤테이터처럼 별도의 메서드를 정의하지 않고도 비슷한 기능을 구현할 수 있습니다. 모델에 casts 메서드를 정의해두면, 속성 값을 자주 사용하는 데이터 타입으로 손쉽게 변환할 수 있습니다.

casts 메서드는 키에는 캐스팅할 속성명을, 값에는 변환할 타입을 지정한 배열을 반환해야 합니다. 지원되는 캐스트 타입은 다음과 같습니다.

  • array
  • AsFluent::class
  • AsStringable::class
  • AsUri::class
  • AsVector::class
  • boolean
  • collection
  • date
  • datetime
  • immutable_date
  • immutable_datetime
  • decimal:<precision>
  • double
  • encrypted
  • encrypted:array
  • encrypted:collection
  • encrypted:object
  • float
  • hashed
  • integer
  • object
  • real
  • string
  • timestamp

예를 들어, 데이터베이스에는 정수(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 배열로 역직렬화됩니다. 반대로 options 속성에 값을 대입하면, 지정한 배열이 저장을 위해 자동으로 JSON으로 다시 직렬화됩니다.

use App\Models\User; $user = User::find(1); $options = $user->options; $options['key'] = 'value'; $user->options = $options; $user->save();

JSON 속성의 특정 필드 하나만 좀 더 간결한 문법으로 업데이트하려면, 해당 속성을 매스 할당 가능(mass assignable)하게 설정한 뒤 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', ]; }

배열 객체 및 컬렉션 캐스팅

표준 array 캐스트는 대부분의 상황에서 충분하지만 몇 가지 단점이 있습니다. array 캐스트는 PHP의 원시 타입(primitive type)을 반환하기 때문에, 배열의 특정 오프셋 값을 직접 변경할 수 없습니다. 예를 들어 다음 코드는 PHP 오류를 발생시킵니다.

$user = User::find(1); $user->options['key'] = $value;

이 문제를 해결하기 위해 라라벨은 JSON 속성을 ArrayObject 클래스로 캐스팅하는 AsArrayObject 캐스트를 제공합니다. 이 기능은 라라벨의 커스텀 캐스트 구현을 기반으로 하며, 변경된 객체를 지능적으로 캐시하고 변환함으로써 개별 오프셋을 수정해도 PHP 오류가 발생하지 않도록 처리합니다. AsArrayObject 캐스트는 다음과 같이 속성에 지정하기만 하면 됩니다.

use Illuminate\Database\Eloquent\Casts\AsArrayObject; /** * 캐스팅할 속성을 반환합니다. * * @return array<string, string> */ protected function casts(): array { return [ 'options' => AsArrayObject::class, ]; }

마찬가지로, 라라벨은 JSON 속성을 라라벨 컬렉션 인스턴스로 캐스팅하는 AsCollection 캐스트도 제공합니다.

use Illuminate\Database\Eloquent\Casts\AsCollection; /** * 캐스팅할 속성을 반환합니다. * * @return array<string, string> */ protected function casts(): array { return [ 'options' => AsCollection::class, ]; }

기본적으로 AsArrayObject나 AsCollection으로 캐스팅된 속성에 null을 대입하면 데이터베이스에는 JSON의 null 값으로 저장됩니다. 만약 null 값을 데이터베이스의 순수 SQL NULL 값으로 저장하고 싶다면, 캐스트를 정의할 때 nullable 메서드를 호출하면 됩니다.

use Illuminate\Database\Eloquent\Casts\AsCollection; /** * 캐스팅할 속성을 반환합니다. * * @return array<string, string> */ protected function casts(): array { return [ 'options' => AsCollection::nullable(), ]; }

nullable 메서드는 커스텀 컬렉션 클래스와 함께 사용할 수도 있습니다.

'options' => AsCollection::nullable(OptionCollection::class),

AsArrayObject 캐스트 역시 동일하게 동작하는 nullable 메서드를 제공합니다.

기본 컬렉션 클래스 대신 커스텀 컬렉션 클래스를 사용해 AsCollection 캐스트를 인스턴스화하고 싶다면, 캐스트 인자로 컬렉션 클래스명을 전달하면 됩니다.

use App\Collections\OptionCollection; use Illuminate\Database\Eloquent\Casts\AsCollection; /** * 캐스팅할 속성을 반환합니다. * * @return array<string, string> */ protected function casts(): array { return [ 'options' => AsCollection::using(OptionCollection::class), ]; }

컬렉션의 mapInto 메서드를 이용해 컬렉션 항목들을 지정한 클래스로 매핑하고 싶다면 of 메서드를 사용할 수 있습니다.

use App\ValueObjects\Option; use Illuminate\Database\Eloquent\Casts\AsCollection; /** * 캐스팅할 속성을 반환합니다. * * @return array<string, string> */ protected function casts(): array { return [ 'options' => AsCollection::of(Option::class) ]; }

컬렉션을 객체로 매핑할 때는, 해당 인스턴스가 데이터베이스에 JSON으로 어떻게 직렬화되어야 하는지 정의할 수 있도록 Illuminate\Contracts\Support\Arrayable과 JsonSerializable 인터페이스를 구현해야 합니다.

<?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(); } }

벡터 캐스팅

Illuminate\Database\Eloquent\Casts\AsVector 캐스트 클래스를 사용하면 데이터베이스의 벡터(vector) 컬럼을 PHP 배열로, 또는 그 반대로 캐스팅할 수 있습니다.

use Illuminate\Database\Eloquent\Casts\AsVector; /** * 캐스팅할 속성을 반환합니다. * * @return array<string, string> */ protected function casts(): array { return [ 'embedding' => AsVector::class, ]; }

속성 값을 설정할 때는 PHP 배열이나 라라벨 컬렉션과 같은 Arrayable 인스턴스를 사용할 수 있습니다. 속성 값을 조회할 때는 float 타입의 배열이 반환됩니다.

바이너리 캐스팅

Eloquent 모델에 자동 증가 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 컬럼을 PHP의 DateTime 클래스를 확장하여 다양한 편의 메서드를 제공하는 Carbon 인스턴스로 캐스팅합니다. 모델의 casts 메서드 안에 날짜 캐스트를 추가로 정의하면 다른 날짜 속성도 원하는 대로 캐스팅할 수 있습니다. 보통은 datetime이나 immutable_datetime 캐스트 타입을 사용하는 것이 좋습니다.

date나 datetime 캐스트를 정의할 때 날짜 형식(format)을 함께 지정할 수도 있습니다. 이 형식은 모델이 배열이나 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 속성(attribute)에서 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)로 직렬화합니다. 이 직렬화 형식을 그대로 유지하는 것을 강력히 권장하며, 마찬가지로 애플리케이션의 timezone 설정을 기본값인 UTC에서 변경하지 않고 날짜를 UTC로 저장하는 것이 좋습니다. 애플리케이션 전반에 걸쳐 일관되게 UTC 타임존을 사용하면 PHP나 JavaScript로 작성된 다른 날짜 처리 라이브러리들과의 호환성을 최대한 높일 수 있습니다.

datetime:Y-m-d H:i:s처럼 date나 datetime 캐스트에 커스텀 형식을 지정한 경우, 날짜를 직렬화할 때 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 값을 배열로 저장해야 할 수도 있습니다. 이럴 때는 라라벨이 제공하는 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 캐스트는 라라벨에 내장된 암호화 기능을 이용해 모델 속성 값을 암호화합니다. 이 외에도 encrypted:array, encrypted:collection, encrypted:object, AsEncryptedArrayObject, AsEncryptedCollection 캐스트는 암호화되지 않은 대응 캐스트와 동일하게 동작하지만, 예상하시는 것처럼 실제 값이 데이터베이스에 저장될 때는 암호화된다는 차이가 있습니다.

암호화된 텍스트의 최종 길이는 예측하기 어렵고 평문보다 길어지므로, 관련 데이터베이스 컬럼은 반드시 TEXT 타입 이상으로 설정해야 합니다. 또한 값이 데이터베이스에 암호화되어 저장되기 때문에, 암호화된 속성 값을 기준으로 조회하거나 검색할 수는 없습니다.

키 로테이션(Key Rotation)

이미 알고 계시겠지만, 라라벨은 애플리케이션의 app 설정 파일에 지정된 key 설정값을 이용해 문자열을 암호화합니다. 일반적으로 이 값은 APP_KEY 환경 변수 값과 일치합니다. 애플리케이션의 암호화 키를 교체해야 한다면, 안전하게 키를 로테이션하는 방법을 참고하시기 바랍니다.

쿼리 실행 시점의 캐스팅

테이블에서 원시(raw) 값을 조회하는 경우처럼, 쿼리를 실행하는 시점에 캐스트를 적용해야 할 때가 있습니다. 다음 쿼리를 예로 들어보겠습니다.

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 메서드는 캐스팅된 값을 데이터베이스에 저장할 수 있는 원시 값으로 변환하는 역할을 합니다. 예를 들어, Laravel에 기본 내장된 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); } }

커스텀 캐스트 타입을 정의했다면, 클래스명을 사용해서 모델 속성에 연결할 수 있습니다.

<?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, ]; } }

NOTE

커스텀 캐스트를 직접 만들기 전에, Laravel이 이미 AsStringable, AsCollection, AsEncryptedCollection처럼 자주 쓰이는 패턴을 기본 캐스트로 제공하고 있는지 먼저 확인해 보는 것이 좋습니다.

값 객체 캐스팅

캐스팅 대상이 반드시 원시 타입(primitive type)일 필요는 없습니다. 값을 객체로 캐스팅할 수도 있습니다. 값을 객체로 캐스팅하는 커스텀 캐스트를 정의하는 방식은 원시 타입으로 캐스팅하는 것과 매우 비슷합니다. 다만 값 객체가 여러 개의 데이터베이스 컬럼을 포괄하는 경우, set 메서드는 모델에 저장할 원시 값들을 키/값 쌍의 배열로 반환해야 합니다. 값 객체가 단일 컬럼에만 관여한다면, 저장 가능한 값을 그대로 반환하면 됩니다.

예를 들어, 여러 모델 값을 하나의 Address 값 객체로 캐스팅하는 커스텀 캐스트 클래스를 정의해 보겠습니다. Address 값 객체는 lineOne, lineTwo라는 두 개의 public 속성을 가지고 있다고 가정합니다.

<?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('The given value is not an Address instance.'); } return [ 'address_line_one' => $value->lineOne, 'address_line_two' => $value->lineTwo, ]; } }

값 객체로 캐스팅된 속성을 수정하면, 모델이 저장되기 전에 그 변경 사항이 자동으로 모델에 다시 동기화됩니다.

use App\Models\User; $user = User::find(1); $user->address->lineOne = 'Updated Address Value'; $user->save();

NOTE

값 객체를 포함하는 Eloquent 모델을 JSON이나 배열로 직렬화할 계획이라면, 값 객체에 Illuminate\Contracts\Support\Arrayable과 JsonSerializable 인터페이스를 구현해야 합니다.

값 객체 캐싱

값 객체로 캐스팅되는 속성이 한 번 값으로 확인(resolve)되면, Eloquent는 이를 캐싱합니다. 따라서 같은 속성에 다시 접근하면 동일한 객체 인스턴스가 반환됩니다.

커스텀 캐스트 클래스의 이러한 객체 캐싱 동작을 비활성화하고 싶다면, 커스텀 캐스트 클래스에 public withoutObjectCaching 속성을 선언하면 됩니다.

class AsAddress implements CastsAttributes { public bool $withoutObjectCaching = true; // ... }

배열 / JSON 직렬화

Eloquent 모델을 toArray, toJson 메서드를 사용해서 배열이나 JSON으로 변환할 때, 커스텀 캐스트로 만들어진 값 객체가 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 메서드만 정의하면 됩니다. make:cast Artisan 명령어에 --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가 어떤 값을 "변경된 값"으로 간주해서 데이터베이스에 저장할지 세밀하게 제어할 수 있습니다.

이 인터페이스는 클래스에 compare 메서드를 포함하도록 요구하며, 두 값이 같다고 판단되면 true를 반환해야 합니다.

/** * 주어진 두 값이 같은지 판단합니다. * * @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; }

Castables

애플리케이션의 값 객체가 자신만의 커스텀 캐스트 클래스를 직접 정의하도록 만들고 싶을 수 있습니다. 이 경우, 모델에 커스텀 캐스트 클래스를 직접 연결하는 대신, Illuminate\Contracts\Database\Eloquent\Castable 인터페이스를 구현한 값 객체 클래스를 연결할 수 있습니다.

use App\ValueObjects\Address; protected function casts(): array { return [ 'address' => Address::class, ]; }

Castable 인터페이스를 구현하는 객체는 castUsing 메서드를 반드시 정의해야 하며, 이 메서드는 해당 Castable 클래스와의 상호 변환을 담당할 커스텀 캐스터 클래스의 이름을 반환해야 합니다.

<?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', ]; }

Castables와 익명 캐스트 클래스

"castables"와 PHP의 익명 클래스(anonymous classes)를 결합하면, 값 객체와 그 캐스팅 로직을 하나의 castable 객체로 함께 정의할 수 있습니다. 이를 위해 값 객체의 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, ]; } }; } }

이 문서는 Laravel 공식 문서(MIT)를 한국 개발자를 위해 번역·재구성한 것입니다.

번역일: 2026년 9월 23일