Setelah sebelumnya membuat Modul Kategori dan Tag, kali ini kita akan membuat langkah-langkah detail untuk membuat Modul Manajemen Artikel / Post di CMS sederhana kita. Modul ini akan meliputi:
- Struktur Database - Membuat tabel
postsdan relasi dengancategoriesdantags - Model untuk Post dan relasi ke model Category dan Tag
- Controller untuk CRUD Post
- Views untuk menampilkan, menambahkan, mengedit, dan menghapus post
- Routes untuk menghubungkan semua fungsi CRUD
Langkah 1: Struktur Database
a) Membuat Migration untuk Tabel posts
Pertama, buat migrasi untuk tabel posts:
php artisan make:migration create_posts_table
Kemudian, buka file migrasi di database/migrations/ dan sesuaikan dengan struktur berikut:
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('slug')->unique();
$table->text('content');
$table->string('image')->nullable();
$table->foreignId('category_id')->constrained('categories')->onDelete('cascade');
$table->timestamps();
});
Schema::create('post_tag', function (Blueprint $table) {
$table->id();
$table->foreignId('post_id')->constrained('posts')->onDelete('cascade');
$table->foreignId('tag_id')->constrained('tags')->onDelete('cascade');
$table->timestamps();
});
}
Di sini kita membuat tabel posts yang memiliki relasi category_id dan tabel pivot post_tag untuk hubungan Many-to-Many antara Post dan Tag.
Lalu jalankan migrasi:
php artisan migrate
Langkah 2: Model Post
Buat model Post dengan perintah berikut:
php artisan make:model Post
Kemudian buka app/Models/Post.php dan tambahkan relasi ke model Category dan Tag:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasFactory;
protected $fillable = ['title', 'slug', 'content', 'image','category_id'];
public function category()
{
return $this->belongsTo(Category::class);
}
public function tags()
{
return $this->belongsToMany(Tag::class, 'post_tag');
}
}
Langkah 3: Controller PostController
Buat controller PostController dengan perintah berikut:
php artisan make:controller PostController --resource
Kemudian buka app/Http/Controllers/PostController.php dan tambahkan fungsi untuk CRUD. Kita juga akan memuat Category dan Tag untuk mengisi pilihan di form create dan edit.
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use App\Models\Category;
use App\Models\Tag;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;
class PostController extends Controller
{
public function index()
{
$posts = Post::with('category', 'tags')->get();
return view('admin.posts.index', compact('posts'));
}
public function create()
{
$categories = Category::all();
$tags = Tag::all();
return view('admin.posts.create', compact('categories', 'tags'));
}
// Di method store
public function store(Request $request)
{
$request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string',
'category_id' => 'required|exists:categories,id',
'tags' => 'array',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048', // validasi gambar
]);
$data = $request->only('title', 'content', 'category_id');
$data['slug'] = Str::slug($request->input('title'));
// Upload gambar
if ($request->hasFile('image')) {
$data['image'] = $request->file('image')->store('images', 'public');
}
$post = Post::create($data);
if ($request->has('tags')) {
$post->tags()->attach($request->tags);
}
return redirect()->route('posts.index')->with('success', 'Post created successfully.');
}
public function edit(Post $post)
{
$categories = Category::all();
$tags = Tag::all();
return view('admin.posts.edit', compact('post', 'categories', 'tags'));
}
public function update(Request $request, Post $post)
{
$request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string',
'category_id' => 'required|exists:categories,id',
'tags' => 'array',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048', // validasi gambar
]);
$data = $request->only('title', 'content', 'category_id');
if ($post->title !== $request->input('title')) {
$data['slug'] = Str::slug($request->input('title'));
}
// Perbarui gambar jika ada file baru yang diupload
if ($request->hasFile('image')) {
if ($post->image) {
Storage::disk('public')->delete($post->image);
}
$data['image'] = $request->file('image')->store('images', 'public');
}
$post->update($data);
if ($request->has('tags')) {
$post->tags()->sync($request->tags);
}
return redirect()->route('posts.index')->with('success', 'Post updated successfully.');
}
public function destroy(Post $post)
{
$post->delete();
return redirect()->route('posts.index')->with('success', 'Post deleted successfully');
}
}
Langkah 4: Routes
Tambahkan route untuk PostController di routes/web.php:
use App\Http\Controllers\PostController;
Route::resource('posts', PostController::class);
Langkah 5: Views untuk CRUD Post
Buat folder posts di dalam resources/views/admin/ untuk menyimpan tampilan CRUD Post.
a) index.blade.php
Buat index.blade.php untuk menampilkan daftar artikel.
@extends('layouts.admin')
@section('content')
<div class="container mt-4">
<h2>Post List</h2>
<a href="{{ route('posts.create') }}" class="btn btn-primary mb-3">Add New Post</a>
@if(session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif
<table class="table table-bordered">
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>Category</th>
<th>Image</th>
<th>Tags</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach($posts as $post)
<tr>
<td>{{ $loop->iteration }}</td>
<td>{{ $post->title }}</td>
<td>{{ $post->category->name }}</td>
<td>
@if($post->image)
<img src="{{ asset('storage/' . $post->image) }}" alt="Post Image" width="50">
@else
No Image
@endif
</td>
<td>{{ $post->tags->pluck('name')->implode(', ') }}</td>
<td>
<a href="{{ route('posts.edit', $post) }}" class="btn btn-warning btn-sm">Edit</a>
<form action="{{ route('posts.destroy', $post) }}" method="POST" style="display:inline-block;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure?')">Delete</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endsection
b) create.blade.php
Buat create.blade.php untuk menambahkan artikel baru.
@extends('layouts.admin')
@section('content')
<div class="container mt-4">
<h2>Create Post</h2>
<form action="{{ route('posts.store') }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="form-group mb-3">
<label for="title">Post Title</label>
<input type="text" name="title" class="form-control" id="title" required>
</div>
<div class="form-group mb-3">
<label for="category_id">Category</label>
<select name="category_id" class="form-control" id="category_id">
@foreach($categories as $category)
<option value="{{ $category->id }}">{{ $category->name }}</option>
@endforeach
</select>
</div>
<div class="form-group mb-3">
<label for="tags">Tags</label>
<select name="tags[]" class="form-control" id="tags" multiple>
@foreach($tags as $tag)
<option value="{{ $tag->id }}">{{ $tag->name }}</option>
@endforeach
</select>
</div>
<div class="form-group mb-3">
<label for="content">Content</label>
<textarea name="content" class="form-control"></textarea>
</div>
<div class="form-group mb-3">
<label for="image">Upload Image</label>
<input type="file" name="image" class="form-control" id="image">
</div>
<div class="form-group mb-3">
<button type="submit" class="btn btn-primary">Save</button>
<a href="{{ route('posts.index') }}" class="btn btn-secondary">Back</a>
</div>
</form>
</div>
@endsection
c) edit.blade.php
Buat file edit.blade.php untuk melakukan editing artikel.
@extends('layouts.admin')
@section('content')
<div class="container mt-4">
<h2>Edit Post</h2>
<form action="{{ route('posts.update', $post->id) }}" method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
<div class="form-group mb-3">
<label for="title">Post Title</label>
<input type="text" name="title" class="form-control" id="title" value="{{ $post->title }}" required>
</div>
<div class="form-group mb-3">
<label for="category_id">Category</label>
<select name="category_id" class="form-control" id="category_id">
@foreach($categories as $category)
<option value="{{ $category->id }}" {{ $post->category_id == $category->id ? 'selected' : '' }}>
{{ $category->name }}
</option>
@endforeach
</select>
</div>
<div class="form-group mb-3">
<label for="tags">Tags</label>
<select name="tags[]" class="form-control" id="tags" multiple>
@foreach($tags as $tag)
<option value="{{ $tag->id }}"
{{ $post->tags->contains($tag->id) ? 'selected' : '' }}>
{{ $tag->name }}
</option>
@endforeach
</select>
</div>
<div class="form-group mb-3">
<label for="content">Content</label>
<textarea name="content" class="form-control" rows="5" required>{{ $post->content }}</textarea>
</div>
<div class="form-group mb-3">
<label for="image">Upload Image</label>
<input type="file" name="image" class="form-control" id="image">
</div>
@if($post->image)
<div class="mb-3">
<img src="{{ asset('storage/' . $post->image) }}" alt="Post Image" width="150">
</div>
@endif
<button type="submit" class="btn btn-primary">Update</button>
<a href="{{ route('posts.index') }}" class="btn btn-secondary">Back</a>
</form>
</div>
@endsection
Kemudian lakukan testing CRUD Post. lakukan insert data seperti berikut:
Jika berhasil, akan seperti berikut:
Lakukan juga untuk update Aticle akan seperti berikut:
Selamat Mencobaa!! Silahkan tinggalkan komentar jika mengalami kendala atau ada yang belum jelas.