Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Easy Csv Laravel Package

jwage/easy-csv

Easy CSV is a lightweight PHP library for reading and writing CSV files. It wraps league/csv with a simple API, letting you parse rows into associative arrays keyed by header and write arrays back to CSV with minimal code. Ideal for imports, exports, and data migrations.

View on GitHub
Deep Wiki
Context7

Getting Started

EasyCSV is two classes and no dependencies: EasyCSV\Reader and EasyCSV\Writer. That is the whole surface area, so there is nothing to configure, publish, or register.

composer require jwage/easy-csv

The package is archived (last release April 2019, targets PHP 7.2+). It still runs fine on modern PHP in practice because it is a thin wrapper over fgetcsv/fputcsv, but treat it as vendored code you own rather than a maintained dependency. Before reaching for it, ask whether league/csv (already an indirect dependency in many Laravel stacks) covers the same ground — for anything beyond trivial read/write, it does.

First use case: a one-off import. Put the file somewhere Laravel can see it and iterate rows:

use EasyCSV\Reader;

$reader = new Reader(storage_path('app/imports/users.csv'));

while (($row = $reader->getRow()) !== false) {
    // $row is an associative array keyed by the header row
    User::create([
        'name'  => $row['name'],
        'email' => $row['email'],
    ]);
}

Look first at the header behaviour: Reader consumes the first line as headers and combines it with every subsequent row, so $row['email'] works out of the box. getHeaders() returns them if you need to introspect; getAll() slurps the whole file into an array (handy in tests and tinker, dangerous in production).

Implementation Patterns

Wrap it in a service, not in a controller. The classes have no Laravel awareness, so the useful work is the mapping layer around them.

namespace App\Services;

use EasyCSV\Reader;

class UserCsvImporter
{
    public function import(string $path): int
    {
        $reader = new Reader($path);
        $count = 0;

        while (($row = $reader->getRow()) !== false) {
            if (! $this->isValid($row)) {
                continue; // or collect into an error bag
            }

            User::updateOrCreate(
                ['email' => $row['email']],
                ['name' => $row['name']]
            );

            $count++;
        }

        return $count;
    }
}

Streaming loop over getAll(). getAll() holds every row in memory; a 200k-row file will hurt. The while loop above is constant-memory and is the pattern you should default to for anything user-uploaded.

Export side with Writer. The writer accepts associative rows and emits the header line from the first row's keys:

use EasyCSV\Writer;

$writer = new Writer(storage_path('app/exports/users.csv'));
$writer->writeRow(['id' => 1, 'name' => 'Jeremy', 'email' => '[email protected]']);
$writer->writeRows($users->map->only(['id', 'name', 'email'])->all());
unset($writer); // releases the handle

Because the header line is derived from the first row, be deliberate: write one explicit header row first if you want stable column order regardless of data.

Artisan commands are the natural home. A php artisan users:import {path} command gives you progress output, exit codes, and a place to wrap the loop in a transaction or a batch counter. For long imports, dispatch a job per file and let the queue handle retries — but chunk the loop yourself (e.g. DB::transaction every 500 rows) since EasyCSV gives you no batching.

Use it in tests as a fixture helper. Building a CSV in a test with Writer is less noise than hand-writing heredocs, and getAll() on a small fixture is perfectly safe:

Storage::fake('local');
(new Writer(Storage::disk('local')->path('t.csv')))->writeRows([...]);
$this->artisan('users:import', ['path' => Storage::disk('local')->path('t.csv')]);

Bind it for consistency. If you use it in several places, register a small CsvFactory in a service provider that returns configured Reader/Writer instances — it makes it a one-line swap to league/csv later, which is exactly the exit strategy an archived package deserves.

Gotchas and Tips

  • The archived status is the top risk. No PHP 8.x/9.x testing, no security response. Pin the version, vendor a fork if you need a fix, and do not let it spread into new code paths. A 40-line fgetcsv helper is a credible replacement.
  • BOM handling is absent. Files exported from Excel often start with a UTF-8 BOM, which becomes part of your first header name ("\xEF\xBB\xBFemail"), silently breaking $row['email']. Strip it before constructing the Reader or from the first header key.
  • while (($row = $reader->getRow()) !== false) — strict comparison matters. An empty trailing line can surface as [null] or [''] rather than false; guard with if (empty(array_filter($row, fn($v) => $v !== null && $v !== ''))) continue;.
  • No header/row count validation. Ragged rows and duplicate header names produce surprising array_combine results rather than a clear exception. Validate the header row yourself against an expected column list before trusting the output.
  • Constructor argument order is ($filePath, $delimiter = ',', $enclosure = '"', $escape = '\\'). Tab- or semicolon-delimited files need the second argument; there is no named-option array, so positional precision matters.
  • Writer truncates on open. The target file is opened in write mode at construction — a crash mid-export leaves a partial file and no atomic rename. Write to a temp path and rename() on success if anyone depends on the artifact.
  • Encoding is not your job here. No UTF-8 conversion, no Excel-friendly BOM emission. If the file lands in Excel, prepend \xEF\xBB\xBF yourself; if the input is Windows-1252, mb_convert_encoding the rows before mapping.
  • Error handling is thin. Missing/unreadable files fail at construction with a generic exception. Wrap the constructor in a try/catch and rethrow as something your app understands (ValidationException, a domain exception) so callers get a useful message.
  • Extension points are essentially zero. No events, no row filters, no callbacks. Anything you want (skipping rows, trimming, casting, progress bars) goes in your wrapper — which is another argument for writing that wrapper before the package is used in three places.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons