Installation:
composer require handcraftedinthealps/goodby-csv
Ensure your composer.json includes "require": {"php": "^8.1"}.
First Use Case: Import a CSV file line-by-line into a database:
use Goodby\CSV\Import\Standard\Lexer;
use Goodby\CSV\Import\Standard\Interpreter;
use Goodby\CSV\Import\Standard\LexerConfig;
$lexer = new Lexer(new LexerConfig());
$interpreter = new Interpreter();
$interpreter->addObserver(function(array $row) {
// Process each row (e.g., save to DB)
DB::table('users')->insert($row);
});
$lexer->parse('users.csv', $interpreter);
Where to Look First:
Import/Standard/ and Export/Standard/ directories: Core classes for CSV handling.Use the Lexer + Interpreter pattern to process large files without loading them entirely into memory:
$lexer = new Lexer(new LexerConfig());
$interpreter = new Interpreter();
$interpreter->addObserver(function(array $row) {
// Process row (e.g., queue job, log, or save)
dispatch(new ProcessCsvRow($row));
});
$lexer->parse(storage_path('app/large_file.csv'), $interpreter);
Export collections (arrays, PDO results, or custom objects) to CSV:
use Goodby\CSV\Export\Standard\Exporter;
use Goodby\CSV\Export\Standard\ExporterConfig;
$config = new ExporterConfig();
$config->setDelimiter("\t"); // TSV output
$exporter = new Exporter($config);
$exporter->export('php://output', User::all()->toArray());
Import:
$interpreter->addObserver(function(array $row) {
User::create([
'name' => $row[0],
'email' => $row[1],
]);
});
Export:
$exporter->export('php://output', new PdoCollection(
DB::connection()->prepare("SELECT * FROM users")
));
Stream CSV directly to a response:
return response()->stream(function() {
$exporter = new Exporter(new ExporterConfig());
$exporter->export('php://output', User::all()->toArray());
}, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="users.csv"',
]);
Transform data before export using CallbackCollection:
$collection = new CallbackCollection(User::all()->toArray(), function($row) {
$row['full_name'] = $row['first_name'] . ' ' . $row['last_name'];
return $row;
});
$exporter->export('php://output', $collection);
Laravel Service Provider: Bind the package for dependency injection:
$this->app->bind(Exporter::class, function() {
return new Exporter(new ExporterConfig());
});
Artisan Commands: Create a CSV import/export command:
class ImportCsvCommand extends Command
{
protected $signature = 'csv:import {file}';
protected $description = 'Import CSV file into database';
public function handle()
{
$lexer = new Lexer(new LexerConfig());
$interpreter = new Interpreter();
$interpreter->addObserver([$this, 'processRow']);
$lexer->parse($this->argument('file'), $interpreter);
}
public function processRow(array $row) { /* ... */ }
}
Queue Jobs: Offload CSV processing to queues for large files:
$interpreter->addObserver(function(array $row) {
ProcessCsvRowJob::dispatch($row);
});
Validation: Validate CSV rows before processing:
$interpreter->addObserver(function(array $row) {
$validator = Validator::make($row, [
'email' => 'required|email',
]);
if ($validator->fails()) {
Log::error("Invalid row: " . json_encode($row));
return;
}
// Proceed with valid row
});
Memory Leaks:
php://output or ensure streams are properly closed after export.$exporter->export('php://temp', $data); // Use temp streams for large exports
Character Encoding:
fromCharset and toCharset in configs:
$config->setFromCharset('ISO-8859-1')->setToCharset('UTF-8');
Row Consistency:
StrictViolationException for inconsistent row lengths.Interpreter::unstrict() if the CSV has irregular rows:
$interpreter->unstrict();
Large Files:
$lexer->parse($file, $interpreter, 1000); // Process 1000 rows at a time
Delimiter Confusion:
$config->setDelimiter("\t"); // For TSV
Log Rows: Add a debug observer to log problematic rows:
$interpreter->addObserver(function(array $row) {
Log::debug('Row:', $row);
});
Validate Config: Ensure configs are applied before parsing:
$config = new LexerConfig();
$config->setDelimiter(';'); // Test with semicolon-delimited files
Test with Small Files: Use a 2-3 row CSV to verify configs before processing large files.
Custom Lexers/Interpreters:
Extend Lexer or Interpreter for domain-specific parsing:
class CustomLexer extends Lexer {
public function parse($file, Interpreter $interpreter, $chunkSize = null) {
// Custom logic (e.g., skip headers)
parent::parse($file, $interpreter, $chunkSize);
}
}
Observer Chaining: Chain observers for multi-step processing:
$interpreter->addObserver(function(array $row) {
$row['processed'] = true;
return $row; // Pass to next observer
});
Event Dispatching: Use Laravel events for decoupled processing:
$interpreter->addObserver(function(array $row) {
event(new CsvRowProcessed($row));
});
Default Values:
, (comma)" (double quote)\ (backslash)null (no conversion)File Modes:
ExporterConfig::setFileMode(CsvFileObject::FILE_MODE_APPEND) for appending to files.Performance:
php://temp for large datasets to avoid memory issues:
$exporter->export('php://temp', $data);
file_put_contents('large_export.csv', $tempStream);
CSV Injection:
Interpreter::unstrict() cautiously—it may expose your app to malformed data.File Handling:
$file
How can I help you explore Laravel packages today?