{"id":15045067,"url":"https://github.com/jgrygierek/batchentityimportbundle","last_synced_at":"2025-05-05T22:21:32.940Z","repository":{"id":39645980,"uuid":"255152784","full_name":"jgrygierek/BatchEntityImportBundle","owner":"jgrygierek","description":"Importing entities with preview and edit features for Symfony.","archived":false,"fork":false,"pushed_at":"2025-04-23T10:51:29.000Z","size":301,"stargazers_count":10,"open_issues_count":1,"forks_count":6,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-23T11:37:01.273Z","etag":null,"topics":["csv-import","php","symfony","symfony-bundle"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jgrygierek.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-04-12T19:14:28.000Z","updated_at":"2025-04-23T10:50:48.000Z","dependencies_parsed_at":"2023-12-01T18:31:47.626Z","dependency_job_id":"08baf08c-7798-404f-b6e7-bbb52d2014af","html_url":"https://github.com/jgrygierek/BatchEntityImportBundle","commit_stats":{"total_commits":128,"total_committers":5,"mean_commits":25.6,"dds":0.140625,"last_synced_commit":"02a010a4ef4273a4d01a70edfb4a19893c3d3c37"},"previous_names":[],"tags_count":37,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jgrygierek%2FBatchEntityImportBundle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jgrygierek%2FBatchEntityImportBundle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jgrygierek%2FBatchEntityImportBundle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jgrygierek%2FBatchEntityImportBundle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jgrygierek","download_url":"https://codeload.github.com/jgrygierek/BatchEntityImportBundle/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252584578,"owners_count":21771986,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csv-import","php","symfony","symfony-bundle"],"created_at":"2024-09-24T20:51:24.969Z","updated_at":"2025-05-05T22:21:32.925Z","avatar_url":"https://github.com/jgrygierek.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# BatchEntityImportBundle\n\n![Code Style](https://github.com/jgrygierek/BatchEntityImportBundle/workflows/Code%20Style/badge.svg)\n![Tests](https://github.com/jgrygierek/BatchEntityImportBundle/workflows/Tests/badge.svg)\n![Code Coverage](https://img.shields.io/codecov/c/github/jgrygierek/BatchEntityImportBundle/master)\n![PHP Versions](https://img.shields.io/badge/PHP-\u003e=8.1-blue)\n![Symfony Versions](https://img.shields.io/badge/Symfony-5.4--7.*-blue)\n[![SymfonyInsight](https://insight.symfony.com/projects/ad63558e-3612-434f-a93d-0fc5fce2dd20/mini.svg)](https://insight.symfony.com/projects/ad63558e-3612-434f-a93d-0fc5fce2dd20)\n\nImporting entities with preview and edit features for Symfony.\n\n* Data can be **viewed and edited** before saving to database.\n* Supports **inserting** new records and **updating** existing ones.\n* Supported extensions: **CSV, XLS, XLSX, ODS**.\n* Supports translations from **KnpLabs Translatable** extension.\n* The code is divided into smaller methods that can be easily replaced if you want to change something.\n* Columns names are required and should be added as header (first row).\n* If column does not have name provided, will be removed from loaded data.\n\n![Select File](docs/select_file.png)\n![Edit Matrix](docs/edit_matrix.png)\n\n## Documentation\n* [Installation](#installation)\n* [Configuration class](#configuration-class)\n  * [Basic configuration class](#basic-configuration-class)\n  * [Fields definitions](#fields-definitions)\n  * [Matrix validation](#matrix-validation)\n  * [Passing services to configuration class](#passing-services-to-configuration-class)\n  * [Show \u0026 hide entity override column](#show--hide-entity-override-column)\n  * [Set allowed file extensions](#set-allowed-file-extensions)\n  * [Optimizing queries](#optimizing-queries)\n* [Creating controller](#creating-controller)\n* [Events](#events)\n* [Translations](#translations)\n* [Overriding templates](#overriding-templates)\n    * [Global templates](#global-templates)\n    * [Controller-specific templates](#controller-specific-templates)\n    * [Main layout](#main-layout)\n    * [Additional data](#additional-data)\n* [Updating entities](#updating-entities)\n* [Importing data to array field](#importing-data-to-array-field)\n* [Full example of CSV file](#full-example-of-csv-file)\n\n## Installation\n\nInstall package via composer:\n\n```\ncomposer require jgrygierek/batch-entity-import-bundle\n```\n\nAdd entry to `bundles.php` file:\n\n```\nJG\\BatchEntityImportBundle\\BatchEntityImportBundle::class =\u003e ['all' =\u003e true],\n```\n\n## Configuration class\n\nTo define how the import function should work, you need to create a configuration class.\n\n### Basic configuration class\n\nIn the simplest case it will contain only class of used entity.\n\n```php\nnamespace App\\Model\\ImportConfiguration;\n\nuse App\\Entity\\User;\nuse JG\\BatchEntityImportBundle\\Model\\Configuration\\AbstractImportConfiguration;\n\nclass UserImportConfiguration extends AbstractImportConfiguration\n{\n    public function getEntityClassName(): string\n    {\n        return User::class;\n    }\n}\n```\n\nThen register it as a service:\n\n```yaml\nservices:\n  App\\Model\\ImportConfiguration\\UserImportConfiguration: ~\n```\n\n### Fields definitions\n\nIf you want to change types of rendered fields, instead of using default ones,\nyou have to override method in your import configuration. If name of field contains spaces, you should use underscores instead.\n\nTo avoid errors during data import, you can add here validation rules.\n\n```php\n\nuse JG\\BatchEntityImportBundle\\Model\\Form\\FormFieldDefinition;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\IntegerType;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\TextareaType;\nuse Symfony\\Component\\Form\\Extension\\Core\\Type\\TextType;\nuse Symfony\\Component\\Validator\\Constraints\\Length;\n\npublic function getFieldsDefinitions(): array\n{\n    return [\n        'age' =\u003e new FormFieldDefinition(\n            IntegerType::class,\n            [\n                'attr' =\u003e [\n                    'min' =\u003e 0,\n                    'max' =\u003e 999,\n                ],\n            ]\n        ),\n        'name' =\u003e new FormFieldDefinition(TextType::class),\n        'description' =\u003e new FormFieldDefinition(\n            TextareaType::class,\n            [\n                'attr' =\u003e [\n                    'rows' =\u003e 2,\n                ],\n                'constraints' =\u003e [new Length(['max' =\u003e 255])],\n            ]\n        ),\n    ];\n}\n```\n\n### Matrix validation\n\nThis bundle provides two new validators.\n\n1) **DatabaseEntityUnique** validator can be used to check if record data does not exist yet in database.\n2) **MatrixRecordUnique** validator can be used to check duplication without checking database, just only matrix records values.\n\nNames of fields should be the same as names of columns in your uploaded file. With one exception! If name contains spaces, you should use underscores instead.\n\n```php\nuse JG\\BatchEntityImportBundle\\Validator\\Constraints\\DatabaseEntityUnique;\nuse JG\\BatchEntityImportBundle\\Validator\\Constraints\\MatrixRecordUnique;\n\npublic function getMatrixConstraints(): array\n{\n    return [\n        new MatrixRecordUnique(['fields' =\u003e ['field_name']]),\n        new DatabaseEntityUnique(['entityClassName' =\u003e $this-\u003egetEntityClassName(), 'fields' =\u003e ['field_name']]),\n    ];\n}\n```\n\n### Passing services to configuration class\n\nIf you want to pass some additional services to your configuration, just override constructor.\n\n```php\npublic function __construct(EntityManagerInterface $em, EventDispatcherInterface $eventDispatcher, TestService $service)\n{\n    parent::__construct($em, $eventDispatcher);\n\n    $this-\u003etestService = $service;\n}\n```\n\n### Show \u0026 hide entity override column\n\nIf you want to hide/show an entity column that allows you to override entity `default: true`,\nyou have to override this method in your import configuration.\n\n```php\npublic function allowOverrideEntity(): bool\n{\n    return true;\n}\n```\n\n### Set allowed file extensions\n\nBy default, allowed file extensions are set to `'csv', 'xls', 'xlsx', 'ods'`.\nHowever, if you want to change it, you can override this method in your import configuration.\n\n```php\n  public function getAllowedFileExtensions(): array\n  {\n      return ['csv', 'xls', 'xlsx', 'ods'];\n  }\n```\n\n### Optimizing queries\n\nIf you use **KnpLabs Translatable** extension for your entity, probably you will notice increased number of queries, because of Lazy Loading.\n\nTo optimize this, you can use `getEntityTranslationRelationName()` method to pass the relation name to the translation.\n\n```php\npublic function getEntityTranslationRelationName(): ?string\n{\n    return 'translations';\n}\n```\n\n## Creating controller\n\nCreate controller with some required code.\n\nThis is just an example, depending on your needs you can inject services in different ways.\n\nTo enable automatic passing configuration service to your controller, please use `ImportConfigurationAutoInjectInterface` and `ImportConfigurationAutoInjectTrait`.\n\n```php\nnamespace App\\Controller;\n\nuse App\\Model\\ImportConfiguration\\UserImportConfiguration;\nuse JG\\BatchEntityImportBundle\\Controller\\ImportConfigurationAutoInjectInterface;\nuse JG\\BatchEntityImportBundle\\Controller\\ImportConfigurationAutoInjectTrait;\nuse JG\\BatchEntityImportBundle\\Controller\\ImportControllerTrait;\nuse Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController;\nuse Symfony\\Component\\HttpFoundation\\RedirectResponse;\nuse Symfony\\Component\\HttpFoundation\\Request;\nuse Symfony\\Component\\HttpFoundation\\Response;\nuse Symfony\\Component\\Routing\\Annotation\\Route;\nuse Symfony\\Component\\Validator\\Validator\\ValidatorInterface;\nuse Symfony\\Contracts\\Translation\\TranslatorInterface;\n\nclass ImportController extends AbstractController implements ImportConfigurationAutoInjectInterface\n{\n    use ImportControllerTrait;\n    use ImportConfigurationAutoInjectTrait;\n\n    /**\n     * @Route(\"/user/import\", name=\"user_import\")\n     */\n    public function import(Request $request, ValidatorInterface $validator): Response\n    {\n        return $this-\u003edoImport($request, $validator);\n    }\n\n    /**\n     * @Route(\"/user/import/save\", name=\"user_import_save\")\n     */\n    public function importSave(Request $request, TranslatorInterface $translator): Response\n    {\n        return $this-\u003edoImportSave($request, $translator);\n    }\n\n    protected function redirectToImport(): RedirectResponse\n    {\n       return $this-\u003eredirectToRoute('user_import');\n    }\n\n    protected function getMatrixSaveActionUrl(): string\n    {\n       return $this-\u003egenerateUrl('user_import_save');\n    }\n\n    protected function getImportConfigurationClassName(): string\n    {\n       return UserImportConfiguration::class;\n    }\n}\n```\n\n## Events\n\n### RecordImportedSuccessfullyEvent\n\nFor each successfully processed record, event `RecordImportedSuccessfullyEvent` is dispatched. This event contains two fields:\n- entity class name\n- entity id\n\n```php\nclass RecordImportedSuccessfullyEvent\n{\n    public function __construct(readonly public string $class, readonly public string $id)\n    {\n    }\n}\n```\n\n## Translations\n\nThis bundle supports KnpLabs Translatable behavior.\n\nTo use this feature, every column with translatable values should be suffixed with locale, for example:\n* `name:en`\n* `description:pl`\n* `title:ru`\n\nIf suffix will be added to non-translatable entity, field will be skipped.\n\nIf suffix will be added to translatable entity, but field will not be found in translation class, field will be skipped.\n\n## Overriding templates\n\n#### Global templates\n\nYou have two ways to override templates globally:\n\n- **Configuration** - just change paths to templates in your configuration file. \nValues in this example are default ones and will be used if nothing will be change.\n\n```yaml\nbatch_entity_import:\n    templates:\n        select_file: '@BatchEntityImport/select_file.html.twig'\n        edit_matrix: '@BatchEntityImport/edit_matrix.html.twig'\n        layout: '@BatchEntityImport/layout.html.twig'\n```\n\n- **Bundle directory** - put your templates in this directory:\n\n```\ntemplates/bundles/BatchEntityImportBundle\n```\n\n#### Controller-specific templates\n\nIf you have controller-specific templates, you can override them in controller:\n\n```php\nprotected function getSelectFileTemplateName(): string\n{\n    return 'your/path/to/select_file.html.twig';\n}\n\nprotected function getMatrixEditTemplateName(): string\n{\n    return 'your/path/to/edit_matrix.html.twig';\n}\n```\n\n#### Main layout\n\nBlock name used in templates is `batch_entity_import_content`, so probably there will be need to override it a bit.\nYou can create a new file with content similar to the given example. Then just use it instead of original layout file.\n\n```twig\n{% extends path/to/your/layout.html.twig %}\n\n{% block your_real_block_name %}\n    {% block batch_entity_import_content %}{% endblock %}\n{% endblock %}\n```\n\nThen you just have to override it in bundle directory, or change a path to layout in your configuration.\n\n#### Additional data\n\nIf you want to add some specific data to the rendered view, just override these methods in your controller:\n\n```php\nprotected function prepareSelectFileView(FormInterface $form): Response\n{\n    return $this-\u003eprepareView(\n        $this-\u003egetSelectFileTemplateName(),\n        [\n            'form' =\u003e $form-\u003ecreateView(),\n        ]\n    );\n}\n\nprotected function prepareMatrixEditView(FormInterface $form, Matrix $matrix, bool $manualSubmit = false): Response\n{\n    if ($manualSubmit) {\n        $this-\u003emanualSubmitMatrixForm($form, $matrix);\n    }\n\n    $configuration = $this-\u003egetImportConfiguration();\n\n    return $this-\u003eprepareView(\n        $this-\u003egetMatrixEditTemplateName(),\n        [\n            'header_info' =\u003e $matrix-\u003egetHeaderInfo($configuration-\u003egetEntityClassName()),\n            'data' =\u003e $matrix-\u003egetRecords(),\n            'form' =\u003e $form-\u003ecreateView(),\n            'importConfiguration' =\u003e $configuration,\n        ]\n    );\n}\n```\n\n## Updating entities\n\nIf you want to update your entities:\n- Set `allowOverrideEntity` to `true` in your import configuration file. \n- Then in your import file:\n  - Add `entity_id` in header and:\n    - Add entity ID to row\n    - Leave it empty (if you want to set it manually or import it as new record)\n  - Or if you don't want to add `entity_id` header, you can still manually set each entity to override.\n\n#### CSV file\n\n```csv\nentity_id,user_name\n2,user_1\n,user_2\n10,user_3\n```\n\n## Importing data to array field\n\nIf your entity has an array field, and you want to import data from CSV file to it, this is how you can do it.\n\n- Default separator is set to `|`.\n- Only one-dimensional arrays are allowed.\n- Keys are not allowed.\n- **IMPORTANT!** There are limitations:\n  - There is no possibility to import array with one empty element, for example:\n    - ['']\n    - [null]\n  - But arrays with at least 2 such elements are allowed:\n    - ['', '']\n    - [null, null]\n    - ['', null]\n  - It is due of mapping CSV data to array:\n    - Empty value in CSV is equal to `[]` \n    - If we have default separator, `|` value in CSV is equal to `['', '']`\n\n#### Entity\n\n- Allowed entity field types:\n  - `json`\n  - `array`\n  - `simple_array`\n\n```php\nnamespace App\\Entity;\n\nuse Doctrine\\ORM\\Mapping as ORM;\n\n#[ORM\\Entity]\nclass User\n{\n    #[ORM\\Column(type: 'json']\n    private array $roles = [];\n}\n```\n\n#### Import configuration\n\n* You have to add a field definition with a custom `ArrayTextType` type. If you skip this configuration, `TextType` will be used as default.\n* You can set here your own separator.\n\n```php\nuse JG\\BatchEntityImportBundle\\Form\\Type\\ArrayTextType;\nuse JG\\BatchEntityImportBundle\\Model\\Form\\FormFieldDefinition;\n\npublic function getFieldsDefinitions(): array\n{\n    return [\n        'roles' =\u003e new FormFieldDefinition(\n            ArrayTextType::class,\n            [\n                'separator' =\u003e '\u0026',\n            ]\n        ),\n    ];\n}\n```\n\n#### CSV file\n\n```csv\nuser_name,roles\nuser_1,USER\u0026ADMIN\u0026SUPER_ADMIN\nuser_2,USER\nuser_3,SUPER_ADMIN\n```\n\n\n## Full example of CSV file\n\n```csv\nentity_id,user_name,age,email,roles,country:en,name:pl\n1,user_1,21,user_1@test.com,USER\u0026ADMIN\u0026SUPER_ADMIN,Poland,Polska\n3, user_2,34,user_2@test.com,USER,England,Anglia\n,user_3,56,user_3@test.com,SUPER_ADMIN,Germany,Niemcy\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjgrygierek%2Fbatchentityimportbundle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjgrygierek%2Fbatchentityimportbundle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjgrygierek%2Fbatchentityimportbundle/lists"}