kzykhys/php-csv-parser
kzykhys/php-csv-parser is a lightweight PHP library for reading and parsing CSV files. It offers a simple API, iterator-based access, configurable delimiters and enclosures, and optional header handling for straightforward CSV import tasks.
Install it as a plain Composer dependency — there is no Laravel service provider or facade to register:
composer require kzykhys/php-csv-parser
Then look first at the package README and vendor/kzykhys/php-csv-parser/src/KzykHys/CsvParser/CsvParser.php. The API is intentionally small: instantiate CsvParser and call parse() with a file path or CSV string. Confirm the exact string-input constant and option setters in the README for the installed version; the stable entry point is the parser instance plus parse().
use KzykHys\CsvParser\CsvParser;
$parser = new CsvParser();
$rows = $parser->parse(storage_path('app/private/imports/users.csv'));
// First row is not special to most CSV parsers; treat headers yourself:
$headers = array_shift($rows);
The first practical use case is a user-uploaded CSV import: validate the upload in a FormRequest, store it under storage/app/private/imports, parse it in a queued job, normalize the header row, validate each data row, and persist with Eloquent. For example:
$path = $request->file('csv')->store('imports');
ImportCsvJob::dispatch($path);
Because the package claims Excel-style support, it is most valuable when the input comes from Excel exports — BOMs, CRLF line endings, quoted commas, and multiline fields are the cases worth testing first.
Wrap the parser in your own service so controllers and jobs do not depend directly on a 2014 utility class. Bind it once and inject the wrapper:
// AppServiceProvider
$this->app->singleton(CsvParser::class, fn () => new CsvParser());
class CsvImporter
{
public function __construct(private CsvParser $parser) {}
public function import(string $absolutePath): array
{
$rows = $this->parser->parse($absolutePath);
$headers = array_map(
fn ($h) => Str::slug(trim($h), '_'),
array_shift($rows)
);
return [$headers, $rows];
}
}
A typical queued import pattern:
class ImportCsvJob implements ShouldQueue
{
public function __construct(public string $path) {}
public function handle(CsvImporter $importer): void
{
[$headers, $rows] = $importer->import(Storage::path($this->path));
$rules = ['email' => 'required|email', 'name' => 'required|string'];
DB::transaction(function () use ($headers, $rows, $rules) {
foreach ($rows as $index => $row) {
if (count($row) !== count($headers)) {
// log or collect as row error
continue;
}
$data = array_combine($headers, $row);
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
// collect errors keyed by $index + 2 (header is row 1)
continue;
}
User::updateOrCreate(
['email' => $data['email']],
$data
);
}
});
}
}
Use updateOrCreate or upsert on a natural key so re-running an import is idempotent. Validate per row rather than trying to validate the whole file at once; include the CSV row number in error messages so users can fix the source file.
For Excel exports from locales that use semicolons, configure the delimiter if the installed version exposes setters or parse options. Do not rely on auto-detection for production imports — pass the delimiter, enclosure, escape, and encoding explicitly based on the source system.
Testing is straightforward with Storage::fake() and fixture files:
Storage::fake('local');
Storage::put('imports/users.csv', "email,name\[email protected],Jane\n");
If the package supports parsing from a string constant, use that in unit tests to avoid temp files; otherwise create fixtures under tests/Fixtures.
Also add an Artisan command around the same service (php artisan csv:import {path}) so imports can be re-run and debugged outside HTTP requests.
league/csv is the safer long-term choice. If you keep this package, isolate it behind your own CsvImporter interface so swapping later is a one-class change.SplFileObject + fgetcsv() for streaming, or chunk at the job level. Do not parse a 100 MB upload synchronously in a web request.ltrim($header, "\xEF\xBB\xBF")) and convert encoding explicitly if needed. Test with real Excel files, not hand-written CSVs.;; tab-separated exports use \t. Configure explicitly. Also verify escape-character behavior across PHP versions — PHP has changed fgetcsv() escape handling in recent releases and can emit deprecation warnings.count($row) === count($headers) before array_combine() — PHP 8 throws a ValueError on mismatched lengths.=, +, -, or @ to avoid formula injection in Excel.How can I help you explore Laravel packages today?