jgrygierek/batch-entity-import-bundle
A Symfony bundle for importing large sets of Doctrine entities in batches from files, with configurable field mapping and validation to keep memory use low and imports reliable.
Reality check first: despite the framing, jgrygierek/batch-entity-import-bundle is a Symfony bundle, not a Laravel package. It ships no service provider, no facade, and no config/ publish tag. It expects Symfony's DI container, Doctrine ORM entities, and (typically) Twig templates for the preview/edit UI. If you're on Laravel, you cannot composer require it into an app and expect it to boot — you either use it inside a Symfony service in a polyglot setup, or you read its source as a reference implementation and rebuild the same flow with maatwebsite/excel + a staging table + Filament/Livewire.
Assuming a Symfony context:
composer require jgrygierek/batch-entity-import-bundle — Flex should register it in config/bundles.php automatically; otherwise add it manually.src/ for the importer service and the attribute/annotation directory — that's where column mapping, required flags, and transformers are declared.tests/ for the canonical end-to-end usage; with a package this small (12 stars, 0 dependents) the tests are the most reliable documentation of the actual API surface.fgetcsv + flush() loop.The canonical wiring is a two-route flow: POST /import/upload parses the file into transient entity objects and renders them in an editable table; POST /import/confirm takes the (possibly edited) rows and persists them.
Upload → preview → edit → commit. Treat the preview step as the contract. Keep parsed rows in the session (or a temporary table for large files), re-validate on confirm, and persist only what the user approved. Never let the upload endpoint write to the DB directly.
Declare the schema on the entity, not in the controller. The bundle's model is to annotate/attribute the Doctrine entity's properties with column metadata (header name, required, transformer, validator constraints). This keeps the import definition next to the field it describes and lets the same config drive both parsing and preview rendering.
// Illustrative shape — confirm exact attribute names in src/
#[Importable(header: 'sku', required: true)]
private string $sku;
#[Importable(header: 'price', transformer: PriceTransformer::class)]
private int $priceCents;
Reuse the Symfony Validator. The preview objects are just entity instances, so $validator->validate($entity) works unchanged. Surface errors on the preview row so the user fixes them in place rather than re-uploading.
Resolve relations by natural key, with a cache. For ManyToOne columns (category name, supplier code), pre-load a lookup map once per request ($repo->createQueryBuilder()->indexBy(...)) rather than querying per row. This is the single biggest performance lever on a 10k-row file.
Batch the flush. EntityManager::flush() per row will crawl. persist() everything, flush() + clear() every 100–500 rows, and wrap the confirm step in a transaction so a mid-file failure doesn't leave half-imported data.
Laravel-side equivalent if you're porting the pattern: a POST /imports that stores the file and creates an import_batches row, an import_rows staging table with status (pending/valid/invalid/committed), a Livewire or Filament table for the edit step, and a queued job that maps staging rows onto Eloquent models inside DB::transaction(). The bundle's two-phase design maps cleanly onto this.
composer require with a pinned version, or fork) and be prepared to own the code. Read src/ end-to-end before building on it — it's small enough to audit in an afternoon.CHANGELOG on GitHub before assuming a stable API. Pin to a commit SHA if the tag history is unclear.\xEF\xBB\xBF) are the usual first-day bugs. Strip the BOM, trim() headers, and normalize before matching against your declared import keys.clear(). If you clear(), remember your relation lookup cache of managed entities goes stale — cache IDs or use getReference().var_dump at the parse boundary will outpace guessing from the README.How can I help you explore Laravel packages today?