A CSV delimiter cannot always be inferred perfectly, but a multi-record sample is safer than counting characters in the first line. Parse each candidate delimiter and score consistent column counts across nonblank records.
Last updated: September 26, 2026.
function detectDelimiter(string $path, array $candidates = [',', ';', " ", '|']): string
{
$scores = array_fill_keys($candidates, []);
$file = new SplFileObject($path, 'r');
for ($sampled = 0; !$file->eof() && $sampled < 20;) {
$line = $file->fgets();
if (trim($line) === '') continue;
$sampled++;
foreach ($candidates as $delimiter) {
$scores[$delimiter][] = count(str_getcsv($line, $delimiter, '"', ''));
}
}
$ranked = [];
foreach ($scores as $delimiter => $counts) {
$frequency = array_count_values($counts);
arsort($frequency);
$columns = (int) array_key_first($frequency);
$ranked[$delimiter] = $columns > 1 ? reset($frequency) * $columns : 0;
}
arsort($ranked);
return (string) array_key_first($ranked);
}The score rewards a candidate that repeatedly produces more than one column. Production code should reject a tie or zero score rather than silently choosing the first delimiter.
Account for quoted multiline fields
Sampling physical lines is imperfect when a quoted field contains line breaks. For high-trust imports, require the delimiter in the file contract or let the user confirm the detected value. After choosing, parse the actual file with SplFileObject::fgetcsv(), which can read logical CSV records.
Separate encoding from delimiter detection
A valid delimiter is one byte. If the sample is UTF-16, zero bytes may appear between ASCII characters and detection will fail. Detect or obtain the source encoding first, convert a sample to UTF-8, and then score delimiters. The existing CSV encoding guide covers BOMs and spreadsheet exports.
Validate after detection
- Reject ambiguous top scores or ask for confirmation.
- Parse the header and several data records.
- Check expected field-count ranges and required names.
- Log the chosen delimiter and confidence without logging sensitive rows.
- Allow an explicit override for known suppliers.
SplFileObject::getCsvControl() only returns the configured control characters; it does not inspect a file and guess them. Once the delimiter is approved, use it consistently in large-file processing or CSV splitting.