이전 포스트에서는 카테고리 선택 필드에 모든 카테고리의 데이터들을 불러왔었다.
그리고 카테고리의 데이터들을 불러오는 코드도 Blade 뷰에 정의되어 있었다.
이번 포스트에서는 articles.new 컴포넌트에서 $childCategories를 정의하고, 이를 Blade 뷰에 전달할 수 있도록 설정해 보겠다.
그리고 새 글을 저장하는 기능까지 구현해 보겠다.
이전 포스트
filament, Category와 Article 리소스 생성 및 글 작성 폼에 카테고리 표시하기
글 저장 기능 구현 및 카테고리 선택 시 자식 카테고리만 가져오기 · 4th-avenue/laravel13-livewire@70f9fad · GitHub
전체적인 코드 수정은 이 깃허브 링크를 통해서 확인할 수 있다.
카테고리 선택 시 자식 카테고리만 가져오기 (mount 메서드)
지금부터 보여주는 코드들은 전부 resources/views/pages/articles/⚡new.blade.php에 추가해야 할 코드들이다.
public $childCategories = [];
자식 카테고리 데이터를 저장할 배열 속성을 추가했다.
public function mount()
{
$this->childCategories = Category::whereNotNull('parent_id')
->pluck('name', 'id')
->toArray();
}
mount 메서드는 Livewire 컴포넌트가 처음으로 인스턴스화될 때 호출되는 라이프사이클 메서드다.
이 메서드의 주요 역할은 컴포넌트가 초기화될 때 필요한 데이터를 설정하는 것이다.
Category::whereNotNull('parent_id'):whereNotNull('parent_id')조건을 사용하여 parent_id 컬럼 값이 null이 아닌 레코드만 필터링한다. 즉, 자식 카테고리만 조회한다.pluck('name', 'id')메서드를 사용하여 각 레코드의 name 속성과 id 속성을 추출하여 새로운 배열을 만든다.- 마지막으로
toArray()메서드를 사용하여 Eloquent 컬렉션을 php 배열로 변환한다.
따라서 $childCategories 변수에는 자식 카테고리의 이름과 ID가 배열 형태로 저장된다.
@foreach ($childCategories as $id => $name)
<option value="{{ $id }}">
{{ $name }}
</option>
@endforeach
기존의 @foreach 디렉티브를 이렇게 수정해서 mount 메서드에서 정의된 $childCategories 데이터를 뷰로 전달받아 사용한다.
pluck() 메서드
@foreach ($childCategories as $category)
<option value="{{ $category->id }}">
{{ $category->name }}
</option>
@endforeach
이 코드는 따라 하라고 보여주는 코드가 아니다.
위에서 보여줬던 코드와 비교해 보자.
코드를 수정하기 전에는 이런 방식으로 코드를 썼었다.
Laravel에서 @foreach ($childCategories as $id => $name) 문법이 작동하는 원리는 php 배열의 키-값 쌍을 순회하는 방식과 같다.
이 방식이 동작하는 이유와, 기존 코드와의 차이를 자세히 설명하겠다.
1. pluck() 메서드의 동작
Category::whereNotNull('parent_id')->pluck('name', 'id') 메서드는 Eloquent 쿼리 결과에서 두 개의 열(name, id)을 사용하여 키-값 쌍의 배열을 생성한다.
pluck('name', 'id')의 결과, id가 배열의 키(key)로 설정되고, name이 배열의 값(value)으로 설정된다.
2. @foreach의 동작 방식
@foreach ($childCategories as $id => $name)는 배열의 키와 값을 각각 $id와 $name에 할당한다.
3. 기존 코드와의 차이
Category::whereNotNull('parent_id')->get()를 사용하면, $childCategories는 Eloquent 컬렉션 객체로 반환된다.
@foreach ($childCategories as $category)에서 $category는 Category 모델의 개별 인스턴스다.
따라서, $category->id와 $category->name에 각각 접근해야 한다.
개선된 코드에서는 pluck('name', 'id')를 사용하면 배열의 키와 값으로 반환된다.
키는 $id로, 값은 $name으로 바로 접근할 수 있어 코드가 간결해진다.
4. pluck를 사용한 코드의 장점
- 메모리 절약: pluck는 필요한 데이터만 가져오므로, 전체 모델 인스턴스를 로드하는 것보다 메모리를 적게 사용한다.
- 간결한 코드: 배열 형태로 바로 작업할 수 있으므로
->id또는->name에 접근할 필요가 없다. - 빠른 쿼리: pluck는 SQL 수준에서 필요한 열만 가져오기 때문에 데이터베이스 쿼리 성능이 향상된다.
Category 모델과 Article 모델과 User 모델의 릴레이션 설정
app/Models/User.php
public function articles(): HasMany
{
return $this->hasMany(Article::class);
}
한 명의 회원은 여러 개의 글을 남길 수 있다는 관계 설정을 해두었다.
app/Models/Category.php
public function articles(): HasMany
{
return $this->hasMany(Article::class);
}
하나의 카테고리에는 여러 개의 글이 달릴 수 있다는 설정을 해두었다.
app/Models/Article.php
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
하나의 글에는 한 명의 작성자만 존재할 수 있고, 하나의 카테고리만 선택할 수 있다는 설정을 해두었다.
Laravel 모델 간의 릴레이션(관계) 설정
모델 간의 릴레이션(관계) 설정에 대한 내용은 이 포스트에서 설명했었으니 참고하도록 하자.
새 글 저장 기능 구현
app/Models/Article.php
#[Fillable(['category_id', 'title', 'user_id', 'body'])]
먼저 글을 저장할 수 있도록 Article 모델에 Fillable을 정의한다.
Post 모델에 Fillable 정의 후 새 글 저장 기능 구현
Fillable에 대한 내용은 이 포스트를 참고하자.
resources/views/pages/articles/⚡new.blade.php
public $category_id;
public $title;
public $body;
이 속성들은 컴포넌트에서 사용될 데이터 필드다.
각 속성은 사용자가 입력한 폼 데이터를 임시로 저장하는 역할을 한다.
1. save() 메서드
public function save()
{
auth()->user()->articles()->create(
$this->only(['category_id', 'title', 'body'])
);
$this->reset();
}
save 메서드는 사용자가 입력한 데이터를 데이터베이스에 저장하는 기능을 한다.
이 메서드는 일반적으로 폼을 제출했을 때 호출된다.
auth()->user()->articles()->create( ... )는 현재 인증된 사용자가 작성한 글을 데이터베이스에 저장한다.auth()->user()는 현재 로그인한 사용자를 반환하며,articles()는 사용자와 연관된 글을 가리킨다.create()메서드는 데이터베이스에 새로운 레코드를 삽입한다.$this->only([ ... ])는 컴포넌트의 속성들 중에서 특정 속성들만 추출하여 배열로 반환한다. 이 배열은create()메서드에 전달되어 데이터베이스에 삽입된다.
save 메서드의 마지막에 있는 $this->reset();는 컴포넌트의 모든 속성을 초기화한다.
이를 통해 글이 저장된 후, 폼 필드들이 비워지게 된다.
예를 들어, 새로운 글을 등록하면 입력 필드가 자동으로 초기화 돼서 사용자에게 저장이 정상적으로 완료되었음을 직관적으로 알려준다.
2. wire:submit
<form wire:submit="save">
"register"를 "save"로 수정했다.
wire:submit="save"는 Livewire의 지시어로, 폼이 제출될 때 JavaScript 이벤트가 발생하고, 지정된 Livewire 메서드(여기서는 save 메서드)가 서버 측에서 호출된다.
동작 방식은 사용자가 폼을 제출(submit)하면, 브라우저에서 기본적으로 수행하는 페이지 전체 새로고침 없이 Livewire가 백그라운드에서 서버와 비동기적으로 통신하여 save 메서드를 호출한다.
이 메서드는 서버에서 동작하며 유효성 검사, 데이터베이스 저장 등의 비즈니스 로직을 처리한다.
처리가 끝나면 업데이트된 데이터와 컴포넌트 상태가 클라이언트 화면에 실시간으로 즉시 반영된다.
3. wire:model
wire:model="..."
<select> 태그나 <input>, <textarea> 태그들에 이 속성들을 추가했다.
이것은 양방향 데이터 바인딩(Two-Way Data Binding)을 제공하는 Livewire의 지시어다.
이것을 사용하면 Livewire 컴포넌트의 속성(Property)과 HTML 입력 필드(input, select, textarea 등)의 값을 실시간으로 동기화할 수 있다.
사용자가 입력 필드에 값을 입력하면, 해당 값이 즉시 Livewire 컴포넌트의 관련 속성으로 전송된다.
반대로 컴포넌트의 속성 값이 변경될 때도 입력 필드에 바로 반영된다.
이렇게 양방향으로 연결되어 있어 서버와 클라이언트 간의 데이터 동기화를 아주 손쉽게 처리할 수 있다.
wire:model은 Livewire 컴포넌트의 데이터 바인딩에서 필수적인 역할을 한다.
특정 입력 필드와 컴포넌트의 속성을 연결해주는 역할을 하며, 이 연결 없이는 Livewire가 데이터 변경을 추적할 수 없어서 에러가 발생한다.
4. Livewire에서는 @csrf 디렉티브가 필요 없다
참고로 일반적인 Laravel Form에서 사용하는 @csrf 디렉티브는 Livewire 컴포넌트의 Form에서는 사용하지 않아도 된다.
이유는 다음과 같다.
- Livewire의 내부적인 CSRF 보호: Livewire는 자체적으로 CSRF 공격을 방지하기 위한 강력한 메커니즘을 가지고 있다. 컴포넌트의 상태를 클라이언트와 서버 간에 동기화하는 과정에서 체크섬(checksum)을 사용하여 데이터 무결성을 검증한다. 이를 통해 CSRF 공격으로부터 안전하게 보호된다.
- @csrf 디렉티브의 역할: 일반적인 Laravel Form에서
@csrf디렉티브는 CSRF 토큰을 숨겨진 필드로 추가하여 서버에서 토큰의 유효성을 검증하는 역할을 한다. 하지만 Livewire는 이러한 방식 대신 자체적인 메커니즘을 사용하기 때문에@csrf디렉티브가 필요 없다.
여기까지 수정을 하면 이제 이렇게 글을 저장할 수 있게 된다.
너무 글이 길어지는 것 같아서 유효성 검사는 아직 정의하지 않았다.
다음 포스트에서는 유효성 검사 기능도 구현해 보겠다.
다음 포스트
0 Comments
댓글 쓰기
🔸 댓글은 블로그 운영자의 승인 후에 블로그에 표시됩니다.
🔸 비로그인 방문자 분께서는 '익명'보다 이름/URL로 댓글을 남겨주시면 감사하겠습니다. (URL은 생략 가능합니다.)
🔸 구글 로그인 방문자는 '알림 사용'에 체크를 하시면, 남겨주신 댓글에 대한 답글 알림을 메일로 받아볼 수 있습니다. 📩