CSV (Comma-Separated Values) adalah format pertukaran data paling universal: hampir semua spreadsheet, database, dan sistem ERP dapat mengekspor serta mengimpor CSV. Sebagai programmer PHP, kita akan sering diminta membaca data pelanggan dari berkas CSV, menggabungkan dua laporan penjualan, atau mengekspor hasil query database ke CSV. PHP menyediakan dua fungsi native untuk tugas tersebut: fgetcsv() untuk membaca dan fputcsv() untuk menulis. Pada Chapter ini kita akan mempelajari keduanya secara bertahap, mulai dari berkas sederhana sampai berkas berukuran besar yang harus diproses baris demi baris.

Apa Itu Berkas CSV #

Berkas CSV menyimpan data tabel sebagai teks biasa: setiap baris mewakili satu record, dan setiap field dipisahkan oleh karakter delimiter, biasanya koma. Konvensi yang umum dipakai adalah baris pertama berisi nama kolom (header). Berikut contoh berkas CSV daftar produk:

csv
nama,harga,stok
Kopi Gayo,55000,10
Teh Melati,28000,25

Aturan penting CSV: apabila sebuah field mengandung delimiter, tanda kutip, atau baris baru, field tersebut harus diapit tanda kutip ganda dan tanda kutip di dalamnya ditulis ganda (""). Karena aturan inilah kita tidak boleh mem-parsing CSV dengan explode(",") — pendekatan tersebut langsung rusak ketika sebuah nilai sah mengandung koma.

Membaca Baris CSV dengan fgetcsv #

Fungsi fgetcsv() membaca satu baris dari berkas yang sudah dibuka dengan fopen(), sekaligus mem-parsing baris tersebut menjadi array. Fungsi mengembalikan false ketika pointer berkas sudah mencapai akhir, sehingga pola while berikut menjadi idiom standar pembacaan CSV:

php
<?php
$path = "/tmp/produk.csv";
file_put_contents($path, "nama,harga,stok\nKopi Gayo,55000,10\nTeh Melati,28000,25\n");

$handle = fopen($path, "r");
while (($row = fgetcsv($handle, null, ",", '"', "")) !== false) {
    echo implode(" | ", $row) . "\n";
}
fclose($handle);

Output yang Dihasilkan:

text
nama | harga | stok
Kopi Gayo | 55000 | 10
Teh Melati | 28000 | 25

Mari kita telaah kode di atas:

  1. Baris 2-3: file_put_contents() menyiapkan berkas contoh di /tmp agar contoh ini mandiri; pada kasus nyata berkas sudah ada dan baris ini diganti dengan path berkas sebenarnya.
  2. Baris 5: fopen($path, "r") membuka berkas untuk dibaca dan mengembalikan resource handle yang kita simpan di $handle.
  3. Baris 6: setiap iterasi memanggil fgetcsv() yang membaca satu baris lalu mengembalikan array field. Saat mencapai akhir berkas, nilai kembaliannya menjadi false dan perulangan berhenti.
  4. Baris 7: implode(" | ", $row) menggabungkan kembali field hanya untuk keperluan menampilkan hasil; data aslinya sudah berupa array per field.
  5. Baris 9: fclose() menutup handle berkas — kebiasaan yang tetap perlu dijaga meskipun PHP menutup berkas otomatis di akhir skrip.

Parameter Delimiter, Enclosure, dan Escape #

fgetcsv() menerima beberapa parameter penting setelah handle berkas:

ParameterPeranNilai umum
$lengthBatas panjang baris (dalam byte); null berarti tanpa batasnull
$separatorKarakter pemisah antar field"," atau ";"
$enclosureKarakter pengapit field yang spesial'"'
$escapeKarakter escape warisan; string kosong berarti perilaku CSV standar""

Banyak sistem — termasuk hasil ekspor spreadsheet dengan regional setting Eropa — memakai titik koma sebagai delimiter. Contoh berikut membaca berkas dengan delimiter ; yang juga memuat nilai mengandung koma di dalam tanda kutip:

php
<?php
$path = "/tmp/penjualan-toko.csv";
$isi = "2026-01-10;\"Kopi Gayo 250g\";3\n"
    . "2026-01-11;\"Teh Melati, celup\";5\n";
file_put_contents($path, $isi);

$handle = fopen($path, "r");
while (($row = fgetcsv($handle, null, ";", '"', "")) !== false) {
    echo $row[0] . " -> " . $row[1] . " (" . $row[2] . " pcs)\n";
}
fclose($handle);

Output yang Dihasilkan:

text
2026-01-10 -> Kopi Gayo 250g (3 pcs)
2026-01-11 -> Teh Melati, celup (5 pcs)

Nilai "Teh Melati, celup" berhasil dibaca utuh sebagai satu field meskipun mengandung koma, karena fgetcsv() menghormati tanda pengapit. Parameter $length kita isi null — sejak PHP 7.x nilai ini opsional dan null membuat PHP membaca baris berapa pun panjangnya, yang merupakan pilihan teraman untuk data nyata.

Baris Header dan Baris Asosiatif #

Konvensi baris pertama sebagai header membuat kode lebih mudah dibaca apabila setiap baris data diubah menjadi array asosiatif. Fungsi array_combine() memetakan array header sebagai kunci dan array baris sebagai nilai:

php
<?php
$path = "/tmp/karyawan.csv";
file_put_contents($path, "nama,divisi,gaji\nBudi,IT,8500000\nSiti,Keuangan,7800000\n");

$handle = fopen($path, "r");
$header = fgetcsv($handle, null, ",", '"', "");
while (($row = fgetcsv($handle, null, ",", '"', "")) !== false) {
    $karyawan = array_combine($header, $row);
    echo sprintf("%s bekerja di divisi %s dengan gaji Rp%d\n", $karyawan["nama"], $karyawan["divisi"], $karyawan["gaji"]);
}
fclose($handle);

Output yang Dihasilkan:

text
Budi bekerja di divisi IT dengan gaji Rp8500000
Siti bekerja di divisi Keuangan dengan gaji Rp7800000

Panggilan fgetcsv() pertama di luar perulangan membaca baris header, sehingga perulangan hanya memproses baris data. Setelah itu, $karyawan["nama"] jauh lebih ekspresif daripada $row[0] dan membuat kode tahan bila urutan kolom berubah. Perlu diwaspadai: array_combine() melempar ValueError apabila jumlah kolom header tidak sama dengan jumlah field baris — kondisi yang terjadi ketika baris data kehilangan tanda koma terakhir, sehingga validasi jumlah kolom akan kita bahas tuntas pada Chapter Data Validation.

Fungsi fputcsv() menulis satu baris dari array sekaligus menangani aturan pengutipan secara otomatis. Kita tidak perlu — dan tidak boleh — mengutip nilai secara manual. Contoh berikut menulis data, menampilkan isi mentah berkasnya, lalu membacanya kembali:

php
<?php
$path = "/tmp/catatan.csv";
$rows = [
    ["id", "catatan"],
    [1, "Sudah dibayar, menunggu konfirmasi"],
    [2, "Catatan \"penting\" dari pelanggan"],
];

$handle = fopen($path, "w");
foreach ($rows as $row) {
    fputcsv($handle, $row, ",", '"', "");
}
fclose($handle);

echo "--- isi mentah berkas ---\n";
echo file_get_contents($path);

echo "--- hasil pembacaan kembali ---\n";
$handle = fopen($path, "r");
while (($row = fgetcsv($handle, null, ",", '"', "")) !== false) {
    echo $row[0] . " => " . $row[1] . "\n";
}
fclose($handle);

Output yang Dihasilkan:

text
--- isi mentah berkas ---
id,catatan
1,"Sudah dibayar, menunggu konfirmasi"
2,"Catatan ""penting"" dari pelanggan"
--- hasil pembacaan kembali ---
id => catatan
1 => Sudah dibayar, menunggu konfirmasi
2 => Catatan "penting" dari pelanggan

Perhatikan isi mentah berkasnya: nilai "Sudah dibayar, menunggu konfirmasi" diapit kutip karena mengandung koma, dan kutip di dalam "penting" ditulis ganda menjadi ""penting"". Saat dibaca kembali dengan fgetcsv(), kedua nilai kembali persis aslinya — bukti bahwa pasangan fputcsv()/fgetcsv() menjaga integritas data secara bolak-balik (round-trip).

Memproses Berkas CSV Besar #

fgetcsv() bekerja per baris, sehingga memori yang dipakai hanya sebesar satu baris berapa pun ukuran berkasnya. Pola berikut membangun berkas 5.000 baris, lalu mengagregasi seluruhnya tanpa pernah memuat berkas itu ke memori sekaligus:

php
<?php
$path = "/tmp/penjualan-besar.csv";
$handle = fopen($path, "w");
fputcsv($handle, ["tanggal", "produk", "jumlah"], ",", '"', "");
for ($i = 1; $i <= 5000; $i++) {
    $tanggal = sprintf("2026-01-%02d", ($i % 28) + 1);
    fputcsv($handle, [$tanggal, "Kopi Gayo", $i % 7 + 1], ",", '"', "");
}
fclose($handle);

$totalJumlah = 0;
$barisData = 0;
$handle = fopen($path, "r");
fgetcsv($handle, null, ",", '"', "");
while (($row = fgetcsv($handle, null, ",", '"', "")) !== false) {
    $totalJumlah += (int) $row[2];
    $barisData++;
}
fclose($handle);

echo "Baris data: $barisData\n";
echo "Total jumlah: $totalJumlah\n";

Output yang Dihasilkan:

text
Baris data: 5000
Total jumlah: 19997

Bagian pertama hanya menyiapkan berkas contoh berukuran realistis. Bagian kedua — perulangan while — adalah intinya: dua variabel penghitung diperbarui per baris, dan $row berisi paling banyak satu baris data pada satu waktu. Untuk berkas berjuta baris, pendekatan ini memakai memori yang hampir konstan.

Ringkasan #

fgetcsv() membaca CSV baris demi baris menjadi array, fputcsv() menulis array menjadi baris CSV dengan pengutipan otomatis, dan array_combine() mengubah baris data menjadi array asosiatif berbasis header. Kita juga telah melihat mengapa parameter $separator, $enclosure, dan $escape perlu diberikan secara eksplisit di PHP 8.4, serta bagaimana pemrosesan per baris menjaga memori tetap kecil untuk berkas besar. Setelah format teks tabular ini dikuasai, langkah alami berikutnya adalah format pertukaran data modern yang mendunia: JSON, yang akan kita bahas pada Chapter berikutnya.

Referensi #