🎯 Tujuan Pembelajaran

Di materi ini, diharapkan pembaca mampu:

  • Menambahkan fitur upload gambar pada form tambah dan edit produk di Laravel.
  • Memahami alur upload file di Laravel, mulai dari menerima input file, validasi, menyimpan, hingga menampilkannya kembali.
  • Melakukan validasi file termasuk tipe file (image), ukuran maksimal, serta keamanan dasar dalam proses upload.
  • Menyimpan file secara aman ke folder public/storage/products menggunakan store() bawaan Laravel.
  • Menyimpan nama file ke database melalui kolom image pada tabel products.

1. Memahami Alur Upload File di Laravel

Sebelum menulis kode, penting memahami alur teknis upload file di Laravel:

Request

File dikirim dari form menggunakan multipart/form-data, dan Laravel menyiapkan objek file di $request->file('image').

Validasi

Rule image memastikan file adalah gambar (jpg, jpeg, png, gif, svg, webp). Rule max membatasi ukuran file dalam kilobytes.

Store / Penyimpanan File

Fungsi store() digunakan untuk:

  • membuat nama file unik otomatis,
  • mencegah file tertimpa,
  • memastikan file berada di folder yang sesuai konfigurasi.

Menyimpan Nama File ke Database

Yang disimpan bukan file-nya, tetapi nama file (path) agar bisa ditampilkan kembali.

Menampilkan Gambar

Gambar dapat ditampilkan melalui asset('storage/...').

Dokumentasi Laravel:

📌 Filesystem Uploads: https://laravel.com/docs/11.x/filesystem#file-uploads 📌 Validation Rule Image: https://laravel.com/docs/11.x/validation#rule-image


2. Konfigurasi Storage (Dijalankan Sekali)

Folder storage/app/public tidak otomatis bisa diakses secara publik. Jalankan perintah berikut:

php artisan storage:link

Penjelasan:

  • Perintah ini membuat folder public/storage sebagai symbolic link.
  • Semua file yang di-upload ke disk public akan tersedia melalui URL storage/....
  • Tanpa symbolic link ini, gambar tidak akan muncul meski upload berhasil.

3. Tambahkan Input File Pada Form Create & Edit

Dua aturan penting:

  1. Form wajib memakai enctype="multipart/form-data".
  2. Input harus <input type="file">.
resources/views/products/create.blade.php
<form action="{{ route('products.store') }}" method="POST" enctype="multipart/form-data">
    @csrf

    <!-- input lainnya -->

    <div class="mb-3">
        <label>Gambar Produk</label>
        <input type="file" name="image" class="form-control">
    </div>

    <button class="btn btn-primary">Simpan</button>
</form>
resources/views/products/edit.blade.php

Biasanya form edit menampilkan gambar lama sebagai preview (opsional).


4. Controller: Store Produk + Upload Gambar

public function store(Request $request)
{
    $validated = $request->validate([
        'name'        => 'required',
        'price'       => 'required|numeric',
        'description' => 'nullable',
        'stock'       => 'nullable|integer',
        'image'       => 'nullable|image|max:2048', // validasi
    ]);

    // Upload image jika ada
    if ($request->hasFile('image')) {
        $validated['image'] = $request->file('image')->store('products', 'public');
    }

    Product::create($validated);

    return redirect()->route('products.index')->with('success', 'Produk berhasil ditambahkan');
}

Penjelasan:

  • image memvalidasi file gambar.
  • max:2048 = maksimal 2MB.
  • Laravel membuat nama file hash agar menghindari duplikasi.

📌 Penjelasan Tambahan: Validasi Format File

Jika ingin membatasi ekstensi tertentu, seperti:

Hanya PNG

'image' => 'nullable|mimes:png|max:2048',

Hanya JPG/JPEG

'image' => 'nullable|mimes:jpg,jpeg|max:2048',

PNG + JPG

'image' => 'nullable|mimes:png,jpg,jpeg|max:2048',

5. Controller: Update Produk + Replace Gambar Lama

public function update(Request $request, Product $product)
{
    $validated = $request->validate([
        'name'        => 'required',
        'price'       => 'required|numeric',
        'description' => 'nullable',
        'stock'       => 'nullable|integer',
        'image'       => 'nullable|image|max:2048',
    ]);

    if ($request->hasFile('image')) {

        if ($product->image && \Storage::disk('public')->exists($product->image)) {
            \Storage::disk('public')->delete($product->image);
        }

        $validated['image'] = $request->file('image')->store('products', 'public');
    }

    $product->update($validated);

    return redirect()->route('products.index')->with('success', 'Produk berhasil diperbarui');
}

Kelebihan pola update ini:

  • File lama tidak menumpuk.
  • Upload baru hanya dilakukan jika user memilih gambar baru.

6. Controller: Hapus Produk + Hapus File

public function destroy(Product $product)
{
    if ($product->image && \Storage::disk('public')->exists($product->image)) {
        \Storage::disk('public')->delete($product->image);
    }

    $product->delete();

    return redirect()->route('products.index')->with('success', 'Produk berhasil dihapus');
}

7. Menampilkan Gambar di Halaman Index

<td>
    @if ($product->image)
        <img src="{{ asset('storage/' . $product->image) }}" width="70">
    @else
        <small class="text-muted">Tidak ada gambar</small>
    @endif
</td>

8. Ringkasan Keunggulan store()

✔ Nama file unik otomatis
✔ Menghindari overwrite
✔ Lebih aman
✔ Direkomendasikan oleh Laravel
✔ Struktur penyimpanan lebih rapi


📁 Struktur Folder Penting

📁 project-laravel/
├── 📁 app/
│   └── 📁 Http/
│       └── 📁 Controllers/
│           └── 📄 ProductController.php      <-- Logika upload file & simpan nama gambar
│
├── 📁 public/
│   └── 📁 storage/
│       └── 📁 products/                      <-- Hasil upload gambar (akses publik)
│
├── 📁 resources/
│   └── 📁 views/
│       └── 📁 products/
│           ├── 📄 create.blade.php           <-- Form tambah produk + upload gambar
│           ├── 📄 edit.blade.php             <-- Form edit produk + upload gambar
│           └── 📄 index.blade.php            <-- Menampilkan daftar produk
│
├── 📁 routes/
│   └── 📄 web.php                             <-- Route CRUD termasuk delete
│
├── 📁 storage/
│   └── 📁 app/
│       └── 📁 public/
│           └── 📁 products/                   <-- File asli sebelum di-link ke public
│