export( ['Name', 'Notes', 'Amount'], [ ['Alice', 'Comma, quote "inside"', 12.5], ["Bob\r\nJones", 'plain', null], ], ), 'CSV fields must follow RFC 4180 quoting and deterministic CRLF records.' ); $withoutBom = $exporter->export(['Name'], [['Alice']]); $withBom = $exporter->export(['Name'], [['Alice']], true); csv_exporter_assert_same("Name\r\nAlice\r\n", $withoutBom, 'CSV must not include a BOM by default.'); csv_exporter_assert_same("\xEF\xBB\xBFName\r\nAlice\r\n", $withBom, 'CSV must include a UTF-8 BOM only when requested.'); csv_exporter_assert_same("Name\r\n'=SUM(A1:A2)\r\n", $exporter->export(['Name'], [['=SUM(A1:A2)']]), 'CSV must neutralize spreadsheet formulas.'); $mismatchRaised = false; try { $exporter->export(['Name', 'Amount'], [['Alice']]); } catch (InvalidArgumentException $exception) { $mismatchRaised = str_contains($exception->getMessage(), 'row 1') && str_contains($exception->getMessage(), '2 headers') && str_contains($exception->getMessage(), '1 fields'); } if (!$mismatchRaised) { throw new RuntimeException('Mismatched row lengths must be rejected with a useful error.'); } printf("CSV exporter tests: 4 passed\n");