Importing entities with preview and edit features for Symfony.
MIT License
Importing entities with preview and edit features for Symfony.
Install package via composer:
composer require jgrygierek/batch-entity-import-bundle
Add entry to bundles.php
file:
JG\BatchEntityImportBundle\BatchEntityImportBundle::class => ['all' => true],
To define how the import function should work, you need to create a configuration class.
In the simplest case it will contain only class of used entity.
namespace App\Model\ImportConfiguration;
use App\Entity\User;
use JG\BatchEntityImportBundle\Model\Configuration\AbstractImportConfiguration;
class UserImportConfiguration extends AbstractImportConfiguration
{
public function getEntityClassName(): string
{
return User::class;
}
}
Then register it as a service:
services:
App\Model\ImportConfiguration\UserImportConfiguration: ~
If you want to change types of rendered fields, instead of using default ones, you have to override method in your import configuration. If name of field contains spaces, you should use underscores instead.
To avoid errors during data import, you can add here validation rules.
use JG\BatchEntityImportBundle\Model\Form\FormFieldDefinition;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Validator\Constraints\Length;
public function getFieldsDefinitions(): array
{
return [
'age' => new FormFieldDefinition(
IntegerType::class,
[
'attr' => [
'min' => 0,
'max' => 999,
],
]
),
'name' => new FormFieldDefinition(TextType::class),
'description' => new FormFieldDefinition(
TextareaType::class,
[
'attr' => [
'rows' => 2,
],
'constraints' => [new Length(['max' => 255])],
]
),
];
}
This bundle provides two new validators.
Names 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.
use JG\BatchEntityImportBundle\Validator\Constraints\DatabaseEntityUnique;
use JG\BatchEntityImportBundle\Validator\Constraints\MatrixRecordUnique;
public function getMatrixConstraints(): array
{
return [
new MatrixRecordUnique(['fields' => ['field_name']]),
new DatabaseEntityUnique(['entityClassName' => $this->getEntityClassName(), 'fields' => ['field_name']]),
];
}
If you want to pass some additional services to your configuration, just override constructor.
public function __construct(EntityManagerInterface $em, TestService $service)
{
parent::__construct($em);
$this->testService = $service;
}
If you want to hide/show an entity column that allows you to override entity default: true
,
you have to override this method in your import configuration
public function allowOverrideEntity(): bool
{
return true;
}
If you use KnpLabs Translatable extension for your entity, probably you will notice increased number of queries, because of Lazy Loading.
To optimize this, you can use getEntityTranslationRelationName()
method to pass the relation name to the translation.
public function getEntityTranslationRelationName(): ?string
{
return 'translations';
}
Create controller with some required code.
This is just an example, depending on your needs you can inject services in different ways.
To enable automatic passing configuration service to your controller, please use ImportConfigurationAutoInjectInterface
and ImportConfigurationAutoInjectTrait
.
namespace App\Controller;
use App\Model\ImportConfiguration\UserImportConfiguration;
use JG\BatchEntityImportBundle\Controller\ImportConfigurationAutoInjectInterface;
use JG\BatchEntityImportBundle\Controller\ImportConfigurationAutoInjectTrait;
use JG\BatchEntityImportBundle\Controller\ImportControllerTrait;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class ImportController extends AbstractController implements ImportConfigurationAutoInjectInterface
{
use ImportControllerTrait;
use ImportConfigurationAutoInjectTrait;
/**
* @Route("/user/import", name="user_import")
*/
public function import(Request $request, ValidatorInterface $validator): Response
{
return $this->doImport($request, $validator);
}
/**
* @Route("/user/import/save", name="user_import_save")
*/
public function importSave(Request $request, TranslatorInterface $translator): Response
{
return $this->doImportSave($request, $translator);
}
protected function redirectToImport(): RedirectResponse
{
return $this->redirectToRoute('user_import');
}
protected function getMatrixSaveActionUrl(): string
{
return $this->generateUrl('user_import_save');
}
protected function getImportConfigurationClassName(): string
{
return UserImportConfiguration::class;
}
}
This bundle supports KnpLabs Translatable behavior.
To use this feature, every column with translatable values should be suffixed with locale, for example:
name:en
description:pl
title:ru
If suffix will be added to non-translatable entity, field will be skipped.
If suffix will be added to translatable entity, but field will not be found in translation class, field will be skipped.
You have two ways to override templates globally:
batch_entity_import:
templates:
select_file: '@BatchEntityImport/select_file.html.twig'
edit_matrix: '@BatchEntityImport/edit_matrix.html.twig'
layout: '@BatchEntityImport/layout.html.twig'
templates/bundles/BatchEntityImportBundle
If you have controller-specific templates, you can override them in controller:
protected function getSelectFileTemplateName(): string
{
return 'your/path/to/select_file.html.twig';
}
protected function getMatrixEditTemplateName(): string
{
return 'your/path/to/edit_matrix.html.twig';
}
Block name used in templates is batch_entity_import_content
, so probably there will be need to override it a bit.
You can create a new file with content similar to the given example. Then just use it instead of original layout file.
{% extends path/to/your/layout.html.twig %}
{% block your_real_block_name %}
{% block batch_entity_import_content %}{% endblock %}
{% endblock %}
Then you just have to override it in bundle directory, or change a path to layout in your configuration.
If you want to add some specific data to the rendered view, just override these methods in your controller:
protected function prepareSelectFileView(FormInterface $form): Response
{
return $this->prepareView(
$this->getSelectFileTemplateName(),
[
'form' => $form->createView(),
]
);
}
protected function prepareMatrixEditView(FormInterface $form, Matrix $matrix, bool $manualSubmit = false): Response
{
if ($manualSubmit) {
$this->manualSubmitMatrixForm($form, $matrix);
}
$configuration = $this->getImportConfiguration();
return $this->prepareView(
$this->getMatrixEditTemplateName(),
[
'header_info' => $matrix->getHeaderInfo($configuration->getEntityClassName()),
'data' => $matrix->getRecords(),
'form' => $form->createView(),
'importConfiguration' => $configuration,
]
);
}
If your entity has an array field, and you want to import data from CSV file to it, this is how you can do it.
|
.[]
|
value in CSV is equal to ['', '']
json
array
simple_array
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class User
{
#[ORM\Column(type: 'json']
private array $roles = [];
}
ArrayTextType
type. If you skip this configuration, TextType
will be used as default.use JG\BatchEntityImportBundle\Form\Type\ArrayTextType;
use JG\BatchEntityImportBundle\Model\Form\FormFieldDefinition;
public function getFieldsDefinitions(): array
{
return [
'roles' => new FormFieldDefinition(
ArrayTextType::class,
[
'separator' => '&',
]
),
];
}
user_name,roles
user_1,USER&ADMIN&SUPER_ADMIN
user_2,USER
user_3,SUPER_ADMIN
user_name,age,email,roles,country:en,name:pl
user_1,21,[email protected],USER&ADMIN&SUPER_ADMIN,Poland,Polska
user_2,34,[email protected],USER,England,Anglia
user_3,56,[email protected],SUPER_ADMIN,Germany,Niemcy