Skip to content

Day 22 โ€” Manajemen File Upload: Tampilkan, Update & Hapus โ€‹

Fase: 3 โ€” Fitur Lanjutan | Minggu: 5 | Hari: Selasa


๐ŸŽฏ Tujuan Hari Ini โ€‹

  • Menampilkan foto aset yang sudah di-upload di halaman detail dan daftar.
  • Menangani logika update foto: hapus file lama, simpan file baru.
  • Menangani logika hapus aset: pastikan file foto ikut dihapus dari storage.
  • Membuat fallback gambar jika aset tidak memiliki foto.

๐Ÿ“– Penjelasan: Siklus Hidup File di Aplikasi โ€‹

Sebuah file yang sudah di-upload perlu dikelola di tiga titik:

OperasiYang Harus Dilakukan
CreateSimpan file, record path-nya di database
UpdateHapus file lama, simpan file baru, update path di database
DeleteHapus file dari storage, lalu hapus record dari database

Jika kamu hanya menghapus record dari database tanpa menghapus filenya, lama kelamaan folder storage akan penuh dengan file sampah yang tidak terpakai. Ini masalah umum di aplikasi yang tidak terkelola dengan baik.


๐Ÿ”ง Step-by-Step โ€‹

Step 1: Tampilkan Foto di Halaman Detail Aset โ€‹

html
{{-- resources/views/assets/show.blade.php --}}

<div class="card">
    <div class="card-body">
        <div class="text-center mb-3">
            @if ($asset->photo)
                <img
                    src="{{ Storage::url($asset->photo) }}"
                    alt="Foto {{ $asset->name }}"
                    class="img-fluid rounded"
                    style="max-height: 300px; object-fit: cover;"
                >
            @else
                {{-- Tampilkan placeholder jika tidak ada foto --}}
                <div class="bg-light d-flex align-items-center justify-content-center rounded"
                     style="height: 200px;">
                    <span class="text-muted">
                        <i class="bi bi-image fs-1"></i>
                        <br>Tidak ada foto
                    </span>
                </div>
            @endif
        </div>

        <h4>{{ $asset->name }}</h4>
        <p><strong>Kategori:</strong> {{ $asset->category->name }}</p>
        <p><strong>Stok:</strong> {{ $asset->stock }}</p>
        <p><strong>Kondisi:</strong> {{ $asset->condition }}</p>
    </div>
</div>

NOTE

Storage::url($asset->photo) menghasilkan URL publik yang benar, misalnya: http://localhost/storage/assets/abc123.jpg. Pastikan kamu sudah use Illuminate\Support\Facades\Storage; di controller, atau cukup gunakan helper asset() di Blade: asset('storage/' . $asset->photo).


Step 2: Tampilkan Thumbnail di Halaman Daftar Aset โ€‹

html
{{-- resources/views/assets/index.blade.php --}}

<table class="table">
    <thead>
        <tr>
            <th>Foto</th>
            <th>Nama Aset</th>
            <th>Kategori</th>
            <th>Stok</th>
            <th>Aksi</th>
        </tr>
    </thead>
    <tbody>
        @foreach ($assets as $asset)
        <tr>
            <td>
                @if ($asset->photo)
                    <img src="{{ asset('storage/' . $asset->photo) }}"
                         alt="{{ $asset->name }}"
                         style="width: 60px; height: 60px; object-fit: cover;"
                         class="rounded">
                @else
                    <div class="bg-secondary rounded d-flex align-items-center justify-content-center"
                         style="width: 60px; height: 60px;">
                        <i class="bi bi-image text-white"></i>
                    </div>
                @endif
            </td>
            <td>{{ $asset->name }}</td>
            <td>{{ $asset->category->name }}</td>
            <td>{{ $asset->stock }}</td>
            <td>
                <a href="{{ route('assets.edit', $asset) }}" class="btn btn-sm btn-warning">Edit</a>
            </td>
        </tr>
        @endforeach
    </tbody>
</table>

Step 3: Update Foto di Controller (Method update) โ€‹

Ini bagian paling penting: kita harus menghapus foto lama sebelum menyimpan foto baru.

php
// app/Http/Controllers/AssetController.php

use Illuminate\Support\Facades\Storage;

public function update(Request $request, Asset $asset)
{
    $validated = $request->validate([
        'name'        => 'required|string|max:255',
        'category_id' => 'required|exists:categories,id',
        'stock'       => 'required|integer|min:0',
        'condition'   => 'required|in:good,damaged,lost',
        'description' => 'nullable|string',
        'photo'       => 'nullable|image|mimes:jpg,jpeg,png,webp|max:2048',
    ]);

    // Cek apakah ada foto baru yang di-upload
    if ($request->hasFile('photo')) {
        // 1. Hapus foto lama jika ada
        if ($asset->photo) {
            Storage::disk('public')->delete($asset->photo);
        }

        // 2. Simpan foto baru
        $validated['photo'] = $request->file('photo')->store('assets', 'public');
    }

    $asset->update($validated);

    return redirect()->route('assets.show', $asset)
        ->with('success', 'Aset berhasil diperbarui!');
}

Step 4: Hapus Foto Saat Aset Dihapus โ€‹

php
// app/Http/Controllers/AssetController.php

public function destroy(Asset $asset)
{
    // Hapus file foto dari storage terlebih dahulu
    if ($asset->photo) {
        Storage::disk('public')->delete($asset->photo);
    }

    // Kemudian hapus record dari database
    $asset->delete();

    return redirect()->route('assets.index')
        ->with('success', 'Aset dan fotonya berhasil dihapus!');
}

Step 5: Tambahkan Preview Foto di Form Edit โ€‹

html
{{-- resources/views/assets/edit.blade.php --}}

<form action="{{ route('assets.update', $asset) }}" method="POST" enctype="multipart/form-data">
    @csrf
    @method('PUT')

    {{-- ... field lainnya ... --}}

    <div class="mb-3">
        <label class="form-label">Foto Aset Saat Ini</label>
        @if ($asset->photo)
            <div class="mb-2">
                <img src="{{ asset('storage/' . $asset->photo) }}"
                     alt="Foto Saat Ini"
                     style="max-height: 150px; border-radius: 8px;">
            </div>
        @else
            <p class="text-muted small">Belum ada foto</p>
        @endif

        <label for="photo" class="form-label">Ganti Foto (Opsional)</label>
        <input type="file" class="form-control @error('photo') is-invalid @enderror"
               id="photo" name="photo" accept="image/*">
        @error('photo')
            <div class="invalid-feedback">{{ $message }}</div>
        @enderror
        <div class="form-text">Kosongkan jika tidak ingin mengganti foto.</div>
    </div>

    <button type="submit" class="btn btn-primary">Update Aset</button>
</form>

๐Ÿงช Uji Coba Mandiri โ€‹

  • [ ] Buka halaman daftar aset โ†’ pastikan thumbnail foto tampil.
  • [ ] Buka halaman detail aset โ†’ pastikan foto besar tampil dengan benar.
  • [ ] Edit aset yang sudah punya foto โ†’ ganti dengan foto baru โ†’ simpan โ†’ pastikan foto lama hilang dari storage/app/public/assets/.
  • [ ] Edit aset tanpa mengganti foto โ†’ pastikan foto lama tetap ada.
  • [ ] Hapus aset yang punya foto โ†’ cek folder storage โ†’ pastikan file ikut terhapus.

๐Ÿ’ก Tips & Best Practices โ€‹

TIP

Untuk aplikasi yang lebih besar, pertimbangkan menggunakan Observer Model Laravel. Kamu bisa membuat AssetObserver dengan method deleting() yang otomatis menghapus file setiap kali model dihapus, sehingga kamu tidak perlu menulis logika hapus di setiap controller.

WARNING

Selalu hapus file lama SEBELUM menyimpan yang baru. Jika urutan terbalik dan proses simpan gagal, kamu akan kehilangan file lama tanpa memiliki gantinya.

Program Magang SMK RPL โ€” Rekayasa Perangkat Lunak (2 Bulan)