Setelah berkas lolos verifikasi, giliran lapisan penyimpanan yang bekerja. Facade Storage menyediakan satu keluarga method untuk menulis isi mentah, menambah baris ke berkas yang ada, memindahkan dan menyalin berkas antar lokasi, sampai menulis dari stream untuk berkas raksasa. Chapter ini menjalankan semuanya pada satu disk public yang kita isi bertahap, lalu memeriksa isi disk setelah tiap operasi — sehingga setiap klaim terlihat buktinya.
put(): Menulis Isi Mentah #
put() adalah method paling dasar: menerima path relatif dan string isi, lalu menimpa berkas bila sudah ada. get() membaca isinya kembali, dan exists() mengecek keberadaan tanpa membaca:
use Illuminate\Support\Facades\Storage;
$disk = Storage::disk('public');
$disk->put('laporan/catatan.txt', "baris pertama\n");
echo $disk->get('laporan/catatan.txt');Output yang Dihasilkan:
baris pertamaFolder laporan/ dibuat otomatis saat penulisan — kita tidak perlu makeDirectory() lebih dulu. Perilaku timpa-menimpa ini juga yang membuat put() cocok untuk berkas yang memang selalu versi terbaru, seperti snapshot state atau cache hasil generate.
append dan prepend #
Dua method pelengkap menambah isi tanpa membaca berkas penuh ke variabel. append() menambahkan di akhir, prepend() di awal; keduanya menyisipkan PHP_EOL sebagai pemisah bila berkas sudah ada:
$disk->append('laporan/catatan.txt', 'baris kedua');
$disk->prepend('laporan/catatan.txt', 'baris pembuka');
echo "--- setelah append & prepend ---\n";
echo $disk->get('laporan/catatan.txt')."\n";Output yang Dihasilkan:
--- setelah append & prepend ---
baris pembuka
baris pertama
baris keduaAda baris kosong di antara “baris pertama” dan “baris kedua” karena isi awal berakhir dengan \n milik kita sendiri, lalu append() menyisipkan PHP_EOL lagi sebagai pemisah. Pola append seperti ini khas untuk berkas log ringan yang tumbuh sepanjang hari.
putFile dan putFileAs: Tulis dari Object Berkas #
Pasangan ini adalah sisi Storage dari store()/storeAs() yang kita pakai di controller. putFile() menerima object berkas dan membuat nama hash otomatis; putFileAs() memakai nama yang kita tentukan:
use Illuminate\Http\UploadedFile;
$tmp = tempnam(sys_get_temp_dir(), 'prop').'.pdf';
file_put_contents($tmp, '%PDF-1.4 isi proposal');
$berkas = new UploadedFile($tmp, 'proposal.pdf', 'application/pdf', null, true);
$hashPath = $disk->putFile('proyek', $berkas);
echo "--- putFile ---\n";
echo 'folder : '.dirname($hashPath)."\n";
echo 'ekstensi : '.pathinfo($hashPath, PATHINFO_EXTENSION)."\n";
echo 'nama 40 karakter acak: '.var_export(
(bool) preg_match('/^[a-zA-Z0-9]{40}$/', basename($hashPath, '.pdf')),
true
)."\n";
$namaPath = $disk->putFileAs('proyek', $berkas, 'proposal-final.pdf');
echo 'putFileAs: '.$namaPath."\n";Output yang Dihasilkan:
--- putFile ---
folder : proyek
ekstensi : pdf
nama 40 karakter acak: true
putFileAs: proyek/proposal-final.pdfBedanya dengan store(): arah panggilan terbalik. Pada upload, object berkas memanggil store() dari sisi request; di sini kode kita memanggil disk untuk menerima object berkas. Keduanya berujung pada implementasi putFileAs() yang sama.
Konvensi Path: Folder Berbasis Tanggal #
Saat volume berkas tumbuh, satu folder datar berisi ribuan berkas menjadi mimpi buruk untuk dibackup. Konvensi umum adalah memecah path per tanggal, misalnya 2025/03/proposal-maret.pdf untuk berkas bulan Maret. Pola pembentukannya satu baris:
use Carbon\Carbon;
Carbon::setTestNow('2025-03-10 09:00:00'); // bekukan waktu agar contoh deterministik
$tanggalPath = $disk->putFileAs(now()->format('Y/m'), $berkas, 'proposal-maret.pdf');
echo 'folder tanggal: '.$tanggalPath."\n";Output yang Dihasilkan:
folder tanggal: 2025/03/proposal-maret.pdfDi aplikasi nyata kita cukup memanggil now()->format('Y/m') tanpa setTestNow — Carbon::setTestNow() di atas hanya membekukan jam agar output contoh ini bisa direproduksi. Hasilnya: berkas bulan berjalan selalu masuk folder tahun/bulan yang rapi dan mudah diarsipkan per periode.
copy, move, dan Penulisan dari Stream #
Tiga operasi penata terakhir. copy() menduplikasi berkas, move() memindahkannya (sekaligus opsi rename), dan put() juga menerima resource stream — jalur yang penting untuk berkas besar karena isi mengalir dari disk ke disk tanpa dimuat seluruhnya ke memori:
$disk->copy('proyek/proposal-final.pdf', 'proyek/proposal-salinan.pdf');
$disk->move('proyek/proposal-final.pdf', 'arsip/proposal-arsip.pdf');
$sumber = fopen($tmp, 'rb');
$disk->put('proyek/dari-stream.pdf', $sumber);
fclose($sumber);
echo 'ukuran dari-stream: '.$disk->size('proyek/dari-stream.pdf')." byte\n";
echo "--- isi disk public (allFiles) ---\n";
print_r(collect($disk->allFiles())
->map(fn ($p) => preg_match('/^proyek\/[a-zA-Z0-9]{40}\.pdf$/', $p) ? 'proyek/<hash-acak>.pdf' : $p)
->sort()->values()->all());Output yang Dihasilkan:
ukuran dari-stream: 21 byte
--- isi disk public (allFiles) ---
Array
(
[0] => .gitignore
[1] => 2025/03/proposal-maret.pdf
[2] => arsip/proposal-arsip.pdf
[3] => laporan/catatan.txt
[4] => proyek/<hash-acak>.pdf
[5] => proyek/dari-stream.pdf
[6] => proyek/proposal-salinan.pdf
)Pemetaan pada listing di atas: proyek/proposal-salinan.pdf adalah hasil copy(), arsip/proposal-arsip.pdf adalah bekas move() yang pindah sekaligus berganti nama, proyek/<hash-acak>.pdf adalah hasil putFile() (nama aslinya 40 karakter acak — ditampilkan sebagai pola agar daftarnya deterministik), dan proyek/dari-stream.pdf berukuran 21 byte sama persis dengan sumbernya. Berkas asli proposal-final.pdf sudah tidak ada di folder proyek karena dipindahkan.
Hash vs Nama Terbaca #
Dua gaya penamaan itu punya medan permainan yang berbeda:
| Kriteria | Nama hash (store, putFile) | Nama terbaca (storeAs, putFileAs) |
|---|---|---|
| Bentrok nama | Mustahil — selalu unik | Harus ditangani sendiri (prefix user, tanggal) |
| Path traversal dari nama asli | Mustahil — nama asli tak dipakai | Harus disanitasi lebih dulu |
| Keterbacaan di disk | Buruk — butuh database untuk memetakan | Bagus — bisa diinspeksi langsung |
| Cocok untuk | Upload pengguna apa pun | Berkas yang di-generate sistem |
Aturan praktisnya: nama dari luar aplikasi selalu lewat nama hash, nama dari dalam aplikasi (laporan yang kita generate sendiri) boleh terbaca asalkan dibentuk dari data yang sudah bersih.
Dengan seluruh senjata menulis di tangan, berkas kini penuh sesak di disk public — dan belum ada cara melihatnya dari browser. Chapter berikutnya menghubungkan folder itu ke URL publik lewat storage:link, sesuai dokumentasi Filesystem
.