LaravelStorageFileMahir4 mnt baca

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.

php
// 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:

bash
composer require league/flysystem-aws-s3-v3
bash
# .env
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
AWS_DEFAULT_REGION=ap-southeast-1
AWS_BUCKET=nama-bucket

Operasi Sehari-hari

php
// 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:

php
$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:

php
// 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' => false di config membuat operasi gagal diam-diam mengembalikan false/null. Untuk file kritis (bukti pembayaran!), set 'throw' => true agar 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?