3. Menambahkan Fitur Kategori & Tag pada Berita (Laravel)
Pada tahap ini, kita akan menambahkan kategori (one-to-many) dan tag (many-to-many) pada fitur berita (posts), sehingga konten menjadi lebih terorganisir dan profesional seperti portal berita sungguhan.
Tujuan Pembelajaran
Setelah menyelesaikan bagian ini, peserta mampu:
- Membuat tabel categories dan tags
- Menghubungkan Post ↔ Category ↔ Tag
- Menampilkan kategori & tag di halaman berita
- Mengelola kategori dan tag saat tambah & edit berita
A. Membuat Model & Migration
1. Generate Model dan Migration
php artisan make:model Category -m
php artisan make:model Tag -m
2. Migration Tabel Categories
Edit file create_categories_table.php:
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
3. Migration Tabel Tags
Edit file create_tags_table.php:
Schema::create('tags', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
B. Menambahkan Relasi ke Posts
1. Buat Migration Relasi
php artisan make:migration add_category_id_to_posts_table --table=posts
php artisan make:migration create_post_tag_table --create=post_tag
2. Tambahkan Category ke Posts
File add_category_id_to_posts_table.php:
Schema::table('posts', function (Blueprint $table) {
$table->foreignId('category_id')->nullable()->constrained('categories')->onDelete('set null');
});
3. Tabel Pivot Post–Tag
File create_post_tag_table.php:
Schema::create('post_tag', function (Blueprint $table) {
$table->id();
$table->foreignId('post_id')->constrained()->onDelete('cascade');
$table->foreignId('tag_id')->constrained()->onDelete('cascade');
});
4. Jalankan Migration
php artisan migrate
C. Menambahkan Relasi di Model
1. Model Post : Post.php
public function category()
{
return $this->belongsTo(Category::class);
}
public function tags()
{
return $this->belongsToMany(Tag::class);
}
2. Model Category : Category.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
use HasFactory;
protected $fillable = [
'name',
];
// Relasi: satu kategori punya banyak post
public function posts()
{
return $this->hasMany(Post::class);
}
}
Model Tag : Tag.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
use HasFactory;
protected $fillable = [
'name',
];
// Relasi: satu tag bisa dimiliki banyak post
public function posts()
{
return $this->belongsToMany(Post::class);
}
}
D. Ubah Form Tambah Berita
1. Dropdown Kategori
Tambahkan dropdown kategori dan checkbox tag.
<div class="mb-3">
<label>Kategori</label>
<select name="category_id" class="form-control">
<option value="">-- Pilih Kategori --</option>
@foreach($categories as $category)
<option value="{{ $category->id }}">{{ $category->name }}</option>
@endforeach
</select>
</div>
<div class="mb-3">
<label>Tag</label><br>
@foreach($tags as $tag)
<label><input type="checkbox" name="tags[]" value="{{ $tag->id }}"> {{ $tag->name }}</label><br>
@endforeach
</div>
E. Update PostController
1. Import Model
Lalu di PostController, tambahkan:
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use App\Models\Category; // Jika perlu kategori
use App\Models\Tag;
use Illuminate\Http\Request;
2. Method create()
Lalu pada create() di PostController, tambahkan:
public function create()
{
$categories = Category::orderBy('name')->get();
$tags = Tag::orderBy('name')->get();
// cara lain jika juga butuh tags:
//$tags = \App\Models\Tag::orderBy('name')->get();
return view('posts.create', compact('categories','tags'));
}
3. Method store()
Lalu pada store() di PostController, tambahkan:
public function store(Request $request)
{
// 1. Validasi input
$request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string',
'author' => 'nullable|string|max:100',
'category_id' => 'nullable|exists:categories,id',
'tags' => 'nullable|array',
'tags.*' => 'exists:tags,id',
'thumbnail' => 'nullable|image|mimes:jpeg,png,jpg|max:2048',
]);
// 2. Ambil data utama
$data = $request->only(['title', 'content', 'author', 'category_id']);
// 3. Upload thumbnail jika ada
if ($request->hasFile('thumbnail')) {
$path = $request->file('thumbnail')->store('thumbnails', 'public');
$data['thumbnail'] = $path;
}
// 4. Simpan post baru
$post = \App\Models\Post::create($data);
// 5. Simpan relasi tags ke tabel pivot
if ($request->has('tags')) {
$post->tags()->sync($request->tags);
}
// 6. Redirect dengan pesan sukses
return redirect()
->route('posts.index')
->with('success', 'Berita berhasil ditambahkan!');
}
F. Halaman Detail / Show Berita
Ubah script di edit.blade.php, menjadi :
@extends('layouts.app')
@section('content')
<div class="container">
<div class="card shadow-sm mb-4">
{{-- Thumbnail --}}
@if($post->thumbnail)
<img src="{{ asset('storage/' . $post->thumbnail) }}"
alt="{{ $post->title }}"
class="card-img-top"
style="max-height: 400px; object-fit: cover;">
@endif
<div class="card-body">
{{-- Judul --}}
<h2 class="card-title">{{ $post->title }}</h2>
{{-- Info tambahan --}}
<div class="text-muted mb-3">
<small>
Oleh <strong>{{ $post->author ?? 'Admin' }}</strong> |
{{ $post->created_at->format('d M Y') }}
</small><br>
{{-- Kategori --}}
@if($post->category)
<span class="badge bg-primary">Kategori: {{ $post->category->name }}</span>
@endif
</div>
{{-- Isi berita --}}
<div class="card-text" style="white-space: pre-line;">
{!! nl2br(e($post->content)) !!}
</div>
{{-- Tag --}}
@if($post->tags->count() > 0)
<div class="mt-4">
<strong>Tags:</strong><br>
@foreach($post->tags as $tag)
<span class="badge bg-secondary">{{ $tag->name }}</span>
@endforeach
</div>
@endif
{{-- Tombol aksi --}}
<div class="mt-4">
<a href="{{ route('posts.index') }}" class="btn btn-outline-secondary">Kembali</a>
</div>
</div>
</div>
</div>
@endsection
G. Edit & Update Berita
Ubah function edit() dan update() di PostController.php :
public function edit($id)
{
// Ambil data post dengan relasi category dan tags
$post = Post::with(['category', 'tags'])->findOrFail($id);
// Ambil semua kategori dan tags untuk form
$categories = Category::all();
$tags = Tag::all();
return view('posts.edit', compact('post', 'categories', 'tags'));
}
//—-----------------------
public function update(Request $request, $id)
{
// 1. Validasi input
$request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string',
'author' => 'nullable|string|max:100',
'category_id' => 'nullable|exists:categories,id',
'tags' => 'nullable|array',
'tags.*' => 'exists:tags,id',
'thumbnail' => 'nullable|image|mimes:jpeg,png,jpg|max:2048',
]);
// 2. Temukan post
$post = Post::findOrFail($id);
// 3. Ambil data utama
$data = $request->only(['title', 'content', 'author', 'category_id']);
// 4. Jika upload thumbnail baru
if ($request->hasFile('thumbnail')) {
// Hapus file lama (jika ada)
if ($post->thumbnail && \Storage::disk('public')->exists($post->thumbnail)) {
\Storage::disk('public')->delete($post->thumbnail);
}
// Upload file baru
$path = $request->file('thumbnail')->store('thumbnails', 'public');
$data['thumbnail'] = $path;
}
// 5. Update data post
$post->update($data);
// 6. Update relasi tags
if ($request->has('tags')) {
$post->tags()->sync($request->tags);
} else {
$post->tags()->detach(); // kalau tidak ada tag, kosongkan
}
// 7. Redirect dengan pesan sukses
return redirect()
->route('posts.index')
->with('success', 'Berita berhasil diperbarui!');
}