ajgl/csv-rfc
Drop-in replacements for PHP CSV functions to read/write RFC4180-compliant CSV. Fixes PHP’s fputcsv escaping bug with backslash + quote sequences, ensuring correct enclosure escaping. Includes fgetcsv/fputcsv/str_getcsv and SplFileObject equivalents.
Installation Add the package via Composer:
composer require ajgl/csv-rfc
Register the service provider in config/app.php under providers:
Ajgl\CsvRfc\CsvRfcServiceProvider::class,
First Use Case
Replace native fputcsv() with Ajgl\CsvRfc\fputcsv() in your controller or service:
use Ajgl\CsvRfc\fputcsv;
$file = fopen('output.csv', 'w');
fputcsv($file, ['Name', 'Email', 'Notes']);
fputcsv($file, ['John Doe', 'john@example.com', 'Escaped "quote" here']);
fclose($file);
Where to Look First
fgetcsv, fputcsv, str_getcsv, str_putcsv).CsvRfcServiceProvider for aliases and bindings.Reading CSV Files
Use Ajgl\CsvRfc\fgetcsv() or Ajgl\CsvRfc\str_getcsv() for RFC4180-compliant parsing:
$file = fopen('input.csv', 'r');
while (($row = fgetcsv($file)) !== false) {
// Process $row (array of fields)
}
fclose($file);
Writing CSV Files Replace native functions with the package’s versions to handle escaped quotes:
$csvData = [
['Name', 'Email'],
['Alice', 'alice@example.com'],
['Bob', 'bob@example.com', 'Line break\ninside']
];
$file = fopen('output.csv', 'w');
foreach ($csvData as $row) {
fputcsv($file, $row);
}
fclose($file);
Streaming Large Files
Use Ajgl\CsvRfc\CsvWriter for memory-efficient writes:
$writer = new \Ajgl\CsvRfc\CsvWriter('output.csv');
$writer->putRow(['Header1', 'Header2']);
$writer->putRow(['Data1', 'Data2']);
$writer->close();
Integration with Laravel
handle() methods:
$csv = $request->file('csv')->getRealPath();
$file = fopen($csv, 'r');
while (($row = fgetcsv($file)) !== false) {
// Validate/process $row
}
return response()->stream(function () {
$file = fopen('php://output', 'w');
fputcsv($file, ['Header1', 'Header2']);
// Write rows...
fclose($file);
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="export.csv"'
]);
Custom Delimiters/Enclosures Override defaults via the second parameter:
fputcsv($file, ['Data'], ';', '"'); // Semicolon-delimited, double-quoted
Backward Compatibility
use Ajgl\CsvRfc\fputcsv).Escaping Quotes
" must be wrapped in " and escaped as "" (RFC4180). The package handles this automatically, but manual overrides may break compliance:
// ❌ Avoid this (unless you know what you're doing)
fputcsv($file, ['Bad "quote"'], '"', '"', '\\');
Line Endings
\n. For Windows compatibility, set the CsvRfcServiceProvider config:
'line_ending' => "\r\n",
in config/services.php (if using the provider).Memory Limits
str_getcsv() loads the entire string into memory. For large data, prefer file-based methods (fgetcsv).Deprecated PHP Functions
csv_to_array() or array_to_csv(). Use fgetcsv/fputcsv instead.Validate Output Use online tools (e.g., CSV Validator) to verify RFC4180 compliance.
Logging Edge Cases Log problematic rows before writing:
if (strpos($field, '"') !== false) {
Log::debug("Escaped field: " . var_export($field, true));
}
Testing Test with these edge cases:
// Test data
$edgeCases = [
['Normal', 'Data'],
['"Quoted"', 'Field'],
['Escaped "quote"', 'Here'],
['Line\nBreak', 'Test'],
['Tab\tSeparated', 'Data'],
];
Custom Writers
Extend Ajgl\CsvRfc\CsvWriter for batch processing:
class BatchCsvWriter extends \Ajgl\CsvRfc\CsvWriter {
public function __construct($path, $batchSize = 1000) {
parent::__construct($path);
$this->batchSize = $batchSize;
$this->buffer = [];
}
public function putRow($row) {
$this->buffer[] = $row;
if (count($this->buffer) >= $this->batchSize) {
$this->flush();
}
}
protected function flush() {
foreach ($this->buffer as $row) {
fputcsv($this->handle, $row);
}
$this->buffer = [];
}
}
Override Defaults Globally
Bind a custom CsvRfc instance in the service provider:
$this->app->bind('csv-rfc', function () {
return new \Ajgl\CsvRfc\CsvRfc(';', '"', '\\', "\r\n");
});
Laravel Facades Create a facade for convenience:
// app/Facades/Csv.php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class Csv extends Facade {
protected static function getFacadeAccessor() { return 'csv-rfc'; }
}
Then use:
use App\Facades\Csv;
Csv::putcsv($file, $data);
How can I help you explore Laravel packages today?