1. Operasi Sistem Berkas yang Aman (file_get_contents, file_put_contents, dan flock)
Untuk berkas konfigurasi atau templat berukuran kecil (di bawah beberapa ratus kilobyte), fungsi file_get_contents() dan file_put_contents() sangat praktis. Namun, ketika menulis ke berkas dari aplikasi web yang melayani banyak permintaan secara bersamaan, gunakan bendera LOCK_EX (Exclusive Lock) agar dua worker PHP-FPM tidak menulis ke berkas yang sama pada mikrodetik yang bersamaan hingga merusak isinya:
<?php
declare(strict_types=1);
function catatBarisLog(string $jalurBerkas, string $pesan): void
{
$baris = sprintf("[%s] %s\n", gmdate('Y-m-d\TH:i:s\Z'), $pesan);
// FILE_APPEND menambahkan ke baris paling bawah,
// LOCK_EX mengunci berkas secara eksklusif selama proses penulisan berlangsung!
$berhasil = file_put_contents($jalurBerkas, $baris, FILE_APPEND | LOCK_EX);
if ($berhasil === false) {
throw new RuntimeException("Gagal menulis ke berkas log: {$jalurBerkas}");
}
}
**🚫 Keamanan Jalur Berkas: Cegah Path Traversal dengan basename() atau realpath()! Jika pengguna mengirimkan parameter nama berkas yang ingin diunduh (misalnya ?berkas=laporan-q1.pdf), jangan pernah langsung menyambungnya ke file_get_contents('/var/data/' . $_GET['berkas'])! Penyerang dapat mengirim ?berkas=../../../etc/passwd! Selalu pangkas informasi direktori dengan basename($input), atau pastikan hasil realpath($jalurLengkap)** benar-benar diawali (str_starts_with) dengan jalur direktori penyimpanan yang diizinkan!
2. Masalah Memori Saat Membaca Berkas Besar dan Solusi Generator (yield)
Bayangkan Anda diminta membuat fitur untuk mengimpor atau mengekspor berkas laporan transaksi transaksi.csv berukuran 500 MB (berisi 2 juta baris), sementara batas memori tiap worker PHP (memory_limit) di server produksi adalah 128 MB.
- Cara Buruk (
file('transaksi.csv')atau$stmt->fetchAll()): PHP mencoba memuat seluruh 2 juta baris sekaligus ke dalam satuarrayraksasa di RAM. Dalam hitungan detik, memori menembus128 MBdan skrip langsung mati denganFatal error: Allowed memory size of 134217728 bytes exhausted! - Cara Profesional (Membaca Baris demi Baris dengan Generator
yield): Sebuah fungsi yang mengandung kata kunciyieldotomatis menjadi Generator (mengembalikan objekGeneratoryang mengimplementasikanIterator). Ia hanya menahan SATU baris di memori pada satu waktu, menyerahkannya ke perulanganforeach, lalu membebaskan memori baris tersebut saat melangkah ke baris berikutnya!
<?php
declare(strict_types=1);
/**
* Membaca berkas CSV berukuran berapa pun (bahkan 10 GB!)
* dengan pemakaian RAM tetap di bawah 1 MB menggunakan Generator (yield).
*
* @return Generator<int, array<string, string>>
*/
function bacaCsvHematMemori(string $jalurCsv): Generator
{
$handle = fopen($jalurCsv, 'rb');
if ($handle === false) {
throw new RuntimeException("Tidak dapat membuka berkas CSV: {$jalurCsv}");
}
try {
// 1. Baca baris pertama sebagai nama kolom header
$header = fgetcsv($handle, escape: '');
if (!is_array($header)) {
return;
}
$nomorBaris = 1;
// 2. Baca baris demi baris secara streaming
while (($baris = fgetcsv($handle, escape: '')) !== false) {
$nomorBaris++;
if (count($baris) !== count($header)) {
continue; // Lewati baris kosong/cacat
}
// Serahkan satu baris asosiatif ke pemanggil, lalu jeda fungsi ini!
yield $nomorBaris => array_combine($header, $baris);
}
} finally {
// Blok finally DIJAMIN menutup berkas meskipun pemanggil melakukan 'break' di tengah foreach!
fclose($handle);
}
}
// Cara memakainya sangat bersih seperti array biasa, namun 100% hemat RAM!
foreach (bacaCsvHematMemori('/tmp/data-besar.csv') as $barisKe => $data) {
// Proses $data['id'], $data['nominal']...
if ($barisKe >= 100) {
break; // Saat break dipanggil, blok 'finally' di dalam generator otomatis menutup berkas!
}
}
fgetcsv() dan fputcsv()Sejak PHP 8.4, memanggil fgetcsv() atau fputcsv() tanpa menentukan parameter $escape secara eksplisit telah dideprekasi (karena perilaku escape \ lama tidak standar RFC 4180). Selalu berikan argumen bernama escape: '' (string kosong) agar penguraian CSV mematuhi standar RFC 4180 sepenuhnya!
3. Memanfaatkan PHP Streams (php://temp & php://output) untuk Ekspor CSV Tanpa Berkas Sampah
Ketika pengguna mengeklik tombol "Unduh Laporan CSV", Anda tidak perlu membuat berkas fisik sementara di folder /tmp/ lalu pusing menghapusnya! Gunakan stream bawaan php://output (atau php://temp yang otomatis memakai RAM hingga 2 MB lalu beralih ke berkas sementara tersembunyi jika lebih besar):
<?php
declare(strict_types=1);
function kirimUnduhanCsv(string $namaBerkas, iterable $barisData): void
{
header('Content-Type: text/csv; charset=utf-8');
header(sprintf('Content-Disposition: attachment; filename="%s"', basename($namaBerkas)));
$keluaran = fopen('php://output', 'wb');
if ($keluaran === false) {
return;
}
// Tulis header kolom
fputcsv($keluaran, ['ID Modul', 'Judul', 'Durasi (Menit)'], escape: '');
foreach ($barisData as $baris) {
fputcsv($keluaran, [$baris['id'], $baris['judul'], $baris['durasi']], escape: '');
}
fclose($keluaran);
}
Latihan Praktik: Pipeline Pengolah Log Server dengan Generator yield
Buatlah berkas analisis-log-generator.php:
- Buat fungsi
hasilkanSampelLog(int $jumlahBaris): Generatoryang menggunakanyielduntuk menghasilkan 50.000 baris log simulasi tanpa membuat array di memori. - Buat fungsi generator penyaring
saringLogGalat(iterable $sumberLog): Generatoryang menerima iterable dari langkah 1 dan hanya melakukanyielduntuk baris yang mengandung kata'ERROR'. - Hitung total baris
'ERROR'dan cetak puncak pemakaian RAM menggunakanmemory_get_peak_usage(true)untuk membuktikan betapa kecilnya RAM yang terpakai!