How to read csv file using php
Reading CSV files is one of the most common tasks in PHP development — whether you’re importing user data, processing exports from spreadsheets, or building data pipelines. PHP’s built-in fgetcsv() function is the cleanest, most reliable way to do it, and in this guide you’ll learn exactly how to use it the right way.
By the end of this tutorial, you’ll know how to read CSVs line by line, handle headers properly, deal with edge cases like commas inside fields and UTF-8 BOM characters, and avoid the memory traps that crash production scripts.
What is fgetcsv in PHP?
fgetcsv() is a native PHP function that reads a single line from an open file pointer and parses it as CSV (Comma-Separated Values), returning an indexed array of the fields. Unlike using explode(',', $line), fgetcsv() correctly handles quoted fields, escaped characters, and fields that contain commas — which is why you should always prefer it for CSV parsing.
It’s been part of PHP since version 4.0 and works on every modern PHP installation without requiring any extensions.
Basic Example: Reading a CSV File Line by Line
Here’s the simplest possible example, adapted from the official PHP documentation:
<?php
$row = 1;
if (($handle = fopen("test.csv", "r")) !== FALSE) {
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
$num = count($data);
echo "<p>$num fields in line $row:</p>\n";
$row++;
for ($c = 0; $c < $num; $c++) {
echo $data[$c] . "<br />\n";
}
}
fclose($handle);
}
What’s happening here:
fopen("test.csv", "r")opens the file in read mode and returns a file handle.fgetcsv($handle, 1000, ",")reads one line at a time. The1000is the maximum line length in bytes (use0for unlimited, recommended in modern PHP). The","is the delimiter.- The
whileloop continues untilfgetcsv()returnsFALSE(end of file). fclose($handle)closes the file handle — always do this to free resources.
A Better, Production-Ready Version
The basic example above works, but real-world CSVs have headers, you usually want associative arrays (not numeric), and you need proper error handling. Here’s a more useful version:
<?php
function readCsvToArray(string $filePath): array {
if (!file_exists($filePath) || !is_readable($filePath)) {
throw new RuntimeException("File not found or unreadable: {$filePath}");
}
$rows = [];
$handle = fopen($filePath, "r");
if ($handle === false) {
throw new RuntimeException("Could not open file: {$filePath}");
}
// Read the first line as headers
$headers = fgetcsv($handle, 0, ",");
if ($headers === false) {
fclose($handle);
return [];
}
// Strip UTF-8 BOM from first header if present
$headers[0] = preg_replace('/^\xEF\xBB\xBF/', '', $headers[0]);
while (($data = fgetcsv($handle, 0, ",")) !== false) {
// Skip empty rows
if (count($data) === 1 && $data[0] === null) {
continue;
}
$rows[] = array_combine($headers, $data);
}
fclose($handle);
return $rows;
}
// Usage
$users = readCsvToArray('users.csv');
foreach ($users as $user) {
echo $user['name'] . ' - ' . $user['email'] . "\n";
}
Why this version is better:
- Returns associative arrays using
array_combine()so you can access fields by name ($user['email']) instead of index ($user[1]). - Handles the UTF-8 BOM — a hidden 3-byte character that Excel adds to CSVs and breaks header matching.
- Throws exceptions on file errors instead of silently failing.
- Uses
0for max line length, removing the arbitrary 1000-byte limit. - Skips empty rows that some spreadsheet exports leave behind.
Reading Large CSV Files Without Running Out of Memory
If you’re processing a 500MB CSV with millions of rows, never load it all into an array. Use a generator instead — it processes one row at a time and uses constant memory:
<?php
function csvRowGenerator(string $filePath): Generator {
$handle = fopen($filePath, "r");
if ($handle === false) {
throw new RuntimeException("Could not open file: {$filePath}");
}
$headers = fgetcsv($handle, 0, ",");
$headers[0] = preg_replace('/^\xEF\xBB\xBF/', '', $headers[0]);
while (($data = fgetcsv($handle, 0, ",")) !== false) {
yield array_combine($headers, $data);
}
fclose($handle);
}
// Memory stays low even for huge files
foreach (csvRowGenerator('huge-file.csv') as $row) {
// Process one row at a time — insert into DB, send to API, etc.
saveToDatabase($row);
}
A generator with yield lets you iterate over millions of rows using only the memory of a single row at a time. This is the pattern Laravel, Symfony, and other major frameworks use internally for bulk imports.
Handling Different Delimiters (TSV, Semicolons, Pipes)
Not every “CSV” actually uses commas. European exports often use semicolons, tab-separated files use \t, and some systems use pipes. Just change the third parameter:
// Semicolon-separated (common in European Excel exports)
$data = fgetcsv($handle, 0, ";");
// Tab-separated values (TSV)
$data = fgetcsv($handle, 0, "\t");
// Pipe-separated
$data = fgetcsv($handle, 0, "|");
Common Problems and How to Fix Them
Problem 1: First header has a weird character
You see something like "\u{feff}name" instead of "name". That’s the UTF-8 Byte Order Mark. Strip it with the regex shown in the production example above.
Problem 2: Fields with commas get split incorrectly
A field like "Smith, John" becomes two fields. The fix: make sure your CSV uses proper quoting. fgetcsv() handles "Smith, John" correctly — but only if the quotes are there. If they’re not, the CSV is malformed and needs to be fixed at the source.
Problem 3: Special characters appear as ? or garbled text
This is an encoding mismatch. Convert the data to UTF-8 as you read it:
$row = array_map(fn($field) => mb_convert_encoding($field, 'UTF-8', 'ISO-8859-1'), $data);
Problem 4: Script times out on large files
Increase the time limit and disable output buffering:
set_time_limit(0);
ini_set('memory_limit', '512M');
But better: use the generator pattern from above so you never hit limits in the first place.
Writing CSV Files: The Counterpart Function
For completeness, the inverse of fgetcsv() is fputcsv(), which writes an array as a CSV line:
$handle = fopen('export.csv', 'w');
fputcsv($handle, ['Name', 'Email', 'Joined']); // Headers
fputcsv($handle, ['Jane Doe', '[email protected]', '2025-01-15']);
fclose($handle);
Quick Reference: fgetcsv Parameters
| Parameter | Default | Description |
|---|---|---|
$stream |
required | File handle from fopen() |
$length |
0 |
Max line length in bytes. Use 0 for unlimited (PHP 8.0+) |
$separator |
, |
Field delimiter |
$enclosure |
" |
Field enclosure character |
$escape |
\ |
Escape character (deprecated in PHP 8.4, will be removed) |
Wrapping Up
The fgetcsv() function is everything you need to read CSV files in PHP, but using it well means thinking about headers, encoding, memory, and edge cases — not just copying the example from the docs. For small files, the production-ready function above is plenty. For large files, switch to the generator pattern and your script will handle gigabytes without breaking a sweat.
If you’re building a CSV importer for users, also validate every field before saving — never trust uploaded data, and always use prepared statements when inserting into your database.
Be the first to comment