getgrav/grav · error · RuntimeException
Decoding CSV failed
Error message
Decoding CSV failed
What it means
CsvFormatter::decode() splits the payload into lines with preg_split('/\r\n|\r|\n/', $data) and throws this only when preg_split itself fails (returns false) — a PCRE engine failure, not a CSV shape problem. The classic cause is exhausting pcre.backtrack_limit on very large input; malformed-but-splittable data produces the sibling 'CSV header missing' / 'Badly formatted CSV line' errors instead.
Source
Thrown at system/src/Grav/Framework/File/Formatter/CsvFormatter.php:92
foreach ($data as $row) {
$string .= $this->encodeLine($row, $delimiter);
}
return $string;
}
/**
* @param string $data
* @param string|null $delimiter
* @return array
* @see FileFormatterInterface::decode()
*/
public function decode($data, $delimiter = null): array
{
$delimiter ??= $this->getDelimiter();
$lines = preg_split('/\r\n|\r|\n/', $data);
if ($lines === false) {
throw new RuntimeException('Decoding CSV failed');
}
// Get the field names
$headerStr = array_shift($lines);
if (!$headerStr) {
throw new RuntimeException('CSV header missing');
}
$header = str_getcsv($headerStr, $delimiter);
// Allow for replacing a null string with null/empty value
$null_replace = $this->getConfig('null');
// Get the data
$list = [];
$line = null;
try {
foreach ($lines as $line) {View on GitHub (pinned to 6040efed04)
Solutions
- Raise the limit for the import: ini_set('pcre.backtrack_limit', '10000000');.
- Stream instead of loading whole: SplFileObject with READ_CSV flag or fgetcsv() row by row.
- Chunk the input and decode pieces, preserving the header row for each chunk.
- Diagnose with a manual split: preg_split(...) then preg_last_error() to confirm PREG_BACKTRACK_LIMIT_ERROR.
Example fix
// before
$rows = $csvFormatter->decode(file_get_contents('big.csv'));
// after: stream rows, no whole-file regex split
$file = new \SplFileObject('big.csv');
$file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY);
$rows = [];
foreach ($file as $row) {
if ($row !== false && $row[0] !== null) { $rows[] = $row; }
} Defensive patterns
Strategy: try-catch
Validate before calling
// Probe the engine before decode(): same split the formatter performs
$lines = @preg_split('/\r\n|\r|\n/', $data);
if ($lines === false) {
$err = preg_last_error(); // PREG_BACKTRACK_LIMIT_ERROR etc.
// chunk the data, raise pcre.backtrack_limit, or use a streaming reader
} Try / catch
try {
$rows = $csvFormatter->decode($data);
} catch (\RuntimeException $e) {
// fall back to row-by-row streaming (SplFileObject::READ_CSV)
$rows = streamCsv($path);
} Prevention
- Stream large CSV imports (fgetcsv/SplFileObject) instead of decoding whole files.
- Raise pcre.backtrack_limit for import jobs that must load big payloads.
- Cap accepted upload sizes to values the runtime can actually split.
When it happens
Trigger: Decoding a multi-megabyte CSV (or one enormous single line) through CsvFormatter when pcre.backtrack_limit (default ~1M) is exceeded; constrained runtimes where the PCRE engine errors during the split.
Common situations: Plugins importing large data exports via the formatter instead of a streaming reader; shared hosting with low PCRE limits; PHP-FPM pools with restrictive ini settings.
Related errors
- CSV header missing
- Badly formatted CSV line: {line}
- Bad data
- Cannot unserialize Block: %s
- Unsupported version %s
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/037dd818a6046805.
Report an issue: GitHub.