본문으로 건너뛰기
버전: 10.x
Laravel 10.x · 한국어 문서https://laravel.com/docs/10.x/eloquent-factories

Eloquent: Factories

Introduction​

애플리케이션을 테스트하거나 데이터베이스를 시딩할 때, 데이터베이스에 몇 개의 레코드를 삽입해야 할 수 있습니다. 각 컬럼의 값을 일일이 지정하는 대신, Laravel에서는 각 Eloquent models에 대해 모델 팩토리를 정의하여 기본 속성 값을 지정할 수 있습니다.

팩토리 작성 예를 보고 싶다면, 애플리케이션의 database/factories/UserFactory.php 파일을 확인해 보시기 바랍니다. 이 팩토리는 모든 새로운 Laravel 프로젝트에 포함되어 있으며 아래와 같은 정의를 가지고 있습니다:

namespace Database\Factories;

use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Factories\Factory;

class UserFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
];
}
}

보시다시피, 팩토리는 기본적으로 Laravel의 팩토리 기본 클래스를 상속받아 definition 메서드를 정의하는 클래스입니다. definition 메서드는 팩토리로 모델을 생성할 때 적용할 기본 속성 값을 반환합니다.

팩토리에서는 fake 헬퍼를 통해 Faker PHP 라이브러리를 사용할 수 있습니다. 이를 통해 테스트 및 데이터 시딩을 위해 여러 종류의 임의 데이터를 생성할 수 있습니다.

애플리케이션의 Faker 로케일(locale)은 config/app.php 설정 파일에 faker_locale 옵션을 추가하여 설정할 수 있습니다.

Defining Model Factories​

Generating Factories​

팩토리를 생성하려면, make:factory Artisan command를 실행합니다:

php artisan make:factory PostFactory

새로 생성된 팩토리 클래스는 database/factories 디렉터리에 생성됩니다.

Model and Factory Discovery Conventions​

팩토리를 정의한 후에는, 모델에 Illuminate\Database\Eloquent\Factories\HasFactory 트레이트를 적용하면 제공되는 정적 factory 메서드를 사용하여 해당 모델의 팩토리 인스턴스를 생성할 수 있습니다.

HasFactory 트레이트의 factory 메서드는 관례를 기반으로, 할당된 모델에 대한 적절한 팩토리를 찾습니다. 구체적으로 이 메서드는 Database\Factories 네임스페이스 아래에서 모델명과 동일하면서 Factory로 끝나는 클래스를 찾습니다. 만약 이러한 관례가 애플리케이션에 맞지 않으면, 모델에서 newFactory 메서드를 직접 오버라이드(재정의)하여 해당 모델에 대한 팩토리 인스턴스를 직접 반환할 수 있습니다:

use Illuminate\Database\Eloquent\Factories\Factory;
use Database\Factories\Administration\FlightFactory;

/**
* Create a new factory instance for the model.
*/
protected static function newFactory(): Factory
{
return FlightFactory::new();
}

그리고, 해당 팩토리에서 model 속성을 명시적으로 지정해줍니다:

use App\Administration\Flight;
use Illuminate\Database\Eloquent\Factories\Factory;

class FlightFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var class-string<\Illuminate\Database\Eloquent\Model>
*/
protected $model = Flight::class;
}

Factory States​

상태(state) 조작 메서드를 사용하면 하나의 모델 팩토리에 대해 다양한 변경점을 조합해서 적용할 수 있습니다. 예를 들어, Database\Factories\UserFactory 팩토리에 기본 속성 중 하나를 변경하는 suspended 상태 메서드를 정의할 수 있습니다.

상태 변환 메서드는 일반적으로 Laravel의 팩토리 기본 클래스에서 제공하는 state 메서드를 호출합니다. state 메서드는 팩토리에 정의된 속성 배열을 입력으로 받는 클로저(익명 함수)를 인자로 받고, 이 클로저는 수정할 속성을 포함한 배열을 반환해야 합니다:

use Illuminate\Database\Eloquent\Factories\Factory;

/**
* Indicate that the user is suspended.
*/
public function suspended(): Factory
{
return $this->state(function (array $attributes) {
return [
'account_status' => 'suspended',
];
});
}

"Trashed" State​

만약 Eloquent 모델이 soft deleted를 지원한다면, 내장된 trashed 상태 메서드를 호출하여 생성된 모델이 소프트 삭제된 상태가 되도록 할 수 있습니다. 별도로 trashed 상태를 정의하지 않아도 모든 팩토리에서 자동으로 사용할 수 있습니다:

use App\Models\User;

$user = User::factory()->trashed()->create();

Factory Callbacks​

팩토리 콜백은 afterMaking과 afterCreating 메서드를 사용해 등록하며, 모델을 만들거나 생성한 후에 추가적인 작업을 수행할 수 있도록 도와줍니다. 이러한 콜백은 팩토리 클래스에서 configure 메서드를 정의하여 등록하며, 팩토리가 인스턴스화될 때 Laravel이 자동으로 호출합니다:

namespace Database\Factories;

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

class UserFactory extends Factory
{
/**
* Configure the model factory.
*/
public function configure(): static
{
return $this->afterMaking(function (User $user) {
// ...
})->afterCreating(function (User $user) {
// ...
});
}

// ...
}

또한, 개별 상태(state) 메서드에서도 팩토리 콜백을 등록할 수 있습니다. 이를 사용하면 특정 상태에서만 필요한 추가 작업을 수행할 수 있습니다:

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
* Indicate that the user is suspended.
*/
public function suspended(): Factory
{
return $this->state(function (array $attributes) {
return [
'account_status' => 'suspended',
];
})->afterMaking(function (User $user) {
// ...
})->afterCreating(function (User $user) {
// ...
});
}

Creating Models Using Factories​

Instantiating Models​

팩토리를 정의했다면, Illuminate\Database\Eloquent\Factories\HasFactory 트레이트를 추가한 모델의 정적 factory 메서드를 사용하여 팩토리 인스턴스를 생성할 수 있습니다. 몇 가지 예시를 살펴보겠습니다. 먼저, make 메서드로 데이터베이스에 저장하지 않고 모델 인스턴스만 생성해보겠습니다:

use App\Models\User;

$user = User::factory()->make();

count 메서드를 이용하면 여러 모델을 담은 컬렉션을 생성할 수 있습니다:

$users = User::factory()->count(3)->make();

Applying States​

정의한 states를 모델에 적용할 수도 있습니다. 여러 상태 변환을 동시에 적용하고 싶다면, 원하는 상태 변환 메서드를 연이어 호출하면 됩니다:

$users = User::factory()->count(5)->suspended()->make();

Overriding Attributes​

모델의 기본 값을 일부만 직접 지정하고 싶다면, make 메서드에 배열로 값을 지정해주면 됩니다. 지정한 속성만 새 값으로 덮어쓰고, 나머지는 팩토리에서 지정한 기본 값이 유지됩니다:

$user = User::factory()->make([
'name' => 'Abigail Otwell',
]);

또는, 팩토리 인스턴스에서 직접 state 메서드를 호출하여 인라인 상태 변환을 수행할 수도 있습니다:

$user = User::factory()->state([
'name' => 'Abigail Otwell',
])->make();

팩토리를 통해 모델을 생성할 때는 Mass assignment protection가 자동으로 비활성화됩니다.

Persisting Models​

create 메서드를 사용하면, 모델 인스턴스를 생성하고 Eloquent의 save 메서드로 데이터베이스에 저장합니다:

use App\Models\User;

// Create a single App\Models\User instance...
$user = User::factory()->create();

// Create three App\Models\User instances...
$users = User::factory()->count(3)->create();

create 메서드에도 속성 배열을 넘겨주어 팩토리의 기본 속성 값을 덮어쓸 수 있습니다:

$user = User::factory()->create([
'name' => 'Abigail',
]);

Sequences​

여러 모델을 생성할 때 특정 속성 값을 반복적으로 교차 변경하고 싶을 때는, 상태 변환을 시퀀스(Sequence)로 정의할 수 있습니다. 예를 들어, 사용자 각각의 admin 컬럼 값을 Y와 N으로 번갈아 설정하고 싶다면 다음과 같이 작성할 수 있습니다:

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Sequence;

$users = User::factory()
->count(10)
->state(new Sequence(
['admin' => 'Y'],
['admin' => 'N'],
))
->create();

위 예제에서는 5명의 사용자는 admin 값이 Y로 생성되고, 나머지 5명은 admin 값이 N으로 생성됩니다.

필요하다면 시퀀스 값으로 클로저(익명 함수)를 사용할 수도 있습니다. 시퀀스를 사용할 때마다 클로저가 실행되어 새로운 값을 반환합니다:

use Illuminate\Database\Eloquent\Factories\Sequence;

$users = User::factory()
->count(10)
->state(new Sequence(
fn (Sequence $sequence) => ['role' => UserRoles::all()->random()],
))
->create();

시퀀스 클로저 내부에서는 클로저에 주입되는 시퀀스 인스턴스의 $index 또는 $count 속성에 접근할 수 있습니다. $index 속성은 지금까지 시퀀스가 몇 번 순환했는지를 담고 있으며, $count 속성은 시퀀스가 전체 몇 번 실행될 것인지를 담고 있습니다:

$users = User::factory()
->count(10)
->sequence(fn (Sequence $sequence) => ['name' => 'Name '.$sequence->index])
->create();

편의상 sequence 메서드를 사용해도 동일한 효과를 낼 수 있습니다. sequence 메서드는 내부적으로 state를 호출하며, 클로저 또는 속성 배열들을 인자로 받습니다:

$users = User::factory()
->count(2)
->sequence(
['name' => 'First User'],
['name' => 'Second User'],
)
->create();

Factory Relationships​

Has Many Relationships​

이제 Laravel의 fluent한 팩토리 메서드를 사용해서 Eloquent 모델 간의 연관관계를 어떻게 구성할 수 있는지 알아보겠습니다. 먼저, App\Models\User 모델과 App\Models\Post 모델을 간단히 가정하겠습니다. 그리고 User 모델이 Post와 hasMany 연관관계를 가진다고 하면, 아래와 같이 has 메서드로 게시글 3개를 가진 사용자를 생성할 수 있습니다. has 메서드에는 팩토리 인스턴스를 전달합니다:

use App\Models\Post;
use App\Models\User;

$user = User::factory()
->has(Post::factory()->count(3))
->create();

관례상, has 메서드에 Post 모델을 전달하면 Laravel은 User 모델이 posts 메서드를 통해 해당 연관관계를 정의한 것으로 간주합니다. 필요하다면 조작할 연관관계 메서드명을 명시적으로 지정할 수도 있습니다:

$user = User::factory()
->has(Post::factory()->count(3), 'posts')
->create();

물론, 연관된 모델에도 상태(state) 변환을 적용할 수 있습니다. 또한, 만약 연관된 모델의 상태 변경 시 상위(부모) 모델에 접근해야 한다면, 클로저 기반 상태 변환도 사용할 수 있습니다:

$user = User::factory()
->has(
Post::factory()
->count(3)
->state(function (array $attributes, User $user) {
return ['user_type' => $user->type];
})
)
->create();

Using Magic Methods​

편의상, Laravel이 제공하는 연관관계용 매직 팩토리 메서드를 이용해 관계를 생성할 수도 있습니다. 아래 예시에서는 관례에 따라 관련 모델이 User 모델의 posts 연관관계 메서드를 통해 생성되어야 한다고 판단합니다:

$user = User::factory()
->hasPosts(3)
->create();

매직 메서드를 사용할 때, 관련 모델의 속성을 배열로 덮어쓸 수도 있습니다:

$user = User::factory()
->hasPosts(3, [
'published' => false,
])
->create();

상태 변환에서 부모 모델에 접근이 필요한 경우, 두 번째 인자로 클로저를 전달할 수도 있습니다:

$user = User::factory()
->hasPosts(3, function (array $attributes, User $user) {
return ['user_type' => $user->type];
})
->create();

Belongs To Relationships​

"has many" 연관관계를 다루는 방법을 살펴봤다면, 이제 그 반대인 "belongs to" 관계도 정의해보겠습니다. for 메서드를 사용하면 팩토리로 생성된 모델이 소속될 부모 모델을 지정할 수 있습니다. 예를 들어, 하나의 사용자에게 속한 3개의 App\Models\Post 모델 인스턴스를 만들 수 있습니다:

use App\Models\Post;
use App\Models\User;

$posts = Post::factory()
->count(3)
->for(User::factory()->state([
'name' => 'Jessica Archer',
]))
->create();

이미 부모 모델 인스턴스를 가지고 있다면, 해당 인스턴스를 직접 for 메서드에 전달해도 됩니다:

$user = User::factory()->create();

$posts = Post::factory()
->count(3)
->for($user)
->create();

Using Magic Methods​

편의상, Laravel이 제공하는 연관관계용 매직 팩토리 메서드를 이용해 "belongs to" 관계도 정의할 수 있습니다. 아래 예시에서는 관례에 따라 3개의 게시글이 Post 모델의 user 연관관계에 속해야 한다고 판단합니다:

$posts = Post::factory()
->count(3)
->forUser([
'name' => 'Jessica Archer',
])
->create();

Many to Many Relationships​

has many relationships와 마찬가지로, "many to many" 관계도 has 메서드를 사용해서 생성할 수 있습니다:

use App\Models\Role;
use App\Models\User;

$user = User::factory()
->has(Role::factory()->count(3))
->create();

Pivot Table Attributes​

연결(pivot) 테이블에 값이 기록되어야 한다면, hasAttached 메서드를 사용할 수 있습니다. 이 메서드는 두 번째 인자로 피벗(pivot) 테이블에 저장할 속성 배열을 받습니다:

use App\Models\Role;
use App\Models\User;

$user = User::factory()
->hasAttached(
Role::factory()->count(3),
['active' => true]
)
->create();

관련 모델에 접근해서 클로저 기반 상태 변환을 적용하고 싶을 때도 사용할 수 있습니다:

$user = User::factory()
->hasAttached(
Role::factory()
->count(3)
->state(function (array $attributes, User $user) {
return ['name' => $user->name.' Role'];
}),
['active' => true]
)
->create();

이미 생성된 모델 인스턴스를 연결하고 싶다면, hasAttached에 모델 인스턴스를 그대로 넘기면 됩니다. 아래 예시에서는 동일한 3개의 역할(role)이 3명의 사용자 모두와 연결됩니다:

$roles = Role::factory()->count(3)->create();

$user = User::factory()
->count(3)
->hasAttached($roles, ['active' => true])
->create();

Using Magic Methods​

편의상, Laravel이 제공하는 연관관계용 매직 팩토리 메서드를 이용해 many to many 관계도 정의할 수 있습니다. 아래 예시에서는 관례에 따라 관련 모델이 User 모델의 roles 연관관계 메서드를 통해 생성되어야 한다고 판단합니다:

$user = User::factory()
->hasRoles(1, [
'name' => 'Editor'
])
->create();

Polymorphic Relationships​

Polymorphic relationships 역시 팩토리로 손쉽게 생성할 수 있습니다. 폴리모픽 "morph many" 관계는 일반적인 "has many"와 같은 방식으로 구현하면 됩니다. 예를 들어, App\Models\Post 모델이 App\Models\Comment 모델과 morphMany 관계를 가지면:

use App\Models\Post;

$post = Post::factory()->hasComments(3)->create();

Morph To Relationships​

매직 메서드는 morphTo 연관관계 생성에는 사용할 수 없습니다. 이 경우에는 for 메서드를 직접 사용하고, 관계명을 명시적으로 지정해야 합니다. 예를 들어, Comment 모델에 morphTo 관계를 정의하는 commentable 메서드가 있다고 가정하면, for 메서드를 직접 사용하여 하나의 게시글에 속하는 댓글 3개를 생성할 수 있습니다:

$comments = Comment::factory()->count(3)->for(
Post::factory(), 'commentable'
)->create();

Polymorphic Many to Many Relationships​

폴리모픽 "many to many"(morphToMany / morphedByMany) 관계도 일반 many to many와 동일하게 생성할 수 있습니다:

use App\Models\Tag;
use App\Models\Video;

$videos = Video::factory()
->hasAttached(
Tag::factory()->count(3),
['public' => true]
)
->create();

물론, 매직 has 메서드로도 폴리모픽 "many to many" 관계를 생성할 수 있습니다:

$videos = Video::factory()
->hasTags(3, ['public' => true])
->create();

Defining Relationships Within Factories​

모델 팩토리 내에서 연관관계를 정의할 때는 보통 해당 관계의 외래키(foreign key) 필드에 새로운 팩토리 인스턴스를 할당합니다. 이는 belongsTo나 morphTo와 같은 "역관계(inverse relationship)"에 주로 사용됩니다. 예를 들어, 게시글을 생성할 때 새로운 사용자를 함께 생성하려면 다음과 같이 작성할 수 있습니다:

use App\Models\User;

/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'user_id' => User::factory(),
'title' => fake()->title(),
'content' => fake()->paragraph(),
];
}

관계 컬럼 값이 팩토리에서 평가된 속성 배열에 따라 달라져야 한다면, 해당 속성에 클로저를 할당하면 됩니다. 이 때 클로저는 팩토리에서 평가된 속성 배열을 인자로 받습니다:

/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'user_id' => User::factory(),
'user_type' => function (array $attributes) {
return User::find($attributes['user_id'])->type;
},
'title' => fake()->title(),
'content' => fake()->paragraph(),
];
}

Recycling an Existing Model for Relationships​

두 개 이상의 모델이 동일한 연관 모델을 공유해야 할 때는 recycle 메서드를 사용해, 팩토리가 관계를 생성할 때 항상 동일한 인스턴스를 재사용하도록 할 수 있습니다.

예를 들어, Airline, Flight, Ticket 모델이 있고, 각 티켓과 플라이트가 같은 항공사를 참조해야 하는 경우, 항공사 인스턴스를 recycle 메서드에 전달하면 됩니다:

Ticket::factory()
->recycle(Airline::factory()->create())
->create();

공통의 사용자나 팀에 속한 모델이 있는 경우 recycle 메서드가 특히 유용할 수 있습니다.

recycle 메서드는 기존 모델의 컬렉션도 받을 수 있습니다. 컬렉션이 recycle 메서드에 제공되면, 팩토리에서 해당 타입의 모델이 필요할 때 컬렉션에서 무작위로 하나의 모델이 선택됩니다:

Ticket::factory()
->recycle($airlines)
->create();