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

Importer Laravel Package

kunicmarko/importer

Kunicmarko Importer is a lightweight PHP package for bringing structured data into your application. It offers a simple, extensible API for defining import sources, mapping fields, and processing rows, so you can add CSV or spreadsheet imports to Laravel projects with minimal boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require kunicmarko/importer

Expect composer resolution to be the first obstacle, not the code. The last release was July 2018, so the php and illuminate/* constraints will likely refuse to install on PHP 8.1+/Laravel 10+. If you're on a modern stack, fork the repo into app/Support/Importer (the surface is small) or a private Composer package with widened constraints — do not fight --ignore-platform-reqs on a dependency you intend to keep.

There is no service provider and no auto-discovery. The package is framework-agnostic, so nothing appears in config/ or bootstrap/providers.php. You construct the pieces yourself:

use KunicMarko\Importer\Importer;

$importer = new Importer();

$importer->import(storage_path('app/imports/contacts.csv'), function (array $row) {
    // $row is the parsed line, as an array of scalars
});

Read these three things before writing any code — the README is the only documentation that exists, and it is thin:

  1. src/Importer.php — the public entry point and how it dispatches on file type.
  2. src/Reader/ — the concrete readers (CSV/JSON/…) and the interface they share. This is the actual contract you'll depend on.
  3. tests/ — the fastest way to see exact call signatures, supported extensions, header behaviour, and edge cases. Verify every signature above against the source before copying it; this package predates most of your conventions.

First real use case: a CSV of contacts or products dropped into storage/app/imports, consumed by an artisan command that inserts rows. Frame the package correctly from day one: it is a file-format dispatcher that gets you from "path on disk" to "array of values per line." It does not map columns to your models, validate, deduplicate, or persist. All of that is your code.

If you need it in more than one place, bind it once:

// app/Providers/AppServiceProvider.php
$this->app->singleton(Importer::class, fn () => new Importer());

Implementation Patterns

Console command is the natural home. Imports are long-running, need visible feedback, and shouldn't block a request. Keep the command thin — parse, iterate, delegate.

class ImportContacts extends Command
{
    protected $signature = 'import:contacts {file} {--dry-run}';

    public function handle(Importer $importer): int
    {
        $path = $this->argument('file');
        abort_unless(is_readable($path), 1, "Cannot read {$path}");

        $bar = $this->output->createProgressBar();
        $errors = [];
        $line = 0;

        DB::transaction(function () use ($importer, $path, &$errors, &$line, $bar) {
            $importer->import($path, function (array $row) use (&$errors, &$line, $bar) {
                $line++;
                try {
                    Contact::updateOrCreate(
                        ['email' => trim($row[0])],
                        ['name' => trim($row[1])],
                    );
                } catch (Throwable $e) {
                    $errors[$line] = $e->getMessage();
                }
                $bar->advance();
            });
        });

        $bar->finish();
        // report $errors
    }
}

Wrap it behind a first-party interface immediately. This is the single highest-value pattern in this whole assessment: the package is archived, and the day you replace it should be a one-class change.

// app/Contracts/FileImporter.php
interface FileImporter {
    /** @return iterable<array> */
    public function rows(string $path): iterable;
}

Implement it with kunicmarko/importer today, with league/csv tomorrow. Every call site depends on FileImporter, never on KunicMarko\Importer\Importer.

Column mapping belongs in a dedicated class, not the closure. Keep the row→DTO transform testable in isolation from file I/O:

final class ContactRowMapper
{
    public function __construct(private array $columns) {}

    public function map(array $row): ContactData
    {
        return new ContactData(
            email: trim($row[$this->columns['email']] ?? ''),
            name:  trim($row[$this->columns['name']]  ?? ''),
        );
    }
}

Test the mapper against plain arrays — no filesystem, no fixtures — and keep the file-reading tests to one or two integration cases.

Header handling is yours. If the CSV has a header row, you either skip it explicitly or resolve column positions from it and pass that map to your mapper. Don't assume the package strips it for you; confirm in src/Reader/ what actually happens to line 1.

Queue it for anything user-triggered. Wrap the artisan command logic in a job so a web upload enqueues work, the request returns immediately, and a failure doesn't take down a web worker:

dispatch(new ImportContactsJob($path, $userId));

Set $timeout generously, $tries = 1 (retries on a partially-completed import are worse than failure), and $failOnTimeout = true. Because the package has no checkpointing, a re-run means a full re-run — which is why the upsert key in the example above matters more than the parsing does.

Batching and transactions. If the reader materializes the whole file anyway (likely — check src/Reader/), you gain nothing from chunking reads; chunk writes instead to keep memory flat and the transaction short: array_chunk($rows, 500) and one insert() per chunk, or ->chunk() inside the transaction for Eloquent. Note that a wrapping DB::transaction around a 100k-row import will hold locks for the whole run — prefer explicit commit-per-chunk for large files and accept partial imports, reporting exactly which line failed.

Testing. The package's own tests/ directory is your best fixture source. Copy the small pattern into your app: put a 3-line CSV in tests/Fixtures/, assert your mapper and your command produce the expected rows. The valuable tests are the ones around your mapper and your persistence logic, not the ones around the library.

Gotchas and Tips

  • Archived and unmaintained since 2018. Zero dependents, no security feed, no issue history to mine. Assume you own it from day one. Fork, pin, and add a CI matrix on your actual PHP/Laravel versions — the transitive 2018-era dependencies are more likely to break than the package's own code.
  • Memory ceiling is the binding constraint. Confirm whether the CSV reader streams (SplFileObject/fgetcsv) or materializes the file. If it materializes, your practical ceiling is tens of megabytes, not hundreds. Measure peak memory on one representative production file before wiring anything into a customer flow; memory_get_peak_usage() in an artisan command is enough.
  • PHP 8 deprecation notices can escalate to fatals. The code is PHP-7 era. Under a strict error handler (e.g. Laravel's or PHPUnit's conversion of notices/warnings to exceptions), unrelated deprecations can abort an import mid-run. Test on your real runtime before trusting it, and consider a scoped error_reporting() around the read loop if the fork isn't worth trimming.
  • No transaction boundary, no rollback, no idempotency. Everything in the "Implementation Patterns" section about DB::transaction and updateOrCreate exists because the package provides none of it. A half-finished import is your responsibility to detect and clean up.
  • Row errors abort or vanish — there is no per-row error accumulation hook. Determine which it is (exception on first bad row, or silent skip) from src/ and tests/. Silent skips are the dangerous case: your row counts won't reconcile and you'll hear about it from the customer, not the logs. Wrap the per-row callback in try/catch and record line numbers yourself.
  • Encoding and delimiter are not magic. UTF-8 BOM will corrupt your first column name (\u{FEFF}email). Semicolon-delimited European exports and UTF-16 exports will parse into a single column of garbage without erroring. Strip the BOM on the first row, and make the delimiter configurable rather than assuming ,.
  • Ragged rows. A row with fewer columns than expected yields missing array keys, not an exception. Use ?? defaults in the mapper and validate after mapping, never before — a null propagating into a NOT NULL column gives you a database error with no line number attached.
  • Files under storage/ need absolute paths. storage_path('app/imports/x.csv') or a Storage::disk('local')->path(...). Passing a relative path or an UploadedFile object straight in will fail obscurely — check the reader's signature for the exact type it accepts.
  • Extension dispatch is the actual value. If you're only ever reading CSV, the package buys you very little over fgetcsv(). Its real justification is routing .csv/.json/.xml to different readers behind one call. If you don't have that requirement, don't adopt it.
  • Extension points worth knowing: custom readers (implement the reader interface and register/construct it) and converters (row array → your object). These are clean seams for your own code — but note that a Converter is still just a callable-shaped transform, and putting your domain logic there couples it to a dependency you're likely to replace. Prefer your own mapper class; keep the library confined to reading.
  • Set a timeout and a memory limit deliberately. php artisan import:contacts ... run from cron inherits CLI max_execution_time of 0 and whatever memory_limit your CLI ini has. Pin both explicitly on the job/command so a bad file fails fast instead of OOM-killing the box.
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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource