Skip to content

Eloquent: 팩토리

소개

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

팩토리를 작성하는 방법의 예시를 보려면 애플리케이션의 database/factories/UserFactory.php 파일을 살펴보십시오. 이 팩토리는 모든 새로운 Laravel 애플리케이션에 포함되어 있으며 다음 팩토리 정의를 포함합니다.

namespace Database\Factories;
 
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
 
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
/**
* 팩토리에서 사용 중인 현재 비밀번호입니다.
*/
protected static ?string $password;
 
/**
* 모델의 기본 상태를 정의합니다.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
];
}
 
/**
* 모델의 이메일 주소가 확인되지 않은 상태여야 함을 나타냅니다.
*/
public function unverified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
}

보시다시피, 가장 기본적인 형태에서 팩토리는 Laravel의 기본 팩토리 클래스를 확장하고 definition 메서드를 정의하는 클래스입니다. definition 메서드는 팩토리를 사용하여 모델을 생성할 때 적용해야 하는 기본 속성 값 세트를 반환합니다.

fake 헬퍼를 통해 팩토리는 Faker PHP 라이브러리에 액세스할 수 있으며, 이를 통해 테스트 및 시딩을 위해 다양한 종류의 임의 데이터를 편리하게 생성할 수 있습니다.

lightbulb

config/app.php 구성 파일에서 faker_locale 옵션을 업데이트하여 애플리케이션의 Faker 로케일을 변경할 수 있습니다.

모델 팩토리 정의

팩토리 생성

팩토리를 생성하려면 make:factory Artisan 명령어를 실행하십시오.

php artisan make:factory PostFactory

새로운 팩토리 클래스는 database/factories 디렉토리에 위치하게 됩니다.

모델 및 팩토리 검색 규칙

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

HasFactory 트레이트의 factory 메서드는 트레이트가 할당된 모델에 적합한 팩토리를 결정하기 위해 규칙을 사용합니다. 특히, 이 메서드는 Database\Factories 네임스페이스에서 모델 이름과 일치하고 Factory 접미사가 붙은 클래스 이름을 가진 팩토리를 찾습니다. 이러한 규칙이 특정 애플리케이션 또는 팩토리에 적용되지 않는 경우, 모델의 newFactory 메서드를 재정의하여 모델에 해당하는 팩토리의 인스턴스를 직접 반환할 수 있습니다:

use Database\Factories\Administration\FlightFactory;
 
/**
* 모델에 대한 새 팩토리 인스턴스를 만듭니다.
*/
protected static function newFactory()
{
return FlightFactory::new();
}

그런 다음, 해당 팩토리에 model 속성을 정의합니다:

use App\Administration\Flight;
use Illuminate\Database\Eloquent\Factories\Factory;
 
class FlightFactory extends Factory
{
/**
* 팩토리의 해당 모델 이름.
*
* @var class-string<\Illuminate\Database\Eloquent\Model>
*/
protected $model = Flight::class;
}

팩토리 상태

상태 조작 메서드를 사용하면 모델 팩토리에 임의의 조합으로 적용할 수 있는 개별적인 수정 사항을 정의할 수 있습니다. 예를 들어 Database\Factories\UserFactory 팩토리에는 기본 속성 값 중 하나를 수정하는 suspended 상태 메서드가 포함될 수 있습니다.

상태 변환 메서드는 일반적으로 Laravel의 기본 팩토리 클래스에서 제공하는 state 메서드를 호출합니다. state 메서드는 팩토리에 정의된 원시 속성 배열을 받는 클로저를 허용하며 수정할 속성 배열을 반환해야 합니다:

use Illuminate\Database\Eloquent\Factories\Factory;
 
/**
* 사용자가 일시 중단되었음을 나타냅니다.
*/
public function suspended(): Factory
{
return $this->state(function (array $attributes) {
return [
'account_status' => 'suspended',
];
});
}

"삭제됨" 상태

Eloquent 모델을 소프트 삭제할 수 있는 경우, 생성된 모델이 이미 "소프트 삭제"되어야 함을 나타내기 위해 내장된 trashed 상태 메서드를 호출할 수 있습니다. 모든 팩토리에서 자동으로 사용할 수 있으므로 trashed 상태를 수동으로 정의할 필요가 없습니다:

use App\Models\User;
 
$user = User::factory()->trashed()->create();

팩토리 콜백

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

namespace Database\Factories;
 
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
 
class UserFactory extends Factory
{
/**
* 모델 팩토리를 구성합니다.
*/
public function configure(): static
{
return $this->afterMaking(function (User $user) {
// ...
})->afterCreating(function (User $user) {
// ...
});
}
 
// ...
}

상태 메서드 내에서 팩토리 콜백을 등록하여 특정 상태에 특정한 추가 작업을 수행할 수도 있습니다:

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
 
/**
* 사용자가 일시 중단되었음을 나타냅니다.
*/
public function suspended(): Factory
{
return $this->state(function (array $attributes) {
return [
'account_status' => 'suspended',
];
})->afterMaking(function (User $user) {
// ...
})->afterCreating(function (User $user) {
// ...
});
}

팩토리를 사용하여 모델 만들기

모델 인스턴스화

팩토리를 정의한 후에는 해당 모델의 팩토리 인스턴스를 인스턴스화하기 위해 Illuminate\Database\Eloquent\Factories\HasFactory 트레이트에 의해 모델에 제공된 정적 factory 메서드를 사용할 수 있습니다. 모델 생성의 몇 가지 예시를 살펴보겠습니다. 먼저, make 메서드를 사용하여 데이터베이스에 저장하지 않고 모델을 생성합니다:

use App\Models\User;
 
$user = User::factory()->make();

count 메서드를 사용하여 여러 모델의 컬렉션을 만들 수 있습니다:

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

상태 적용

상태를 모델에 적용할 수도 있습니다. 모델에 여러 상태 변환을 적용하려면 상태 변환 메서드를 직접 호출하면 됩니다:

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

속성 재정의

모델의 기본값 일부를 재정의하려면 make 메서드에 값 배열을 전달할 수 있습니다. 지정된 속성만 대체되고 나머지 속성은 팩토리에 지정된 기본값으로 설정된 상태로 유지됩니다:

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

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

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

[!참고]
팩토리를 사용하여 모델을 생성할 때 대량 할당 보호가 자동으로 비활성화됩니다.

모델 지속

create 메서드는 모델 인스턴스를 인스턴스화하고 Eloquent의 save 메서드를 사용하여 데이터베이스에 저장합니다:

use App\Models\User;
 
// 단일 App\Models\User 인스턴스를 만듭니다...
$user = User::factory()->create();
 
// 세 개의 App\Models\User 인스턴스를 만듭니다...
$users = User::factory()->count(3)->create();

create 메서드에 속성 배열을 전달하여 팩토리의 기본 모델 속성을 재정의할 수 있습니다:

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

시퀀스

경우에 따라 생성된 각 모델에 대해 특정 모델 속성의 값을 번갈아 변경할 수 있습니다. 시퀀스로 상태 변환을 정의하여 이를 수행할 수 있습니다. 예를 들어 생성된 각 사용자에 대해 admin 열의 값을 YN 사이에서 번갈아 변경할 수 있습니다:

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Sequence;
 
$users = User::factory()
->count(10)
->state(new Sequence(
['admin' => 'Y'],
['admin' => 'N'],
))
->create();

이 예에서는 admin 값이 Y인 5명의 사용자와 admin 값이 N인 5명의 사용자가 생성됩니다.

필요한 경우 시퀀스 값으로 클로저를 포함할 수 있습니다. 클로저는 시퀀스에 새 값이 필요할 때마다 호출됩니다:

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 메서드를 사용하여 적용할 수도 있으며, 이 메서드는 내부적으로 state 메서드를 호출합니다. sequence 메서드는 클로저 또는 시퀀스된 속성 배열을 허용합니다:

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

팩토리 관계

Has Many 관계

다음으로 Laravel의 유창한 팩토리 메서드를 사용하여 Eloquent 모델 관계를 구축하는 방법을 살펴보겠습니다. 먼저 애플리케이션에 App\Models\User 모델과 App\Models\Post 모델이 있다고 가정해 보겠습니다. 또한 User 모델이 PosthasMany 관계를 정의한다고 가정합니다. Laravel 팩토리에서 제공하는 has 메서드를 사용하여 3개의 게시물이 있는 사용자를 만들 수 있습니다. has 메서드는 팩토리 인스턴스를 허용합니다:

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

규칙에 따라 Post 모델을 has 메서드에 전달하면 Laravel은 User 모델에 관계를 정의하는 posts 메서드가 있어야 한다고 가정합니다. 필요한 경우 조작하려는 관계의 이름을 명시적으로 지정할 수 있습니다:

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

물론 관련 모델에서 상태 조작을 수행할 수 있습니다. 또한 상태 변경에 상위 모델에 대한 접근이 필요한 경우 클로저 기반 상태 변환을 전달할 수 있습니다:

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

매직 메서드 사용

편의를 위해 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 관계

팩토리를 사용하여 "has many" 관계를 구축하는 방법을 살펴보았으므로, 이제 관계의 반대쪽을 살펴보겠습니다. for 메서드는 팩토리에서 생성된 모델이 속하는 상위 모델을 정의하는 데 사용할 수 있습니다. 예를 들어 단일 사용자에 속하는 세 개의 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();

매직 메서드 사용

편의를 위해 Laravel의 매직 팩토리 관계 메서드를 사용하여 "belongs to" 관계를 정의할 수 있습니다. 예를 들어 다음 예에서는 규칙을 사용하여 세 개의 게시물이 Post 모델의 user 관계에 속해야 한다고 결정합니다:

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

Many to Many 관계

has many 관계와 마찬가지로 has 메서드를 사용하여 "many to many" 관계를 만들 수 있습니다:

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

피벗 테이블 속성

모델을 연결하는 피벗/중간 테이블에 설정해야 하는 속성을 정의해야 하는 경우 hasAttached 메서드를 사용할 수 있습니다. 이 메서드는 피벗 테이블 속성 이름과 값의 배열을 두 번째 인수로 허용합니다:

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 메서드에 전달할 수 있습니다. 이 예에서는 동일한 세 개의 역할이 세 명의 모든 사용자에게 연결됩니다:

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

매직 메서드 사용

편의를 위해 Laravel의 매직 팩토리 관계 메서드를 사용하여 many to many 관계를 정의할 수 있습니다. 예를 들어 다음 예에서는 규칙을 사용하여 관련 모델이 User 모델의 roles 관계 메서드를 통해 생성되어야 한다고 결정합니다:

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

다형 관계

다형 관계도 팩토리를 사용하여 만들 수 있습니다. 다형 "morph many" 관계는 일반적인 "has many" 관계와 같은 방식으로 만들어집니다. 예를 들어 App\Models\Post 모델에 App\Models\Comment 모델과 morphMany 관계가 있는 경우:

use App\Models\Post;
 
$post = Post::factory()->hasComments(3)->create();

Morph To 관계

매직 메서드는 morphTo 관계를 만드는 데 사용할 수 없습니다. 대신 for 메서드를 직접 사용해야 하며 관계 이름을 명시적으로 제공해야 합니다. 예를 들어 Comment 모델에 morphTo 관계를 정의하는 commentable 메서드가 있다고 가정해 보겠습니다. 이 경우 for 메서드를 직접 사용하여 단일 게시물에 속하는 세 개의 댓글을 만들 수 있습니다:

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

다형 Many to Many 관계

다형 "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();

팩토리 내에서 관계 정의

모델 팩토리 내에서 관계를 정의하려면 일반적으로 관계의 외래 키에 새 팩토리 인스턴스를 할당합니다. 이는 일반적으로 belongsTomorphTo 관계와 같은 "역" 관계에 대해 수행됩니다. 예를 들어 게시물을 만들 때 새 사용자를 만들려면 다음과 같이 할 수 있습니다:

use App\Models\User;
 
/**
* 모델의 기본 상태를 정의합니다.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'user_id' => User::factory(),
'title' => fake()->title(),
'content' => fake()->paragraph(),
];
}

관계의 열이 해당 열을 정의하는 팩토리에 따라 달라지면 속성에 클로저를 할당할 수 있습니다. 클로저는 팩토리의 평가된 속성 배열을 받습니다:

/**
* 모델의 기본 상태를 정의합니다.
*
* @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(),
];
}

관계에 기존 모델 재활용

다른 모델과 공통 관계를 공유하는 모델이 있는 경우 recycle 메서드를 사용하여 팩토리가 만든 모든 관계에 대해 관련 모델의 단일 인스턴스가 재활용되도록 할 수 있습니다.

예를 들어 항공사, 비행 및 티켓 모델이 있고 티켓이 항공사와 비행에 속하고 비행도 항공사에 속한다고 가정해 보겠습니다. 티켓을 만들 때 티켓과 비행 모두에 대해 동일한 항공사를 원할 것이므로 항공사 인스턴스를 recycle 메서드에 전달할 수 있습니다:

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

일반적인 사용자 또는 팀에 속한 모델이 있는 경우 recycle 메서드가 특히 유용하다는 것을 알 수 있습니다.

recycle 메서드는 기존 모델의 컬렉션도 허용합니다. 컬렉션이 recycle 메서드에 제공되면 팩토리에 해당 유형의 모델이 필요할 때 컬렉션에서 임의의 모델이 선택됩니다:

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