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:

  1. Struktur Database - Membuat tabel posts dan relasi dengan categories dan tags
  2. Model untuk Post dan relasi ke model Category dan Tag
  3. Controller untuk CRUD Post
  4. Views untuk menampilkan, menambahkan, mengedit, dan menghapus post
  5. 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.