> ## 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 입문

> Laravel의 ORM 'Eloquent'를 사용해 데이터베이스의 데이터를 조작하는 방법을 설명합니다.

## Eloquent ORM이란

Laravel에는 Eloquent라는 객체 관계 매퍼(ORM)가 포함되어 있습니다.
Eloquent는 데이터베이스와의 대화를 쉽게 하기 위한 것으로, **ActiveRecord 패턴**을 구현하고 있습니다.

Eloquent를 사용하면 데이터베이스의 각 테이블에 대응하는 "모델" 클래스를 준비합니다.
모델을 통해 레코드의 조회·삽입·갱신·삭제를 할 수 있습니다.

<Info>
  Eloquent를 사용하기 전에 `config/database.php`에서 데이터베이스 접속을 설정해 주세요.
  기본적으로 `.env` 파일의 `DB_*` 설정이 사용됩니다.
</Info>

## 모델 생성

`make:model` Artisan 명령으로 새 모델을 생성합니다.

```shell theme={null}
php artisan make:model Post
```

마이그레이션과 동시에 생성하는 경우에는 `-m` 옵션을 사용합니다.

```shell theme={null}
php artisan make:model Post -m
```

모델은 `app/Models` 디렉터리에 생성됩니다.

```php theme={null}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    // ...
}
```

## 모델과 테이블의 대응 관계

Eloquent는 클래스명에서 테이블명을 자동으로 추측합니다.
클래스명을 스네이크케이스의 복수형으로 변환한 것이 테이블명이 됩니다.

| 모델명                    | 테이블명                      |
| ---------------------- | ------------------------- |
| `Post`                 | `posts`                   |
| `User`                 | `users`                   |
| `AirTrafficController` | `air_traffic_controllers` |

테이블명이 명명 규칙에 맞지 않는 경우, 모델에 `$table` 프로퍼티를 정의해 명시적으로 지정할 수 있습니다.

```php theme={null}
class Post extends Model
{
    protected $table = 'blog_posts';
}
```

### 타임스탬프

Eloquent는 기본적으로 `created_at`과 `updated_at` 컬럼을 자동 관리합니다.
마이그레이션에서 `$table->timestamps()`를 추가해 두면 모델의 저장·갱신 시에 자동으로 값이 설정됩니다.

타임스탬프의 자동 관리를 무효화하려면 `$timestamps`를 `false`로 설정합니다.

```php theme={null}
class Post extends Model
{
    public $timestamps = false;
}
```

## 매스 어사인먼트 보호

Eloquent로 데이터를 일괄 저장할 때는 매스 어사인먼트 보호 설정이 필요합니다.

### fillable

대입을 허용하는 컬럼을 `$fillable` 프로퍼티로 지정합니다.

```php theme={null}
class Post extends Model
{
    protected $fillable = [
        'user_id',
        'title',
        'body',
        'published',
    ];
}
```

### guarded

반대로 대입을 금지하는 컬럼을 지정하려면 `$guarded`를 사용합니다.

```php theme={null}
class Post extends Model
{
    // 기본 키만 보호하고 나머지는 모두 허용한다
    protected $guarded = ['id'];
}
```

<Warning>
  `$guarded`를 빈 배열로 하면 모든 컬럼에 대한 대입을 허용합니다.
  사용자 입력을 그대로 전달하는 경우 의도하지 않은 컬럼이 변경될 위험이 있으므로, `$fillable`로 허용할 컬럼을 명시적으로 지정할 것을 권장합니다.
</Warning>

## Eloquent 쿼리 실행 플로우

`User::where()->get()` 같은 쿼리가 어떻게 실행되는지, 내부 흐름을 보여 줍니다.

```mermaid theme={null}
flowchart LR
    A["User::where('active', true)"] --> B["쿼리 빌더 구축"]
    B --> C["->get() 호출"]
    C --> D["SQL 생성"]
    D --> E["데이터베이스 실행"]
    E --> F["결과를 Eloquent 모델로 변환"]
    F --> G["Collection으로 반환"]
```

## 기본 CRUD 조작

### 레코드 가져오기(Read)

모든 레코드를 가져옵니다.

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

$posts = Post::all();
```

조건을 지정해서 가져옵니다.

```php theme={null}
// published가 true인 게시물을 가져오기
$publishedPosts = Post::where('published', true)->get();

// 첫 번째 1건만 가져오기
$post = Post::where('published', true)->first();

// ID로 1건 가져오기
$post = Post::find(1);

// 찾지 못하면 404 응답을 반환
$post = Post::findOrFail(1);
```

### 레코드 생성(Create)

`create` 메서드로 레코드를 하나 삽입합니다(`$fillable` 설정이 필요합니다).

```php theme={null}
$post = Post::create([
    'title' => '첫 게시물',
    'body' => 'Laravel은 훌륭한 프레임워크입니다.',
    'published' => true,
]);
```

인스턴스를 생성해 개별적으로 대입할 수도 있습니다.

```php theme={null}
$post = new Post;
$post->title = '첫 게시물';
$post->body = 'Laravel은 훌륭한 프레임워크입니다.';
$post->save();
```

### 레코드 갱신(Update)

모델을 가져와 값을 변경한 후 `save`를 호출하면 갱신됩니다.

```php theme={null}
$post = Post::find(1);
$post->title = '갱신된 제목';
$post->save();
```

`update` 메서드를 사용하면 여러 컬럼을 한 번에 갱신할 수 있습니다.

```php theme={null}
Post::find(1)->update([
    'title' => '갱신된 제목',
    'published' => true,
]);
```

조건에 일치하는 여러 레코드를 일괄 갱신할 수도 있습니다.

```php theme={null}
Post::where('published', false)->update(['published' => true]);
```

### 레코드 삭제(Delete)

`delete` 메서드로 레코드를 삭제합니다.

```php theme={null}
$post = Post::find(1);
$post->delete();
```

ID를 지정해 직접 삭제할 수도 있습니다.

```php theme={null}
Post::destroy(1);

// 여러 ID를 일괄 삭제
Post::destroy([1, 2, 3]);
```

## 기본 쿼리 메서드

| 메서드                                          | 설명                         |
| -------------------------------------------- | -------------------------- |
| `Post::all()`                                | 모든 레코드를 가져오기               |
| `Post::find($id)`                            | ID로 1건 가져오기(찾지 못하면 `null`) |
| `Post::findOrFail($id)`                      | ID로 1건 가져오기(찾지 못하면 404)    |
| `Post::where('column', 'value')->get()`      | 조건에 일치하는 레코드 가져오기          |
| `Post::where('column', 'value')->first()`    | 조건에 일치하는 첫 1건 가져오기         |
| `Post::where('column', 'value')->count()`    | 조건에 일치하는 건수 가져오기           |
| `Post::orderBy('created_at', 'desc')->get()` | 정렬 순서를 지정해서 가져오기           |
| `Post::latest()->get()`                      | `created_at` 내림차순으로 가져오기   |
| `Post::limit(10)->get()`                     | 건수를 제한해서 가져오기              |

## 실전 예시: Post 모델을 사용한 데이터 조작

마이그레이션으로 생성한 `posts` 테이블을 조작하는 컨트롤러 예시입니다.

```php theme={null}
<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class PostController extends Controller
{
    // 게시물 목록 표시
    public function index(): View
    {
        $posts = Post::where('published', true)
            ->latest()
            ->get();

        return view('posts.index', ['posts' => $posts]);
    }

    // 게시물 생성
    public function store(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'title' => ['required', 'string', 'max:255'],
            'body' => ['required', 'string'],
            'published' => ['boolean'],
        ]);

        Post::create([
            ...$validated,
            'user_id' => $request->user()->id,
        ]);

        return redirect('/posts');
    }

    // 게시물 갱신
    public function update(Request $request, Post $post): RedirectResponse
    {
        $validated = $request->validate([
            'title' => ['required', 'string', 'max:255'],
            'body' => ['required', 'string'],
        ]);

        $post->update($validated);

        return redirect('/posts');
    }

    // 게시물 삭제
    public function destroy(Post $post): RedirectResponse
    {
        $post->delete();

        return redirect('/posts');
    }
}
```

<Tip>
  컨트롤러 메서드의 인수에 `Post $post`라고 쓰면 Laravel이 라우트 파라미터에서 모델을 자동으로 가져옵니다(라우트 모델 바인딩).
  `Post::findOrFail($id)`를 직접 작성할 필요가 없어집니다.
</Tip>

## 모델 이벤트의 라이프사이클

`save()`를 호출했을 때 발생하는 이벤트의 흐름을 보여줍니다. 신규 생성인지 갱신인지에 따라 다른 이벤트가 발생하지만, `saving`·`saved`는 양쪽 경우에 공통적으로 발생합니다.

```mermaid theme={null}
flowchart TD
    A["신규 인스턴스 생성"] --> B{"save()"}
    B --> I["saving 이벤트"]
    I -->|"신규"| C["creating 이벤트"]
    C --> D["INSERT 실행"]
    D --> E["created 이벤트"]
    I -->|"갱신"| F["updating 이벤트"]
    F --> G["UPDATE 실행"]
    G --> H["updated 이벤트"]
    E --> J["saved 이벤트"]
    H --> J
```

## 다음 단계

<Card title="마이그레이션" icon="table" href="/ko/migrations">
  Eloquent가 이용하는 테이블을 마이그레이션으로 생성하는 방법을 되짚어 봅니다.
</Card>


## Related topics

- [Eloquent 컬렉션](/ko/eloquent-collections.md)
- [데이터베이스 시딩](/ko/seeding.md)
- [MongoDB](/ko/mongodb.md)
- [데이터베이스 마이그레이션](/ko/migrations.md)
- [Eloquent 관계 입문](/ko/eloquent-relationships.md)
