Import and export Doctrine entities from Excel and CSV in Symfony applications.
- Streamed CSV export and CSV/XLSX import and export.
- Ordered, translated headers and downloadable import templates.
- Symfony Form validation and data transformation for every imported row.
- Doctrine metadata conversion for booleans, dates, backed enums, and associations.
- Create, update, and optional delete candidates identified by configured unique fields.
- Structured row errors with explicit flush control in the application.
- Configurable date, boolean, CSV, BOM, and strict-header behavior.
- PHP 8.1 or newer
- Symfony 6.4, 7.x, or 8.x
- Doctrine ORM 3.2 or newer within 3.x
- PhpSpreadsheet 3.10.7+ or 5.9+
Follow the 5-minute quick guide to configure a Doctrine entity, import your first CSV file, and expose a CSV or XLSX export endpoint.
composer require hugoseigle/symfony-import-export-bundleSymfony Flex normally registers bundles. If it does not, add:
// config/bundles.php
return [
HugoSEIGLE\SymfonyImportExportBundle\SymfonyImportExportBundle::class => ['all' => true],
];# config/packages/import_export.yaml
import_export:
date_format: 'Y-m-d'
importers:
App\Entity\Company:
fields: [name, email, active]
unique_fields: [email]Field order is column order. Strict header validation is enabled by default. See installation for all requirements.
Inject ExporterInterface, pass it a Doctrine ORM Query, getter names in column order, a base filename, and a format:
use HugoSEIGLE\SymfonyImportExportBundle\Services\Export\ExporterInterface;
$query = $companyRepository->createQueryBuilder('company')->getQuery();
return $exporter->export(
$query,
['getName', 'getEmail', 'isActive'],
'companies',
ExporterInterface::CSV, // or ExporterInterface::XLSX
);CSV rows stream from Query::toIterable(). XLSX iterates the query but retains workbook cells in memory. See exporting.
Create a Symfony form type containing every configured field, then pass an uploaded .csv or .xlsx file to ImporterInterface:
use App\Entity\Company;
use App\Form\CompanyImportType;
use HugoSEIGLE\SymfonyImportExportBundle\Services\Import\ImporterInterface;
$result = $importer->import(
$uploadedFile,
Company::class,
CompanyImportType::class,
);The result exposes getCreatedEntities(), getUpdatedEntities(), and getDeletedEntities(). The bundle schedules persist() and remove() operations while importing; your application stays in control of transaction boundaries and must call flush(). See importing.
Typical controller flow:
use Doctrine\ORM\EntityManagerInterface;
use HugoSEIGLE\SymfonyImportExportBundle\Services\Import\ImportError;
$result = $importer->import($uploadedFile, Company::class, CompanyImportType::class);
if (!$result->isValid()) {
$errors = array_map(static fn (ImportError $error): array => [
'row' => $error->row,
'field' => $error->field,
'message' => $error->message,
'value' => $error->value,
], $result->getErrors());
// return or display the errors; do not flush
}
$entityManager->flush();use HugoSEIGLE\SymfonyImportExportBundle\Services\Import\ImportError;
if (!$result->isValid()) {
$errors = array_map(static fn (ImportError $error): array => [
'row' => $error->row,
'field' => $error->field,
'message' => $error->message,
'value' => $error->value,
], $result->getErrors());
}Header mismatches stop the import. Row errors accumulate while later rows continue. Call flush() only when your chosen all-or-partial import policy allows it; validation guidance shows the relevant edge cases.
| Operation | CSV | XLSX |
|---|---|---|
| Import | Yes | Yes |
| Export | Yes | Yes |
| Empty import template | Yes | Yes |
Only .csv and .xlsx imports are implemented. CSV is expected to be UTF-8; a first-header BOM is accepted. XLSX formulas are read from stored values without formula evaluation.
Configure global date/boolean formatting, strict headers, CSV delimiter/enclosure/escape/BOM, entity fields, unique fields, and deletion:
import_export:
bool_true: 'yes'
bool_false: 'no'
validate_headers: true
csv:
delimiter: ';'
enclosure: '"'
escape: ''
bom: true
importers:
App\Entity\Company:
fields: [name, email, active]
unique_fields: [email]
allow_delete: trueThe optional deleted column is added only with allow_delete: true. Per-call allowDelete, allowCreate, and allowUpdate flags can further restrict operations. Headers use import_export.<snake_case_name> keys from the application messages translation domain. See customization.
The bundle dispatches no events. Extend behavior through Symfony form constraints and transformers, translations, runtime operation flags, configuration, or service decoration. MethodToSnakeInterface, ExporterInterface, ImporterInterface, and ImporterTemplateInterface are autowireable.
Generate a translated empty file with:
return $templates->getImportTemplate(Company::class, ImporterInterface::XLSX);The demo/ directory is a complete Symfony application backed by SQLite. It installs this bundle from the parent directory and demonstrates imports, validation feedback, downloadable templates, and CSV/XLSX exports.
cd demo
composer install
composer setup
symfony serveThen open the URL printed by Symfony CLI. A ready-to-import companies.csv file is included. The smaller Company example remains available for copying individual files into an existing application.
| Bundle | PHP | Symfony | Doctrine ORM | PhpSpreadsheet |
|---|---|---|---|---|
| 2.x | >= 8.1 | 6.4 / 7.x / 8.x | >= 3.2, < 4.0 | >= 3.10.7, < 4.0 or >= 5.9, < 6.0 |
Composer also enforces each Symfony release's own PHP requirement. Version 1.x users must follow the upgrade guide, especially for the canonical HugoSEIGLE\SymfonyImportExportBundle namespace and result-based import API.
composer validate --strict
composer dump-autoload --optimize --strict-psr
composer test
composer lint
composer phpstanCI runs supported PHP/Symfony combinations. composer lint is read-only.
Reproducible CLI benchmarks cover CSV/XLSX imports and exports with selectable dataset sizes. See the performance benchmark guide for commands, methodology, environment reporting, and interpretation guidance.
Bug reports and focused pull requests are welcome. Read CONTRIBUTING.md before submitting changes.
Released under the MIT License.
