04강: SEO 친화적 슬러그(Str::slug) & 소프트 삭제(SoftDeletes) 복구
실습 프로젝트: 지니샵(JinyShop) 온라인 도서 쇼핑몰
주제: 문자열 조작(Str::slug), 라우트 모델 바인딩 커스텀 키({book:slug}), 소프트 삭제의 수명주기(trashed,restore,forceDelete)
등장인물: 🐱 지니(시니어 멘토), 👧 도로시(주니어 개발자), 🐶 토토(개발 보조견)
공식 문서 연계: Eloquent 시작하기 (Eloquent), 문자열 (Strings)
💬 1. 지니와 도로시의 티키타카 회의
👧 도로시: “지니! 책 주소가 jinyshop.test/books/142 처럼 숫자 ID로 되어 있으면 구글 검색엔진이 이 책이 무슨 책인지 알기 어렵잖아요. jinyshop.test/books/laravel-13-master-class 처럼 예쁜 영문 슬러그(Slug) 주소로 바꿀 수 있나요? 그리고 만약 서점 관리자가 실수로 베스트셀러 책을 삭제 버튼 눌러버리면 어떻게 복구하나요?”
🐱 지니: “도로시, 바로 그 두 가지가 실무 서비스의 완성도를 결정짓는 디테일이란다! 라라벨의 Str::slug() 헬퍼를 쓰면 한글 책 제목도 군더더기 없는 완벽한 SEO 슬러그로 변환할 수 있고, 라우트 모델 바인딩 {book:slug}를 쓰면 컨트롤러에서 ID 대신 슬러그로 즉시 책을 찾아내지! 그리고 실수로 삭제한 책은 SoftDeletes 마법 덕분에 restore() 한 줄이면 휴지통에서 새 책처럼 완벽 복원된단다!”
🐶 토토: “멍멍! 관리자 화면에서는 Book::onlyTrashed()로 삭제된 도서 목록만 모아서 조회하고 영구 삭제(forceDelete)할 수도 있다멍!”
🎯 2. 실습 목표 및 서점 시나리오
Str::slug()및 모델 라이프사이클 이벤트(booted)를 통해 책 제목으로부터 고유 슬러그를 자동 생성합니다.- 숫자 ID 대신 슬러그로 책을 조회하는 커스텀 라우트 모델 바인딩(
books/{book:slug})을 적용합니다. - 소프트 삭제된 도서 조회, 복원(
restore()), 영구 삭제(forceDelete()) 절차를 실습합니다.
🛠️ 3. 단계별 실습 절차 (Step-by-Step)
[Step 1] Book 모델에 자동 슬러그 생성 로직 추가
app/Models/Book.php에 모델 이벤트 훅 booted()를 선언합니다:
// app/Models/Book.php
use Illuminate\Support\Str;
protected static function booted(): void
{
// 신규 도서 저장 시 slug가 비어있으면 제목 기반 자동 생성
static::creating(function (Book $book) {
if (empty($book->slug)) {
// 한글 및 특수문자 안전 변환 + 고유성 유지를 위한 랜덤 숫자 접미사
$baseSlug = Str::slug($book->title);
$book->slug = $baseSlug ?: 'book-'.fake()->numerify('####');
}
});
}
[Step 2] 슬러그 기반 라우트 모델 바인딩 설정
routes/web.php의 도서 상세 라우트를 ID 대신 slug로 조회하도록 수정합니다:
// routes/web.php
use App\Models\Book;
// {book:slug} 표기법: 라라벨이 URL의 문자열로 Book::where('slug', $slug)->firstOrFail() 자동 수행!
Route::get('/books/{book:slug}', function (Book $book) {
return view('books.show', compact('book'));
})->name('books.show');
지니의 감탄:
컨트롤러에서$book = Book::where('slug', $slug)->firstOrFail();을 직접 작성하지 않아도 라라벨이 인젝션 단계에서 자동으로 조회하고 404 예외까지 처리해 준단다!
[Step 3] 소프트 삭제(Soft Deletes) 수명주기 실습
Tinker를 열어 도서 삭제 및 복구 프로세스를 직접 실행해 봅니다:
php artisan tinker
// 1. 도서 한 권 조회
$book = App\Models\Book::first();
echo "책 제목: " . $book->title;
// 2. 도서 삭제 실행
$book->delete();
// 3. 삭제 여부 확인
echo "삭제 상태인가요? " . ($book->trashed() ? '네, 휴지통에 있습니다.' : '아니오');
// 결과: 네, 휴지통에 있습니다.
// 4. 일반 도서 조회 쿼리에서는 자동으로 제외됨!
$found = App\Models\Book::find($book->id);
echo "일반 조회 결과: " . ($found ? '조회됨' : '자동 제외됨 (null)');
// 결과: 자동 제외됨 (null)
// 5. 휴지통 포함 조회 (관리자용)
$trashedBook = App\Models\Book::withTrashed()->find($book->id);
echo "휴지통 조회 결과: " . $trashedBook->title;
// 6. 삭제된 도서 기적의 복구!
$trashedBook->restore();
echo "복구 후 상태: " . ($trashedBook->trashed() ? '삭제상태' : '정상 출판 상태로 복원 완료!');
🔍 4. 라라벨 공식 문서 원리 심층 분석 (Deep Dive)
Str::of() Fluent Stringable 인터페이스
라라벨의 Str 파사드는 문자열을 물 흐르듯 다듬는 Fluent 인터페이스를 제공합니다:
$summary = Str::of($book->description)
->trim()
->limit(100, '... (더보기)')
->title();
도서 상세 소개글에서 앞 100자만 잘라내어 목록 카드에 표시할 때 매우 유용합니다.
🐶 5. 토토의 트러블슈팅 & 주의사항
🐶 토토의 멍멍 가이드!
- 휴지통에 있는 책을 영구히 지우려면요?
$book->forceDelete()를 호출하면 데이터베이스에서 레코드가 물리적으로 완전히 지워진다멍! 되돌릴 수 없으니 관리자 비밀번호 확인이 필수다멍!
💡 6. 4강 자가진단 과제
- 브라우저에서 실제 시딩된 책 중 하나의 슬러그 주소(예:
http://127.0.0.1:8000/books/실제슬러그문자열)로 접속하여 상세 페이지가 성공적으로 열리는지 확인하세요. - 특정 도서를
$book->delete()한 뒤 브라우저에서 해당 슬러그로 접속했을 때 라라벨이 자동으로404 Not Found화면을 띄우는지 확인해 보세요.