> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Eloquent Factories

> 모델 팩토리를 사용해 테스트나 시딩용의 페이크 데이터를 효율적으로 생성하는 방법을 설명합니다.

## 팩토리란

테스트나 데이터베이스 시딩에서는 데이터베이스에 레코드를 삽입할 필요가 있습니다.
수동으로 각 컬럼의 값을 지정하는 대신, Laravel의 **모델 팩토리**를 사용하면 각 Eloquent 모델의 기본 속성 세트를 정의할 수 있습니다.

모든 새 Laravel 애플리케이션에는 `database/factories/UserFactory.php`가 포함되어 있습니다.

```php theme={null}
namespace Database\Factories;

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

class UserFactory extends Factory
{
    // 같은 비밀번호를 매번 재해시하지 않도록 캐시
    protected static ?string $password;

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

`fake()` 헬퍼를 통해 [Faker](https://github.com/FakerPHP/Faker) 라이브러리를 이용할 수 있으며, 다양한 랜덤 데이터를 생성할 수 있습니다.

```mermaid theme={null}
flowchart LR
    F["Factory<br>database/factories/UserFactory"] -->|"make() / create()"| M["Model Instance<br>App\\Models\\User"]
    M -->|"create()"| DB[("Database")]
    F -->|"Faker"| R["랜덤 데이터<br>name, email, ..."]
    R --> F
```

<Info>
  Faker의 로케일은 `config/app.php`의 `faker_locale` 옵션으로 변경할 수 있습니다.
</Info>

## 팩토리 생성

`make:factory` Artisan 명령어로 팩토리를 작성합니다.

```shell theme={null}
php artisan make:factory PostFactory
```

새로운 팩토리 클래스는 `database/factories` 디렉터리에 배치됩니다.

### 모델과 팩토리의 자동 검출

모델에 `HasFactory` 트레이트를 사용하면 Laravel이 자동으로 대응하는 팩토리를 찾습니다.
`Database\Factories` 네임스페이스에서 모델명에 `Factory`를 붙인 클래스명을 검색합니다.

명명 규칙이 맞지 않는 경우는 `UseFactory` 속성으로 명시적으로 지정할 수 있습니다.

```php theme={null}
use Illuminate\Database\Eloquent\Attributes\UseFactory;
use Database\Factories\Administration\FlightFactory;

#[UseFactory(FlightFactory::class)]
class Flight extends Model
{
    // ...
}
```

## 팩토리 정의

### `definition()` 메서드

팩토리의 중심이 되는 `definition()` 메서드에서 기본 속성을 정의합니다.

```php theme={null}
namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

class PostFactory extends Factory
{
    public function definition(): array
    {
        return [
            'user_id' => \App\Models\User::factory(),
            'title' => fake()->sentence(),
            'content' => fake()->paragraphs(3, true),
            'published_at' => fake()->optional()->dateTimeBetween('-1 year', 'now'),
        ];
    }
}
```

Faker에서 자주 사용되는 메서드 목록:

| 메서드                             | 생성되는 데이터 예시        |
| ------------------------------- | ------------------ |
| `fake()->name()`                | `John Doe`         |
| `fake()->email()`               | `john@example.com` |
| `fake()->sentence()`            | 랜덤한 문장             |
| `fake()->paragraph()`           | 랜덤한 단락             |
| `fake()->numberBetween(1, 100)` | `1`\~`100`의 정수     |
| `fake()->dateTime()`            | 랜덤한 일시             |
| `fake()->boolean()`             | `true` / `false`   |

## 팩토리 스테이트(States)

스테이트 메서드를 사용하면 팩토리에 대해 개별 변경을 정의할 수 있습니다.

```php theme={null}
use Illuminate\Database\Eloquent\Factories\Factory;

class UserFactory extends Factory
{
    public function definition(): array
    {
        return [
            'name' => fake()->name(),
            'email' => fake()->unique()->safeEmail(),
            'account_status' => 'active',
        ];
    }

    // 정지 중 사용자의 스테이트
    public function suspended(): static
    {
        return $this->state(fn (array $attributes) => [
            'account_status' => 'suspended',
        ]);
    }

    // 관리자 사용자의 스테이트
    public function admin(): static
    {
        return $this->state(fn (array $attributes) => [
            'is_admin' => true,
        ]);
    }
}
```

스테이트는 조합하여 사용할 수 있습니다.

```php theme={null}
$user = User::factory()->suspended()->create();
$admin = User::factory()->admin()->create();
```

### 소프트 삭제의 스테이트

소프트 삭제 대응 모델에는 빌트인의 `trashed()` 스테이트를 이용할 수 있습니다.

```php theme={null}
$user = User::factory()->trashed()->create();
```

## 팩토리 콜백(Callbacks)

`afterMaking`과 `afterCreating`으로 모델 생성 후의 추가 처리를 정의합니다.

```php theme={null}
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) {
            // make() 후에 실행(DB에는 저장되어 있지 않음)
        })->afterCreating(function (User $user) {
            // create() 후에 실행(DB에 저장 완료)
        });
    }

    // ...
}
```

스테이트 메서드 내에서도 콜백을 등록할 수 있습니다.

```php theme={null}
public function withProfile(): static
{
    return $this->state(fn (array $attributes) => [])
        ->afterCreating(function (User $user) {
            $user->profile()->create([
                'bio' => fake()->paragraph(),
            ]);
        });
}
```

## 모델 생성

### make() — DB 에 저장하지 않음

```php theme={null}
use App\Models\User;

// 1건 생성
$user = User::factory()->make();

// 3건 생성(컬렉션으로 반환됨)
$users = User::factory()->count(3)->make();
```

### create() — DB 에 저장

```php theme={null}
// 1건 생성해서 DB에 저장
$user = User::factory()->create();

// 3건 생성해서 DB에 저장
$users = User::factory()->count(3)->create();
```

### 속성의 덮어쓰기

`make()`나 `create()`에 배열을 전달하면 특정 속성만 덮어쓸 수 있습니다.

```php theme={null}
$user = User::factory()->make([
    'name' => '山田太郎',
]);

$user = User::factory()->create([
    'name' => '鈴木花子',
    'email' => 'hanako@example.com',
]);
```

스테이트를 사용한 인라인 덮어쓰기도 가능합니다.

```php theme={null}
$user = User::factory()->state([
    'name' => '田中一郎',
])->make();
```

<Info>
  팩토리로 모델을 생성할 때 매스 어사인먼트 보호는 자동으로 무효화됩니다.
</Info>

### Sequence(시퀀스)

여러 모델을 생성할 때 속성을 번갈아 바꾸는 경우 `Sequence`를 사용합니다.

```php theme={null}
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'으로 생성됨
```

`sequence()` 메서드를 사용하면 더 간결하게 쓸 수 있습니다.

```php theme={null}
$users = User::factory()
    ->count(2)
    ->sequence(
        ['name' => '최초의 사용자'],
        ['name' => '2번째 사용자'],
    )
    ->create();
```

클로저로 랜덤한 값을 동적으로 생성할 수도 있습니다.

```php theme={null}
use Illuminate\Database\Eloquent\Factories\Sequence;

$users = User::factory()
    ->count(10)
    ->state(new Sequence(
        fn (Sequence $sequence) => ['name' => '사용자 ' . $sequence->index],
    ))
    ->create();
```

## 릴레이션십 팩토리

```mermaid theme={null}
flowchart TD
    UF["UserFactory"] -->|"has(PostFactory)"| PF["PostFactory"]
    PF -->|"for(UserFactory)"| UF
    UF -->|"hasAttached(RoleFactory)"| RF["RoleFactory"]

    UF -->|"create()"| U["User 모델"]
    PF -->|"create()"| P["Post 모델"]
    RF -->|"create()"| R["Role 모델"]

    U -->|"hasMany"| P
    U -->|"belongsToMany"| R
```

### Has Many(1대다)

`has()` 메서드로 "1대다" 릴레이션을 가지는 모델을 생성합니다.

```php theme={null}
use App\Models\Post;
use App\Models\User;

// 3건의 Post를 가지는 User를 생성
$user = User::factory()
    ->has(Post::factory()->count(3))
    ->create();
```

매직 메서드를 사용하면 더 간결하게 쓸 수 있습니다(`has` + 릴레이션 이름의 복수형).

```php theme={null}
$user = User::factory()
    ->hasPosts(3)
    ->create();

// 속성을 덮어쓰기
$user = User::factory()
    ->hasPosts(3, ['published' => false])
    ->create();
```

### Belongs To(역 릴레이션)

`for()` 메서드로 "속하는" 부모 모델을 지정합니다.

```php theme={null}
use App\Models\Post;
use App\Models\User;

// 특정 사용자에 속하는 3건의 Post를 생성
$posts = Post::factory()
    ->count(3)
    ->for(User::factory()->state(['name' => '田中花子']))
    ->create();

// 기존 모델을 부모로 사용
$user = User::factory()->create();
$posts = Post::factory()->count(3)->for($user)->create();
```

매직 메서드 버전:

```php theme={null}
$posts = Post::factory()
    ->count(3)
    ->forUser(['name' => '田中花子'])
    ->create();
```

### Many to Many(다대다)

`hasAttached()` 메서드로 다대다 릴레이션을 다룹니다. 중간 테이블의 속성도 지정할 수 있습니다.

```php theme={null}
use App\Models\Role;
use App\Models\User;

$user = User::factory()
    ->hasAttached(
        Role::factory()->count(3),
        ['active' => true]  // 중간 테이블의 속성
    )
    ->create();
```

매직 메서드 버전:

```php theme={null}
$user = User::factory()
    ->hasRoles(1, ['name' => 'Editor'])
    ->create();
```

### 폴리모픽 릴레이션

일반 "1대다"와 동일하게 `has()`나 매직 메서드를 사용할 수 있습니다.

```php theme={null}
use App\Models\Post;

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

`morphTo` 릴레이션에는 매직 메서드를 사용할 수 없습니다. `for()`에 관계명을 명시합니다.

```php theme={null}
$comments = Comment::factory()->count(3)->for(
    Post::factory(), 'commentable'
)->create();
```

### 팩토리 내에서의 릴레이션 정의

`definition()` 내에서 외래 키에 다른 팩토리를 할당하면, 모델 생성 시에 자동으로 부모 모델도 생성됩니다.

```php theme={null}
class PostFactory extends Factory
{
    public function definition(): array
    {
        return [
            'user_id' => \App\Models\User::factory(),
            'title' => fake()->sentence(),
            'content' => fake()->paragraph(),
        ];
    }
}
```

### recycle() — 기존 모델의 재사용

여러 릴레이션에서 같은 모델 인스턴스를 재사용하고 싶은 경우 `recycle()`을 사용합니다.

```php theme={null}
// 같은 Airline을 Ticket과 Flight 양쪽에서 사용
Ticket::factory()
    ->recycle(Airline::factory()->create())
    ->create();

// 컬렉션에서 랜덤으로 선택
$airlines = Airline::factory()->count(3)->create();
Ticket::factory()->count(10)->recycle($airlines)->create();
```

## 시딩에서의 이용

`DatabaseSeeder`나 시더 클래스에서 팩토리를 호출합니다.

```php theme={null}
namespace Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        // 10건의 사용자를 생성하고, 각 사용자에게 3건의 투고를 갖게 함
        User::factory()
            ->count(10)
            ->hasPosts(3)
            ->create();
    }
}
```

시더 실행:

```shell theme={null}
php artisan db:seed
```

## 테스트에서의 이용

테스트에서는 `RefreshDatabase` 트레이트와 조합해서 팩토리를 사용합니다.

```php theme={null}
namespace Tests\Feature;

use App\Models\User;
use App\Models\Post;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class PostTest extends TestCase
{
    use RefreshDatabase;

    public function test_user_can_view_their_posts(): void
    {
        $user = User::factory()->create();
        $posts = Post::factory()->count(3)->for($user)->create();

        $response = $this->actingAs($user)
            ->get('/dashboard');

        $response->assertOk();
        $response->assertSee($posts->first()->title);
    }

    public function test_suspended_user_cannot_post(): void
    {
        $user = User::factory()->suspended()->create();

        $response = $this->actingAs($user)
            ->post('/posts', ['title' => '테스트']);

        $response->assertForbidden();
    }
}
```

`RefreshDatabase`는 테스트마다 데이터베이스를 리셋하기 때문에, 테스트 간에 데이터가 간섭하지 않습니다.

<Tip>
  Pest로 테스트할 때도 팩토리의 API는 동일합니다.
  `uses(RefreshDatabase::class)`를 사용해 데이터베이스를 리셋해 주십시오.
</Tip>

## 다음 단계

<Card title="데이터베이스 시딩" icon="seedling" href="/ko/seeding">
  시더와 팩토리를 조합해 초기 데이터를 투입하는 방법을 확인합니다.
</Card>

<Card title="테스트 입문" icon="flask" href="/ko/testing">
  Laravel의 테스트 기능과 RefreshDatabase 트레이트의 사용법을 확인합니다.
</Card>


## Related topics

- [Eloquent Bootable Traits](/ko/advanced/eloquent-bootable-traits.md)
- [Contracts(계약)](/ko/contracts.md)
- [Eloquent 입문](/ko/eloquent.md)
- [Laravel 12에서 13으로의 업그레이드 가이드](/ko/blog/upgrade-12-to-13.md)
- [Laravel Sanctum (API 토큰 인증)](/ko/sanctum.md)
