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:
| Operasi | Yang Harus Dilakukan |
|---|---|
| Create | Simpan file, record path-nya di database |
| Update | Hapus file lama, simpan file baru, update path di database |
| Delete | Hapus 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 โ
{{-- 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 โ
{{-- 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.
// 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 โ
// 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 โ
{{-- 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.