CRUD Manajemen Artikel - Menambahkan Artikel

6 Maret 2025
  Β·  

11 menit  Β·  

Pada artikel sebelumnya, kita sudah menampilkan artikel berdasarkan data dummy yang diperoleh melalui seeder. Sekarang, kita akan melangkah lebih jauh dengan menambahkan fitur untuk menambah artikel baru.

Untuk memberikan pengalaman user yang lebih baik, kita tambahkan notifikasi setiap kali artikel berhasil disimpan. Kita juga akan menampilkan pesan error apabila terdapat kesalahan input. Dengan begitu, user bisa langsung tahu apakah proses berhasil dilakukan atau perlu diperbaiki.

Sebagai pengingat, kita sudah membuat route berikut:

  • GET /articles/create β†’ mengarah ke method create() untuk menampilkan form pembuatan artikel baru.
  • POST /articles β†’ mengarah ke method store() untuk menyimpan artikel ke database.

Yuk, kita mulai dari langkah pertama.

Menyesuaikan Controller untuk Menampilkan Form Pembuatan Artikel Baru

Seperti yang kita bahas sebelumnya, untuk menampilkan form pembuatan artikel baru, kita perlu menyesuaikan method create().

Buka file ArticleController.php dan sesuaikan kode menjadi sebagai berikut:

πŸ“„ app/Http/Controllers/ArticleController.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/** ... **/

    /**
     * Show the form for creating a new resource.
     */
    public function create(): View
    {
        $statusList = Article::STATUS_LIST;

        return view('pages.articles.create', compact('statusList'));
    }

/** ... **/
  • Kita memanfaatkan konstanta STATUS_LIST (sudah kita buat sebelumnya pada saat membuat model untuk tabel artikel) untuk menampilkan daftar status artikel. Konstanta ini akan digunakan untuk mengisi opsi pada dropdown status publikasi artikel.

Membuat View untuk Menampilkan Form Pembuatan Artikel Baru

Setelah menyiapkan route dan controller, sekarang saatnya membuat form input untuk artikel.

Form ini dibagi menjadi dua kolom:

  • bagian kiri berupa input utama seperti judul, slug, dan konten;
  • bagian kanan berupa input metadata tambahan seperti gambar pilihan, status publikasi, dan tanggal terbit.

Untuk mendukung pembuatan form, kita akan memanfaatkan beberapa package/library berikut:

  • Laravel HTML β€” membantu kita membuat elemen-elemen HTML menggunakan sintaks PHP yang lebih ringkas, rapi, dan konsisten. Dengan Laravel HTML, kita bisa membuat elemen form seperti input, select, dan textarea langsung dari PHP, tanpa menulis tag HTML secara manual. Package ini mendukung berbagai fitur seperti binding data dari model, integrasi CSRF token otomatis, pengelolaan atribut, dan sebagainya.
  • Froala Editor β€” sebuah rich text editor berbasis JavaScript yang akan kita gunakan untuk memberikan pengalaman WYSIWYG (What You See Is What You Get) saat menulis konten artikel.

Instalasi dan Konfigurasi Laravel HTML

  1. Install Laravel HTML menggunakan Composer dengan perintah berikut:

    sail composer require spatie/laravel-html
    
  2. Sesuaikan file pada bootstrap/providers.php menjadi sebagai berikut:

    πŸ“„ bootstrap/providers.php

    1
    2
    3
    4
    5
    6
    
    <?php
    
    return [
        App\Providers\AppServiceProvider::class,
        Spatie\Html\HtmlServiceProvider::class,
    ];
    

Instalasi dan Konfigurasi Froala Editor

Untuk mempermudah proses instalasi, kita akan memanfaatkan CDN. Versi yang kita gunakan adalah v4.5.1.

Penggunaan versi spesifik penting agar dokumentasi dan perilaku editor sesuai dengan yang kita bahas pada artikel ini.

  1. Buka file styles.blade.php dan tambahkan kode berikut:

    πŸ“„ resources/views/layouts/styles.blade.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    <style>
        /** ... **/
    </style>
    
    @vite(['resources/css/app.css'])
    
    <link href='https://cdn.jsdelivr.net/npm/[email protected]/css/froala_editor.pkgd.min.css' rel='stylesheet' type='text/css' />
    
    @stack('styles')
    
  2. Buka file scripts.blade.php dan tambahkan kode berikut:

    πŸ“„ resources/views/layouts/scripts.blade.php

    1
    2
    3
    4
    5
    
    @vite(['resources/js/app.js'])
    
    <script type='text/javascript' src='https://cdn.jsdelivr.net/npm/[email protected]/js/froala_editor.pkgd.min.js'></script>
    
    @stack('scripts')
    

Membuat View

Sekarang semua dependensi yang dibutuhkan sudah siap, saatnya kita membangun form input artikel.

Buat file resources/views/pages/articles/create.blade.php dan tambahkan kode berikut:

πŸ“„ resources/views/pages/articles/create.blade.php

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
@extends('layouts.app')

@section('title', 'Tambah Artikel')

@section('content')
    <div id="content" class="container">
        {{ html()->form('POST', route('articles.store'))->acceptsFiles()->autocomplete(false)->open() }}

        <div class="row">
            <div class="col-lg-8">
                <div class="mb-3">
                    @php
                        $name = 'title';
                        $placeholder = 'Judul';
                    @endphp

                    {{ html()->text($name)->class('form-control fs-3' . ($errors->has($name) ? ' is-invalid' : ''))->placeholder($placeholder) }}

                    @error($name)
                        <div class="invalid-feedback">{{ $message }}</div>
                    @enderror
                </div>

                <div class="mb-3">
                    @php
                        $name = 'slug';
                        $placeholder = 'Slug';
                    @endphp

                    <div class="input-group">
                        <div class="input-group-prepend">
                            <div class="input-group-text">{{ route('articles.index') }}/</div>
                        </div>

                        {{ html()->text($name)->class('form-control' . ($errors->has($name) ? ' is-invalid' : ''))->placeholder($placeholder) }}

                        @error($name)
                            <div class="invalid-feedback">{{ $message }}</div>
                        @enderror
                    </div>
                </div>

                <div class="mb-3">
                    @php $name = 'content'; @endphp

                    {{ html()->textarea($name)->class('form-control' . ($errors->has($name) ? ' is-invalid' : '')) }}

                    @error($name)
                        <div class="invalid-feedback">{{ $message }}</div>
                    @enderror
                </div>
            </div>

            <div class="col-lg-4">
                <div class="card mb-3">
                    @php
                        $name = 'feature_image_url';
                        $placeholder = 'Gambar Pilihan';
                    @endphp

                    <div class="card-header">{{ $placeholder }}</div>
                    <div class="card-body">
                        {{ html()->file($name)->class('form-control' . ($errors->has($name) ? ' is-invalid' : '')) }}

                        @error($name)
                            <div class="invalid-feedback">{{ $message }}</div>
                        @enderror
                    </div>
                </div>

                <div class="card mb-3">
                    <div class="card-body">
                        <div class="mb-2">
                            @php
                                $name = 'status';
                                $placeholder = 'Status';
                            @endphp

                            {{ html()->select($name, $statusList)->class('form-select' . ($errors->has($name) ? ' is-invalid' : ''))->placeholder($placeholder) }}

                            @error($name)
                                <div class="invalid-feedback">{{ $message }}</div>
                            @enderror
                        </div>

                        <div class="mb-2">
                            @php $name = 'published_at'; @endphp

                            {{ html()->datetime($name)->class('form-control' . ($errors->has($name) ? ' is-invalid' : '')) }}

                            @error($name)
                                <div class="invalid-feedback">{{ $message }}</div>
                            @enderror
                        </div>
                    </div>
                </div>

                {{ html()->submit('Simpan')->class('btn btn-primary') }}
            </div>
        </div>

        {{ html()->form()->close() }}
    </div>
@endsection

@push('scripts')
    <script type="module">
        $('[name=title]').on('input', function() {
            $('[name=slug]').val($(this).val().replace(/\s+/g, '-').replace(/[^a-z0-9-]/gi, '').toLowerCase());
        });

        new FroalaEditor('textarea[name=content]', {
            htmlRemoveTags: ['div'],
        });
    </script>
@endpush
  • Form akan mengirim data ke endpoint POST /articles, yang ditangani oleh method store() pada controller.
  • Method acceptsFiles() menambahkan atribut enctype="multipart/form-data" ke tag <form> agar form bisa mengirimkan file (gambar).
  • Fungsi html()->form(), html()->text(), dan lainnya yang diawali dengan html() adalah bagian dari package Laravel HTML, yang menggantikan penulisan elemen form menggunakan tag HTML manual seperti <input type="text" ...>.
    • Laravel HTML juga otomatis menangani binding nilai lama (old()) saat form gagal validasi.
  • Fungsi $errors->has() dan directive @error() β€” memeriksa apakah terdapat error pada field yang ditentukan.

  • Pada baris 107, atribut type=module ditujukan agar script menggunakan skema ES Module, yang kompatibel dengan Vite.
  • Pada baris 108 - 110, nilai pada field slug otomatis diisi apabila terdapat pengubahan pada field title dengan ketentuan sebagai berikut:
    • Spasi diubah menjadi tanda hubung.
    • Karakter selain a-z, 0-9, dan - (tanda hubung) dihapus.
    • Semuanya diubah menjadi huruf kecil.
  • Para baris 112 s.d. 114, Froala Editor diinisialisasi untuk field content dengan menghilangkan tag <div>.

Kita sudah berhasil membuat form pembuatan artikel baru. Keren!

Menyesuaikan Controller untuk Menyimpan Artikel

Sebelum benar-benar menyimpan artikel ke database, kita perlu memastikan bahwa data yang dikirimkan melalui form sudah valid. Untuk itu, kita akan melakukan proses validasi terlebih dahulu.

Agar kode tetap rapi dan terstruktur, kita akan menggunakan Form Request β€” sebuah fitur dari Laravel yang memungkinkan kita memisahkan logika validasi ke dalam kelas tersendiri.

Membuat Form Request

  1. Jalankan perintah berikut untuk membuat file ArticleRequest.php:

    sail artisan make:request ArticleRequest
    
    • Perintah ini akan membuat file baru bernama ArticleRequest.php pada direktori app/Http/Requests.
    • Pada kasus ini, kita akan menggunakan form request yang sama untuk proses menambahkan (store) dan mengubah (update) artikel.
      • Apabila validasi untuk masing-masing proses sangat berbeda atau lebih kompleks, kita bisa memisahkan form request untuk setiap proses. Misalnya: StoreArticleRequest untuk proses menambahkan artikel dan UpdateArticleRequest untuk proses mengubah artikel.
  2. Buka file ArticleRequest.php dan sesuaikan kode menjadi sebagai berikut:

    πŸ“„ app/Http/Requests/ArticleRequest.php

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    
    <?php
    
    namespace App\Http\Requests;
    
    use App\Models\Article;
    use Illuminate\Foundation\Http\FormRequest;
    
    class ArticleRequest extends FormRequest
    {
        /**
         * Determine if the user is authorized to make this request.
         */
        public function authorize(): bool
        {
            return true;
        }
    
        /**
         * Get the validation rules that apply to the request.
         *
         * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
         */
        public function rules(): array
        {
            return [
                'title' => 'required|string|max:255',
                'slug' => 'required|string|max:255|regex:/^[a-z0-9-]+$/u|unique:articles,slug'.($this->article?->id ? ", {$this->article->id}" : ''),
                'feature_image_url' => 'nullable|file|mimes:jpeg,jpg,png,gif',
                'content' => 'required|string',
                'status' => 'required|in:'.implode(',', array_keys(Article::STATUS_LIST)),
                'published_at' => 'required|date',
            ];
        }
    }
    
    • Method authorize() β€” mengatur apakah user memiliki hak untuk membuat request ini. Untuk kasus ini, kita cukup mengembalikan nilai true.
    • Method rules() β€” mendefinisikan aturan validasi untuk setiap input. Penjelasan masing-masing field:
      FieldKeterangan
      titleWajib diisi, berupa string, maksimal 255 karakter.
      slug
      • Wajib diisi, berupa string, maksimal 255 karakter.
      • Hanya boleh terdiri dari huruf kecil, angka, dan tanda hubung (-).
      • Harus unik di tabel `articles` pada kolom `slug`. Namun, pada proses `update`, slug artikel yang sedang diedit akan dikecualikan.
      feature_image_urlOpsional. Jika diisi, harus berupa file dengan tipe gambar: jpeg, jpg, png, atau gif.
      contentWajib diisi, berupa string.
      statusWajib diisi. Nilai harus salah satu dari daftar yang tersedia di Article::STATUS_LIST. Kita menggunakan fungsi implode() untuk menyusun daftar nilai valid yang dipisahkan koma.
      published_atWajib diisi dan harus berupa tanggal yang valid.

Instalasi dan Konfigurasi PHPFlasher

Untuk memberikan umpan balik yang jelas kepada user setelah menyimpan artikel, kita akan menggunakan PHPFlasher β€” sebuah package yang memungkinkan kita menampilkan notifikasi yang elegan hanya dengan satu baris kode.

Untuk melakukan instalasi, cukup jalankan perintah berikut di Teminal:

sail composer require php-flasher/flasher-laravel

Setelah proses instalasi selesai, jalankan perintah berikut untuk setup asset yang dibutuhkan:

sail artisan flasher:install

Saat ini, kita menggunakan layout dengan Navbar dari Bootstrap. Agar tampilan Flasher tidak tertimpa dengan Navbar, kita bisa menyesuaikan nilai z-index pada class fl-wrapper sebagai berikut:

πŸ“„ resources/views/partials/styles.blade.php

1
2
3
4
5
6
7
8
9
<style>
    /** ... **/

    .fl-wrapper {
        z-index: 1031 !important;
    }
</style>

/** ... **/

PHPFlasher sudah siap digunakan!

Mengenal Laravel File Storage

Ketika user mengunggah gambar untuk artikel, kita perlu menyimpannya ke sistem file dengan aman dan terstruktur. Laravel menyediakan fitur File Storage yang memudahkan proses penyimpanan file ke berbagai media seperti local disk, Amazon S3, dan lainnya.

Secara default, Laravel sudah menyediakan beberapa konfigurasi disk penyimpanan yang bisa kita temukan di file config/filesystems.php, yaitu local, public, dan s3. Untuk menyimpan gambar artikel, kita akan menggunakan disk public karena file gambar perlu diakses secara publik (misalnya saat menampilkan thumbnail artikel).

Disk ini menyimpan file di direktori storage/app/public, bukan langsung di folder public. Agar file bisa diakses melalui browser, kita harus membuat symbolic link dari direktori storage/app/public ke direktori public/storage.

Jalankan perintah berikut di Terminal:

sail artisan storage:link

Setelah menjalankan perintah di atas, semua file yang disimpan di direktori storage/app/public akan dapat diakses melalui URL http://localhost/storage/.

Menyesuaikan Controller

Selanjutnya, kita akan mengubah tipe parameter $request di method store() menjadi ArticleRequest. Dengan begitu, Laravel akan otomatis menjalankan validasi berdasarkan aturan yang telah kita definisikan sebelumnya.

Berikut adalah implementasi method store() di ArticleController yang sudah disesuaikan:

πŸ“„ app/Http/Controllers/ArticleController.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/** ... **/

use App\Http\Requests\ArticleRequest;
/** ... **/
use Illuminate\Http\RedirectResponse;

/** ... **/

    /**
     * Store a newly created resource in storage.
     */
    public function store(ArticleRequest $request): RedirectResponse
    {
        try {
           $input = $request->validated();

           if ($request->hasFile('feature_image_url')) {
               $file = $request->file('feature_image_url');
               $fileName = time().'_'.$file->getClientOriginalName();

               $file->storeAs(Article::IMAGE_STORAGE_PATH, $fileName, 'public');
               $input['feature_image_url'] = $fileName;
           }

           Article::create($input);

           flash()->success('Data berhasil disimpan!');
        } catch (\Exception $e) {
           report($e);
           flash()->error('Data tidak berhasil disimpan!');
        } finally {
           return redirect(route('articles.index'));
        }
    }

/** ... **/
  • Baris 12
    Data dari form sudah otomatis divalidasi oleh ArticleRequest. Method validated() akan mengembalikan data yang valid dan aman untuk disimpan ke database.
  • Baris 17-23
    Jika user mengunggah gambar pilihan, kita simpan di direktori penyimpanan lokal. Nama dari file ditambahkan timestamp di depan agar unik, lalu nama file disimpan ke dalam database melalui field feature_image_url.
  • Baris 25
    Data artikel langsung disimpan menggunakan Eloquent.
  • Baris 27
    Jika proses berhasil, tampilkan notifikasi flash message dengan status sukses.
  • Baris 29-30
    Sebaliknya, jika terjadi kegagalan, kita tangkap exception-nya dan tampilkan notifikasi flash message dengan status error.
    Fungsi report($e) akan mengirimkan exception ke sistem log Laravel.
  • Baris 32
    Apapun hasilnyaβ€”berhasil atau gagalβ€”user akan diarahkan kembali ke halaman daftar artikel.

Berikut adalah tampilan form apabila terdapat kesalahan validasi:


Berikut adalah tampilan form apabila seluruh form sudah diisi dengan benar:

Agar user bisa membuka form pembuatan artikel baru tanpa harus mengetik link http://localhost/articles/create, kita perlu menambahkan link pada halaman menampilan daftar artikel.

Buka file resources/views/pages/articles/index.blade.php, lalu sesuaikan kode berikut:

πŸ“„ resources/views/pages/articles/index.blade.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
/** ... **/

@section('content')
    <div id="content" class="container">
        <div class="row">
            <div class="col">
                <div class="card">
                    <div class="card-header d-flex flex-row-reverse">
                        <a href="{{ route('articles.create') }}" class="btn btn-primary btn-sm">Tambah Data</a>
                    </div>
                    <div class="card-body">
                        {{ $dataTable->table(['class' => 'table table-striped']) }}
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

/** ... **/

Hasil dari pengubahan di atas adalah sebagai berikut:



Dengan ini, kita telah berhasil membuat fitur untuk menambahkan artikel baru, lengkap dengan form input, validasi yang rapi menggunakan Form Request, serta notifikasi menggunakan PHPFlasher untuk memberikan umpan balik kepada user.

Pada artikel berikutnya, kita akan melanjutkan dengan fitur untuk mengedit artikel yang sudah ada.

Sampai jumpa di pembahasan selanjutnya! πŸ‘‹

CRUD Manajemen Artikel - Menambahkan Artikel
Top