Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 1 addition & 17 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,14 @@ name: Tests

on:
push:
branches:
- '**'
pull_request:
branches:
- master
types: [ opened, reopened ]

jobs:
pre_job:
runs-on: ubuntu-latest
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
concurrent_skipping: always
skip_after_successful_duplicate: true
do_not_skip: '["pull_request"]'

tests:
runs-on: ubuntu-latest
name: Tests
needs: pre_job
if: needs.pre_job.outputs.should_skip != 'true'

strategy:
fail-fast: false
Expand Down
28 changes: 24 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Importing entities with preview and edit features for Symfony.
* [Controller-specific templates](#controller-specific-templates)
* [Main layout](#main-layout)
* [Additional data](#additional-data)
* [Updating entities](#updating-entities)
* [Importing data to array field](#importing-data-to-array-field)
* [Full example of CSV file](#full-example-of-csv-file)

Expand Down Expand Up @@ -360,6 +361,25 @@ protected function prepareMatrixEditView(FormInterface $form, Matrix $matrix, bo
}
```

## Updating entities

If you want to update your entities:
- Set `allowOverrideEntity` to `true` in your import configuration file.
- Then in your import file:
- Add `entity_id` in header and:
- Add entity ID to row
- Leave it empty (if you want to set it manually or import it as new record)
- Or if you don't want to add `entity_id` header, you can still manually set each entity to override.

#### CSV file

```csv
entity_id,user_name
2,user_1
,user_2
10,user_3
```

## Importing data to array field

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.
Expand Down Expand Up @@ -434,8 +454,8 @@ user_3,SUPER_ADMIN
## Full example of CSV file

```csv
user_name,age,email,roles,country:en,name:pl
user_1,21,user_1@test.com,USER&ADMIN&SUPER_ADMIN,Poland,Polska
user_2,34,user_2@test.com,USER,England,Anglia
user_3,56,user_3@test.com,SUPER_ADMIN,Germany,Niemcy
entity_id,user_name,age,email,roles,country:en,name:pl
1,user_1,21,user_1@test.com,USER&ADMIN&SUPER_ADMIN,Poland,Polska
3, user_2,34,user_2@test.com,USER,England,Anglia
,user_3,56,user_3@test.com,SUPER_ADMIN,Germany,Niemcy
```
108 changes: 32 additions & 76 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -1,70 +1,34 @@
UPGRADE TO 3.6
=======================
# UPGRADE TO 3.7

Import data to array
--------------
### Preset entity id before updating - [go to the documentation](README.md#updating-entities)

* By default, allowed file extensions are set to `'csv', 'xls', 'xlsx', 'ods'`.
However, if you want to change it, you can override this method in your import configuration.
# UPGRADE TO 3.6

```php
public function getAllowedFileExtensions(): array
{
return ['csv', 'xls', 'xlsx', 'ods'];
}
```
### Set allowed file extensions - [go to the documentation](README.md#set-allowed-file-extensions)

UPGRADE TO 3.5
=======================

Import data to array
--------------
* If your entity has an array field, and you want to import data from CSV file to it, it is now possible.

```php
use JG\BatchEntityImportBundle\Form\Type\ArrayTextType;
use JG\BatchEntityImportBundle\Model\Form\FormFieldDefinition;

public function getFieldsDefinitions(): array
{
return [
'roles' => new FormFieldDefinition(
ArrayTextType::class,
[
'separator' => '&',
]
),
];
}
```
# UPGRADE TO 3.5

### Import data to array - [go to the documentation](README.md#importing-data-to-array-field)

UPGRADE TO 3.1
=======================
# UPGRADE TO 3.1

CSV File
--------------
* Now CSV file can contain spaces and dashes as a header name, for example "my column name" or "my-column-name".
## CSV File
- Now CSV file can contain spaces and dashes as a header name, for example "my column name" or "my-column-name".

Import Configuration class
--------------
* When header name contains spaces we should use underscores instead of spaces when defining fields names in fields definitions and in constraints.
## Import Configuration class
- When header name contains spaces we should use underscores instead of spaces when defining fields names in fields definitions and in constraints.


UPGRADE TO 3.0
=======================
# UPGRADE TO 3.0

Controller
--------------
## Controller
* Passing configuration class by `getSubscribedServices()` was removed. Now it is only possible by autoconfiguration.


UPGRADE TO 2.5
=======================
# UPGRADE TO 2.5

Import Configuration class
--------------
* Added new validator to check matrix record data uniqueness in database.
## Import Configuration class
- Added new validator to check matrix record data uniqueness in database.
```php
use JG\BatchEntityImportBundle\Validator\Constraints\DatabaseEntityUnique;

Expand All @@ -76,12 +40,10 @@ public function getMatrixConstraints(): array
}
```

UPGRADE TO 2.4
=======================
# UPGRADE TO 2.4

Import Configuration class
--------------
* Added new validator to check matrix record data uniqueness.
## Import Configuration class
- Added new validator to check matrix record data uniqueness.
```php
use JG\BatchEntityImportBundle\Validator\Constraints\MatrixRecordUnique;

Expand All @@ -93,33 +55,27 @@ public function getMatrixConstraints(): array
}
```

Controller
--------------
* List of options passed to form in `createMatrixForm()` method, should contain new `constraints` element:
## Controller
- List of options passed to form in `createMatrixForm()` method, should contain new `constraints` element:
`'constraints' => $importConfiguration->getMatrixConstraints()`

UPGRADE TO 2.3
=======================
# UPGRADE TO 2.3

Controller
--------------
* Passing configuration class by `getSubscribedServices()` method is not needed anymore and will be removed in the future.
* To make sure that configuration class will be injected automatically:
* Interface `JG\BatchEntityImportBundle\Controller\ImportConfigurationAutoInjectInterface` should be implemented.
* Trait `JG\BatchEntityImportBundle\Controller\ImportConfigurationAutoInjectTrait` should be used to add needed methods.
## Controller
- Passing configuration class by `getSubscribedServices()` method is not needed anymore and will be removed in the future.
- To make sure that configuration class will be injected automatically:
- Interface `JG\BatchEntityImportBundle\Controller\ImportConfigurationAutoInjectInterface` should be implemented.
- Trait `JG\BatchEntityImportBundle\Controller\ImportConfigurationAutoInjectTrait` should be used to add needed methods.


UPGRADE TO 2.2
=======================
# UPGRADE TO 2.2

Import Configuration class
--------------
* Now configuration class should be always registered as a service:
## Import Configuration class
- Now configuration class should be always registered as a service:
```yaml
services:
App\Model\ImportConfiguration\UserImportConfiguration: ~
```

Controller
--------------
* Entity Manager is no longer passed as an argument of actions.
## Controller
- Entity Manager is no longer passed as an argument of actions.
1 change: 0 additions & 1 deletion src/Controller/BaseImportControllerTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ protected function prepareMatrixEditView(FormInterface $form, Matrix $matrix, bo
$this->getMatrixEditTemplateName(),
[
'header_info' => $matrix->getHeaderInfo($configuration->getEntityClassName()),
'data' => $matrix->getRecords(),
'form' => $form->createView(),
'importConfiguration' => $configuration,
]
Expand Down
15 changes: 15 additions & 0 deletions src/Form/Type/MatrixRecordType.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\Exception\AccessException;
use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException;
use Symfony\Component\OptionsResolver\OptionsResolver;
Expand Down Expand Up @@ -101,4 +103,17 @@ private function addField(array $fieldDefinitions, string $columnName, FormEvent
? $event->getForm()->add($columnName, $definition->getClass(), $definition->getOptions())
: $event->getForm()->add($columnName, TextType::class);
}

public function finishView(FormView $view, FormInterface $form, array $options): void
{
/** @var MatrixRecord $entity */
$entity = $form->getData();
$selectedValue = $entity->entityId;

foreach ($view['entity']->vars['choices'] ?? [] as $index => $choice) {
if ($choice->value === $selectedValue) {
$view['entity']->vars['choices'][$index]->attr['selected'] = 'selected';
}
}
}
}
20 changes: 16 additions & 4 deletions src/Model/Matrix/Matrix.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
class Matrix
{
private const RESERVED_ENTITY_COLUMN_NAME = 'entity';
private const RESERVED_ENTITY_ID_COLUMN_NAME = 'entity_id';
#[Assert\All([
new Assert\NotBlank(),
new Assert\Type('string'),
Expand All @@ -33,9 +34,9 @@ public function __construct(array $header = [], array $recordsData = [])
$this->header = $this->clearHeader($header);

foreach ($recordsData as $data) {
$data = $this->clearRecordData($data);
if ($data) {
$this->records[] = new MatrixRecord($data);
$clearedData = $this->clearRecordData($data);
if ($clearedData) {
$this->records[] = new MatrixRecord($clearedData, $this->getEntityIdValue($data));
}
}
}
Expand Down Expand Up @@ -65,6 +66,17 @@ public function getHeaderInfo(string $className): array
return $info;
}

private function getEntityIdValue(array $data): int|string|null
{
foreach ($data as $name => $value) {
if (self::RESERVED_ENTITY_ID_COLUMN_NAME === $name) {
return $value;
}
}

return null;
}

private function clearHeader(array $header): array
{
$header = array_values(
Expand All @@ -81,6 +93,6 @@ private function clearRecordData(array $data): array

private function isColumnNameValid(?string $name): bool
{
return !empty(trim((string) $name)) && self::RESERVED_ENTITY_COLUMN_NAME !== $name;
return !empty(trim((string) $name)) && !\in_array($name, [self::RESERVED_ENTITY_COLUMN_NAME, self::RESERVED_ENTITY_ID_COLUMN_NAME], true);
}
}
3 changes: 2 additions & 1 deletion src/Model/Matrix/MatrixFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use JG\BatchEntityImportBundle\Service\CsvDelimiterDetector;
use PhpOffice\PhpSpreadsheet\Reader\BaseReader;
use PhpOffice\PhpSpreadsheet\Reader\Csv;
use PhpOffice\PhpSpreadsheet\Reader\Xls;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use Symfony\Component\HttpFoundation\File\UploadedFile;

Expand Down Expand Up @@ -55,7 +56,7 @@ private static function getReader(UploadedFile $file): BaseReader
if ($reader instanceof Csv) {
$detectedDelimiter = (new CsvDelimiterDetector())->detect($file->getContent());
$reader->setDelimiter($detectedDelimiter->value);
} elseif ($reader instanceof Xlsx) {
} elseif ($reader instanceof Xls || $reader instanceof Xlsx) {
$reader->setIgnoreRowsWithNoCells(true);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Model/Matrix/MatrixRecord.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class MatrixRecord
private ?object $entity = null;
private array $data = [];

public function __construct(array $data = [])
public function __construct(array $data = [], public readonly int|string|null $entityId = null)
{
foreach ($data as $name => $value) {
if (!empty(\trim((string) $name))) {
Expand Down
22 changes: 22 additions & 0 deletions tests/Controller/ImportControllerTraitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,28 @@ public function testUpdateExistingRecord(
$this->assertEntityValues($expectedDefaultValues, $updatedEntityId);

$this->submitSelectFileForm(__DIR__ . '/../Fixtures/Resources/test_updated_data.csv');

$this->assertSame(
'2',
$this->client->getCrawler()->filterXpath('//select[@name="matrix[records][0][entity]"]/option[@selected]')->attr('value'),
);
$this->assertSame(
'test',
$this->client->getCrawler()->filterXpath('//input[@name="matrix[records][0][test_private_property]"]')->attr('value'),
);
$this->assertSame(
'lorem ipsum',
$this->client->getCrawler()->filterXpath('//input[@name="matrix[records][0][test-private-property2]"]')->attr('value'),
);
$this->assertSame(
'qwerty',
$this->client->getCrawler()->filterXpath('//input[@name="matrix[records][0][test_public_property]"]')->attr('value'),
);
$this->assertSame(
'arr_val_1000|array_val_1001',
$this->client->getCrawler()->filterXpath('//input[@name="matrix[records][0][test_array_field]"]')->attr('value'),
);

$this->client->submitForm('btn-submit', [
'matrix' => [
'records' => [
Expand Down
Binary file added tests/Fixtures/Resources/test.xls
Binary file not shown.
Binary file added tests/Fixtures/Resources/test.xlsx
Binary file not shown.
Binary file not shown.
4 changes: 2 additions & 2 deletions tests/Fixtures/Resources/test_updated_data.csv
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
test_private_property,test-private-property2,test public property,test_array_field
test,lorem ipsum,qwerty,arr_val_1000|array_val_1001
test_private_property,test-private-property2,test public property,test_array_field,entity_id
test,lorem ipsum,qwerty,arr_val_1000|array_val_1001,2
Loading
Loading