Storage Disk: Local, Public, dan Cloud
Pahami disk storage dan ganti ke S3 tanpa ubah kode.
Kenapa Abstraksi Disk Penting
Bayangkan aplikasimu menyimpan ribuan foto produk di server lokal. Suatu hari traffic naik, kamu pindah ke S3. Tanpa abstraksi disk, kamu harus berburu setiap file_put_contents, move_uploaded_file, dan path hardcode di seluruh codebase. Dengan disk Laravel, kamu cukup ubah .env dan semua kode Storage:: tetap jalan. Inilah contoh nyata kenapa arsitektur yang baik menghemat minggu kerja.
Konfigurasi Disk
Semua disk didefinisikan di config/filesystems.php. Laravel 11 bawaan menyediakan tiga: local (privat, di storage/app), public (bisa diakses web via symlink), dan s3.
// config/filesystems.php
'default' => env('FILESYSTEM_DISK', 'local'),
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'throw' => false,
],
'public' => [
'driver' => 'local',
'root' => storage_path('app/public'),
'url' => env('APP_URL').'/storage',
'visibility' => 'public',
'throw' => false,
],
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
],
],Untuk memakai S3, install dulu drivernya lalu isi .env:
composer require league/flysystem-aws-s3-v3# .env
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
AWS_DEFAULT_REGION=ap-southeast-1
AWS_BUCKET=nama-bucketOperasi Sehari-hari
// app/Services/DokumenService.php
namespace App\Services;
use Illuminate\Support\Facades\Storage;
class DokumenService
{
public function simpan(string $nama, string $isi): string
{
// put() mengembalikan path relatif, bukan boolean
return Storage::disk('local')->put('dokumen/'.$nama, $isi);
}
public function baca(string $path): ?string
{
if (! Storage::disk('local')->exists($path)) {
return null;
}
return Storage::disk('local')->get($path);
}
public function tautanPublik(string $path): string
{
// hanya masuk akal untuk disk public/s3
return Storage::disk('public')->url($path);
}
}Untuk S3, ada senjata rahasia: URL sementara bertanda tangan. File tetap privat di bucket, tapi user dapat link yang kedaluwarsa otomatis:
$url = Storage::disk('s3')->temporaryUrl(
'invoice/INV-001.pdf',
now()->addMinutes(15)
);File Privat: Serve Lewat Controller
Inilah pola yang benar untuk file yang butuh otorisasi (invoice, KTP, rapor). Jangan taruh di disk public, simpan di local, lalu buat route yang mengecek policy:
// app/Http/Controllers/DokumenController.php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Storage;
class DokumenController extends Controller
{
public function unduh(string $file)
{
// cegah path traversal: ambil nama file saja
$file = basename($file);
$path = 'dokumen-pribadi/'.$file;
abort_unless(Storage::disk('local')->exists($path), 404);
// contoh cek kepemilikan via model Dokumen
$dokumen = \App\Models\Dokumen::where('path', $path)->firstOrFail();
$this->authorize('view', $dokumen);
return Storage::disk('local')->download($path);
}
}Jebakan Umum
Memanggil Storage::url() di disk local. Disk local tidak punya URL publik, hasilnya URL ngawur yang 404. Pakai url() hanya untuk disk public atau s3.
Menyimpan hasil Storage::url() ke database. URL mengandung APP_URL. Saat domain berubah (staging ke production), semua link di database rusak. Simpan selalu path relatif (dokumen/a.jpg), bangun URL saat dibutuhkan.
Lupa visibility di S3. File yang di-upload ke S3 default-nya privat. Kalau butuh publik, set 'visibility' => 'public' di config atau pakai Storage::disk('s3')->put($path, $isi, 'public').
Catatan teknis: Option
'throw' => falsedi config membuat operasi gagal diam-diam mengembalikanfalse/null. Untuk file kritis (bukti pembayaran!), set'throw' => trueagar exception dilempar dan kamu sadar ada yang salah, bukan mengira file tersimpan padahal tidak.
Tantangan
Download Privat
Simpan file ke disk local (privat). Buat route /dokumen/{file} dengan policy: hanya pemilik yang bisa download via Storage::download(). Buktikan user lain dapat 403.
Kuis Bab
Uji pemahamanmu: Bab 8: REST API
Jawab 5 soal berikut, lalu tekan "Periksa Jawaban".
1.Apa fungsi API Resource?
2.Status code yang tepat untuk DELETE sukses tanpa body?
3.Bagaimana client mengirim Sanctum token?
4.Atribut form apa yang WAJIB untuk upload file?
5.Keuntungan abstraksi Storage disk?