Panduan Tutorial Laravel

Belajar Laravel Batch 8: Upload File dan Storage - Mengelola Media Aplikasi

Belajar Laravel Batch 8: Upload File dan Storage - Mengelola Media Aplikasi
Twitter / X WhatsApp Facebook LinkedIn Telegram
Apa yang kamu butuhkan

Selamat datang kembali, PahamITian! Di Batch 8 ini, kita akan menyelami dunia upload file dan manajemen penyimpanan media di Laravel. Fitur ini krusial untuk aplikasi modern seperti blog, e-commerce, atau portofolio. Kita akan belajar mulai dari bagaimana file diupload, validasinya, cara menyimpannya dengan aman menggunakan Storage Facade, hingga menampilkannya di halaman web dan menghapus file lama saat tidak diperlukan lagi. Siap menambahkan "featured image" ke artikelmu?

Halo, PahamITian! Selamat datang kembali di petualangan Laravel kita! Setelah di Batch sebelumnya kita berhasil mengamankan aplikasi dengan sistem autentikasi dasar, kini saatnya kita membuat aplikasi kita lebih "hidup" dan interaktif dengan kemampuan mengelola media. Yap, di Batch 8 ini, kita akan fokus pada Upload File dan Storage di Laravel.

Bayangkan sebuah blog tanpa gambar? Atau toko online tanpa foto produk? Pasti kurang menarik, kan? Fitur upload file adalah tulang punggung dari banyak aplikasi web modern. Di sini, kita akan belajar bagaimana Laravel mempermudah proses ini, mulai dari menerima file dari pengguna, memvalidasinya, menyimpannya dengan aman, hingga menampilkannya kembali.


Apa yang Akan Kita Pelajari di Batch Ini?

  1. Konsep Dasar Upload File: Memahami bagaimana file dikirim dari browser ke server.
  2. Validasi File: Memastikan file yang diupload aman dan sesuai kriteria.
  3. Storage Facade: Menggunakan fitur penyimpanan file bawaan Laravel.
  4. Menampilkan Gambar: Cara menampilkan file yang sudah diupload di halaman Blade.
  5. Menghapus File Lama: Mengelola file agar tidak menumpuk saat ada perubahan atau penghapusan.
  6. Latihan Praktik: Menerapkan "Featured Image" untuk artikel kita.

1. Konsep Dasar Upload File di Laravel

Ketika pengguna mengupload file melalui form HTML, ada beberapa hal yang terjadi di balik layar. Browser akan mengirimkan data file bersamaan dengan data form lainnya ke server. Di sisi server (dalam hal ini, aplikasi Laravel kita), kita perlu "menangkap" file tersebut dan memprosesnya.

Penting: Untuk form yang memiliki input file, kamu wajib menambahkan atribut enctype="multipart/form-data" pada tag <form>. Tanpa ini, file tidak akan terkirim ke server.

HTML
<form action="/upload" method="POST" enctype="multipart/form-data">
    @csrf
    <input type="file" name="gambar_artikel">
    <button type="submit">Upload</button>
</form>

2. Validasi File: "Saring Dulu Sebelum Disimpan!"

Validasi adalah langkah krusial untuk keamanan dan integritas aplikasi. Kamu tidak ingin pengguna mengupload file berbahaya (misalnya, script PHP) atau file yang terlalu besar hingga memenuhi servermu, kan? Laravel menyediakan aturan validasi yang sangat lengkap untuk file.

Mari kita lihat contohnya di Controller:

PHP
// app/Http/Controllers/PostController.php

use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;

class PostController extends Controller
{
    public function store(Request $request)
    {
        try {
            $request->validate([
                'title' => 'required|string|max:255',
                'content' => 'required|string',
                'featured_image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048' // Aturan validasi file
            ]);

            // ... logika penyimpanan data lainnya

        } catch (ValidationException $e) {
            return redirect()->back()->withErrors($e->errors())->withInput();
        }
    }
}

Penjelasan Aturan Validasi File:

  • nullable: File boleh kosong (tidak wajib diupload).
  • image: Memastikan file yang diupload adalah gambar (jpeg, png, bmp, gif, svg, webp).
  • mimes:jpeg,png,jpg,gif: Memastikan ekstensi file hanya yang disebutkan. Ini lebih spesifik dari image.
  • max:2048: Ukuran maksimal file dalam kilobyte (KB). Di sini, maksimal 2MB.
  • dimensions:min_width=100,min_height=100: (Opsional) Memastikan dimensi gambar minimal 100x100 piksel.

Untuk menampilkan error validasi file di Blade, caranya sama seperti menampilkan error validasi input teks biasa:

HTML
@error('featured_image')
    <div class="alert alert-danger">{{ $message }}</div>
@enderror

3. Menyimpan File dengan Storage Facade

Laravel menyediakan Storage facade yang sangat powerful dan fleksibel untuk mengelola penyimpanan file. Secara default, Laravel menggunakan local driver yang menyimpan file di storage/app. Namun, untuk file yang ingin diakses publik (seperti gambar artikel), kita akan menggunakan public disk.

Langkah-langkahnya:

  1. Konfigurasi Disk public:
PHP
    'disks' => [
        // ...
        'public' => [
            'driver' => 'local',
            'root' => storage_path('app/public'),
            'url' => env('APP_URL').'/storage',
            'visibility' => 'public',
        ],
        // ...
    ],
    
  1. Membuat Symlink (Symbolic Link):

BASH
    php artisan storage:link
    

Perintah ini akan membuat folder storage di dalam folder public project kamu, yang sebenarnya adalah "shortcut" ke storage/app/public.

  1. Menyimpan File di Controller:
  • $request->file('nama_input_file')->store('folder_tujuan', 'nama_disk');

Contoh menyimpan featured_image:

PHP
    // app/Http/Controllers/PostController.php

    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Storage; // Jangan lupa ini!
    use App\Models\Post;

    class PostController extends Controller
    {
        public function store(Request $request)
        {
            $request->validate([
                'title' => 'required|string|max:255',
                'content' => 'required|string',
                'featured_image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048'
            ]);

            $post = new Post();
            $post->title = $request->title;
            $post->content = $request->content;

            // Jika ada file gambar yang diupload
            if ($request->hasFile('featured_image')) {
                // Simpan gambar ke folder 'public/posts_images'
                // Laravel akan meng-generate nama unik untuk file ini
                $path = $request->file('featured_image')->store('posts_images', 'public');
                $post->featured_image = $path; // Simpan path file ke database
            }

            $post->save();

            return redirect()->route('posts.index')->with('success', 'Artikel berhasil ditambahkan!');
        }
    }
    

Path yang disimpan di database ($path) akan terlihat seperti posts_images/nama_file_unik.jpg.


4. Menampilkan Gambar di Blade

Untuk menampilkan gambar yang sudah disimpan di storage/app/public (dan di-symlink ke public/storage), kita bisa menggunakan helper asset() atau Storage::url().

HTML
<!-- resources/views/posts/show.blade.php -->

@if ($post->featured_image)
    <img src="{{ asset('storage/' . $post->featured_image) }}" alt="{{ $post->title }}" class="img-fluid">
@else
    <img src="{{ asset('images/default_placeholder.jpg') }}" alt="Placeholder" class="img-fluid">
@endif

<!-- Atau menggunakan Storage::url() -->
<img src="{{ Storage::url($post->featured_image) }}" alt="{{ $post->title }}" class="img-fluid">

Catatan: Pastikan kamu sudah membuat php artisan storage:link agar asset('storage/...') bisa bekerja.


5. Menghapus File Lama saat Update/Delete

Untuk menjaga kebersihan server dan menghindari file "sampah", kita perlu menghapus file lama saat gambar diupdate atau saat artikel dihapus.

Saat Update Artikel (Gambar Diganti)

Ketika pengguna mengupload gambar baru untuk artikel yang sudah ada, kita harus menghapus gambar lama terlebih dahulu.

PHP
// app/Http/Controllers/PostController.php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use App\Models\Post;

class PostController extends Controller
{
    public function update(Request $request, Post $post)
    {
        $request->validate([
            'title' => 'required|string|max:255',
            'content' => 'required|string',
            'featured_image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048'
        ]);

        $post->title = $request->title;
        $post->content = $request->content;

        // Jika ada file gambar baru yang diupload
        if ($request->hasFile('featured_image')) {
            // Hapus gambar lama jika ada
            if ($post->featured_image) {
                Storage::disk('public')->delete($post->featured_image);
            }

            // Simpan gambar baru
            $path = $request->file('featured_image')->store('posts_images', 'public');
            $post->featured_image = $path;
        }
        // Jika tidak ada file baru, tapi ada keinginan menghapus gambar yang sudah ada (misal ada checkbox 'hapus gambar')
        // if ($request->has('remove_image') && $post->featured_image) {
        //     Storage::disk('public')->delete($post->featured_image);
        //     $post->featured_image = null;
        // }

        $post->save();

        return redirect()->route('posts.index')->with('success', 'Artikel berhasil diperbarui!');
    }
}

Saat Delete Artikel

Ketika sebuah artikel dihapus, gambar terkait juga harus dihapus dari penyimpanan.

PHP
// app/Http/Controllers/PostController.php

use Illuminate\Support\Facades\Storage;
use App\Models\Post;

class PostController extends Controller
{
    public function destroy(Post $post)
    {
        // Hapus gambar terkait jika ada
        if ($post->featured_image) {
            Storage::disk('public')->delete($post->featured_image);
        }

        $post->delete();

        return redirect()->route('posts.index')->with('success', 'Artikel berhasil dihapus!');
    }
}

6. Latihan Praktik: "Featured Image" untuk Artikel

Sekarang giliranmu, PahamITian! Kita akan mengimplementasikan fitur featured_image (gambar unggulan) untuk artikel kita.

Langkah-langkahnya:

  1. Tambahkan Kolom featured_image ke Tabel posts:
  1. Tambahkan featured_image ke $fillable di Model Post:
  1. Modifikasi Form create.blade.php dan edit.blade.php:
HTML
    <!-- resources/views/posts/create.blade.php atau edit.blade.php -->

    <form action="{{ route('posts.store') }}" method="POST" enctype="multipart/form-data">
        @csrf
        <!-- ... input title dan content ... -->

        <div class="mb-3">
            <label for="featured_image" class="form-label">Gambar Unggulan</label>
            <input type="file" class="form-control @error('featured_image') is-invalid @enderror" id="featured_image" name="featured_image">
            @error('featured_image')
                <div class="invalid-feedback">{{ $message }}</div>
            @enderror
        </div>

        @if (isset($post) && $post->featured_image)
            <div class="mb-3">
                <p>Gambar saat ini:</p>
                <img src="{{ asset('storage/' . $post->featured_image) }}" alt="{{ $post->title }}" class="img-thumbnail" width="200">
            </div>
        @endif

        <button type="submit" class="btn btn-primary">Simpan Artikel</button>
    </form>
    
  1. Modifikasi PostController:
  1. Tampilkan Gambar di show.blade.php dan index.blade.php:
HTML
    <!-- resources/views/posts/show.blade.php -->
    @if ($post->featured_image)
        <img src="{{ asset('storage/' . $post->featured_image) }}" alt="{{ $post->title }}" class="img-fluid mb-4">
    @endif
    <h1>{{ $post->title }}</h1>
    <p>{{ $post->content }}</p>
    
HTML
    <!-- resources/views/posts/index.blade.php (dalam loop foreach) -->
    <div class="card mb-3">
        @if ($post->featured_image)
            <img src="{{ asset('storage/' . $post->featured_image) }}" class="card-img-top" alt="{{ $post->title }}">
        @endif
        <div class="card-body">
            <h5 class="card-title">{{ $post->title }}</h5>
            <p class="card-text">{{ Str::limit($post->content, 100) }}</p>
            <a href="{{ route('posts.show', $post->id) }}" class="btn btn-primary">Baca Selengkapnya</a>
        </div>
    </div>
    
  1. Jangan Lupa php artisan storage:link! Jika belum, jalankan perintah ini.

Ringkasan Singkat Batch 8

Di Batch ini, kita sudah belajar bagaimana cara mengelola upload file di Laravel. Kita mulai dari memahami pentingnya enctype="multipart/form-data" pada form, melakukan validasi file yang ketat untuk keamanan, menggunakan Storage facade untuk menyimpan file ke disk public, membuat symlink dengan php artisan storage:link agar file bisa diakses publik, hingga menampilkan gambar di Blade dan mengelola penghapusan file lama.


Latihan Praktik (Checklist Pemahaman)

Setelah menyelesaikan Batch 8, kamu seharusnya bisa:

  • [ ] Menambahkan kolom featured_image ke tabel posts melalui migration.
  • [ ] Menambahkan input type="file" ke form create dan edit artikel.
  • [ ] Memastikan form memiliki atribut enctype="multipart/form-data".
  • [ ] Mengimplementasikan validasi file (misalnya image, mimes, max) di Controller.
  • [ ] Menggunakan Storage::disk('public')->store() untuk menyimpan file yang diupload.
  • [ ] Menjalankan php artisan storage:link untuk membuat symlink.
  • [ ] Menampilkan gambar yang disimpan di Blade menggunakan asset('storage/' . $path) atau Storage::url($path).
  • [ ] Mengimplementasikan logika untuk menghapus gambar lama saat artikel diupdate dengan gambar baru.
  • [ ] Mengimplementasikan logika untuk menghapus gambar terkait saat artikel dihapus.

Pertanyaan Cek Pemahaman

  1. Mengapa atribut enctype="multipart/form-data" sangat penting untuk form upload file?
  2. Apa perbedaan antara Storage::disk('local') dan Storage::disk('public')?
  3. Jelaskan fungsi dari perintah php artisan storage:link.
  4. Bagaimana cara membatasi ukuran file yang diupload menjadi maksimal 5MB dan hanya menerima format pdf atau docx?
  5. Jika kamu menyimpan gambar dengan path posts_images/gambar_unik.jpg di disk public, bagaimana cara menampilkannya di Blade?

Kesalahan Umum Pemula

  • Lupa enctype="multipart/form-data": Ini adalah penyebab paling sering kenapa file tidak terupload.
  • Lupa php artisan storage:link: Gambar tidak akan muncul di browser karena path publik tidak terhubung ke penyimpanan internal Laravel.
  • Salah Path Gambar: Menggunakan path absolut atau path yang salah saat menampilkan gambar di Blade.
  • Tidak Melakukan Validasi File: Sangat berbahaya karena bisa membuka celah keamanan atau membebani server.
  • Tidak Menghapus File Lama: Menyebabkan penumpukan file "sampah" di server seiring waktu.

Persiapan untuk Batch Berikutnya

Selamat, PahamITian! Kamu sudah berhasil menguasai manajemen media di Laravel. Di Batch 9 nanti, kita akan belajar bagaimana membuat aplikasi kita lebih SEO-friendly dan mengelola status publikasi artikel. Kita akan membahas tentang slug, status draft/published, meta title dan description, serta filter artikel yang sudah dipublikasikan. Pastikan featured_image di artikelmu sudah berfungsi dengan baik ya!

Sampai jumpa di Batch 9!