Skip to content

Commit

Permalink
add default delimiter for csv files
Browse files Browse the repository at this point in the history
  • Loading branch information
jgrygierek committed Mar 26, 2024
1 parent 4abde34 commit a78c4be
Show file tree
Hide file tree
Showing 19 changed files with 236 additions and 36 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Importing entities with preview and edit features for Symfony.
* [Installation](#installation)
* [Configuration class](#configuration-class)
* [Basic configuration class](#basic-configuration-class)
* [CSV file](#csv-file)
* [Fields definitions](#fields-definitions)
* [Matrix validation](#matrix-validation)
* [Passing services to configuration class](#passing-services-to-configuration-class)
Expand Down Expand Up @@ -81,6 +82,17 @@ services:
App\Model\ImportConfiguration\UserImportConfiguration: ~
```
### CSV file
To set default delimiter just override `getCsvDelimiter()` method:

```php
public function getCsvDelimiter(): string
{
return ',';
}
```

### Fields definitions

If you want to change types of rendered fields, instead of using default ones,
Expand Down
15 changes: 15 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
UPGRADE TO 3.2
=======================

Import Configuration class
--------------

* CSV files: added possibility to choose delimiter, default is `,`.

```php
public function getCsvDelimiter(): string
{
return ',';
}
```

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

Expand Down
5 changes: 3 additions & 2 deletions src/Controller/BaseImportControllerTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use InvalidArgumentException;
use JG\BatchEntityImportBundle\Exception\BatchEntityImportExceptionInterface;
use JG\BatchEntityImportBundle\Form\Type\FileImportType;
use JG\BatchEntityImportBundle\Model\Configuration\CsvConfigurationInterface;
use JG\BatchEntityImportBundle\Model\Configuration\ImportConfigurationInterface;
use JG\BatchEntityImportBundle\Model\FileImport;
use JG\BatchEntityImportBundle\Model\Matrix\Matrix;
Expand Down Expand Up @@ -50,7 +51,7 @@ protected function doImport(Request $request, ValidatorInterface $validator): Re
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
$matrix = MatrixFactory::createFromUploadedFile($fileImport->getFile());
$matrix = MatrixFactory::createFromUploadedFile($fileImport->getFile(), $this->getImportConfiguration());

$errors = $validator->validate($matrix);
if (0 === $errors->count()) {
Expand Down Expand Up @@ -130,7 +131,7 @@ protected function doImportSave(Request $request, TranslatorInterface $translato
return $this->prepareMatrixEditView($form, $matrix);
}

protected function getImportConfiguration(): ImportConfigurationInterface
protected function getImportConfiguration(): ImportConfigurationInterface|CsvConfigurationInterface
{
if (!$this->importConfiguration) {
throw new ServiceNotFoundException($this->getImportConfigurationClassName());
Expand Down
7 changes: 6 additions & 1 deletion src/Model/Configuration/AbstractImportConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use Knp\DoctrineBehaviors\Contract\Entity\TranslatableInterface;
use TypeError;

abstract class AbstractImportConfiguration implements ImportConfigurationInterface
abstract class AbstractImportConfiguration implements ImportConfigurationInterface, CsvConfigurationInterface
{
public function __construct(private readonly EntityManagerInterface $em)
{
Expand Down Expand Up @@ -119,4 +119,9 @@ public function allowOverrideEntity(): bool
{
return true;
}

public function getCsvDelimiter(): string
{
return ',';
}
}
13 changes: 13 additions & 0 deletions src/Model/Configuration/CsvConfigurationInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace JG\BatchEntityImportBundle\Model\Configuration;

interface CsvConfigurationInterface
{
/**
* Get default delimiter for CSV file.
*/
public function getCsvDelimiter(): string;
}
4 changes: 2 additions & 2 deletions src/Model/Matrix/Matrix.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

namespace JG\BatchEntityImportBundle\Model\Matrix;

use const ARRAY_FILTER_USE_KEY;

use JG\BatchEntityImportBundle\Service\PropertyExistenceChecker;
use Symfony\Component\Validator\Constraints as Assert;

use const ARRAY_FILTER_USE_KEY;

class Matrix
{
private const RESERVED_ENTITY_COLUMN_NAME = 'entity';
Expand Down
17 changes: 12 additions & 5 deletions src/Model/Matrix/MatrixFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@
namespace JG\BatchEntityImportBundle\Model\Matrix;

use InvalidArgumentException;
use JG\BatchEntityImportBundle\Model\Configuration\CsvConfigurationInterface;
use PhpOffice\PhpSpreadsheet\Reader\BaseReader;
use PhpOffice\PhpSpreadsheet\Reader\Csv;
use Symfony\Component\HttpFoundation\File\UploadedFile;

class MatrixFactory
{
/**
* @throws InvalidArgumentException
*/
public static function createFromUploadedFile(UploadedFile $file): Matrix
public static function createFromUploadedFile(UploadedFile $file, CsvConfigurationInterface $csvConfiguration): Matrix
{
$reader = self::getReader($file);
$reader = self::getReader($file, $csvConfiguration);
$spreadsheet = $reader->load($file->getPathname());

$data = $spreadsheet->getActiveSheet()->toArray();
Expand All @@ -36,18 +38,23 @@ private static function addKeysToRows(array $header, array &$data): void
$data,
static function (array &$row) use ($header): void {
$row = array_combine($header, $row);
}
},
);
}

private static function getReader(UploadedFile $file): BaseReader
private static function getReader(UploadedFile $file, CsvConfigurationInterface $csvConfiguration): BaseReader
{
$extension = ucfirst(strtolower($file->getClientOriginalExtension()));
$readerClass = 'PhpOffice\PhpSpreadsheet\Reader\\' . $extension;
if (!class_exists($readerClass)) {
throw new InvalidArgumentException("Reader for extension $extension is not supported by PhpOffice.");
}

return new $readerClass();
$reader = new $readerClass();
if ($reader instanceof Csv) {
$reader->setDelimiter($csvConfiguration->getCsvDelimiter());
}

return $reader;
}
}
38 changes: 34 additions & 4 deletions tests/Controller/ImportControllerTraitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,27 @@ public function testControllerWorksOk(): void
$this->checkData(['new_value', 'new_value2', 'new_value3', 'new_value4', 'new_value5'], $updatedEntityId, $importUrl);
}

public function testUpdateOnlySingleTranslatableColumn(): void
{
$importUrl = '/jg_batch_entity_import_bundle/import_base_translatable';
$updatedEntityId = 10;
self::assertCount(self::DEFAULT_RECORDS_NUMBER, $this->getRepository()->findAll());
$this->assertEntityValues(['abcd_9', '', '', 'qwerty_9', 'qwerty_9'], $updatedEntityId);

$this->submitSelectFileForm(__DIR__ . '/../Fixtures/Resources/test_update_single_column.csv', $importUrl);
$this->client->submitForm('btn-submit', [
'matrix' => [
'records' => [
[
'entity' => $updatedEntityId,
'testTranslationProperty:pl' => 'Lorem Ipsum',
],
],
],
]);
$this->checkData(['abcd_9', '', '', 'Lorem Ipsum', 'Lorem Ipsum'], $updatedEntityId, $importUrl, 0);
}

public function testDuplicationFoundInDatabase(): void
{
$importUrl = '/jg_batch_entity_import_bundle/import';
Expand Down Expand Up @@ -124,17 +145,26 @@ private function submitSelectFileForm(string $uploadedFile, string $importUrl =
self::assertEquals($importUrl, $this->client->getRequest()->getRequestUri());
}

private function checkData(array $expectedValues, int $entityId, string $importUrl = '/jg_batch_entity_import_bundle/import'): void
{
private function checkData(
array $expectedValues,
int $entityId,
string $importUrl = '/jg_batch_entity_import_bundle/import',
int $newRecordsNumber = self::NEW_RECORDS_NUMBER,
): void {
$repository = $this->getRepository();
self::assertTrue($this->client->getResponse()->isRedirect($importUrl));
$this->client->followRedirect();
self::assertTrue($this->client->getResponse()->isSuccessful());
self::assertStringContainsString('Data has been imported', $this->client->getResponse()->getContent());
self::assertCount(self::DEFAULT_RECORDS_NUMBER + self::NEW_RECORDS_NUMBER, $repository->findAll());
self::assertCount(self::DEFAULT_RECORDS_NUMBER + $newRecordsNumber, $repository->findAll());

$this->assertEntityValues($expectedValues, $entityId);
}

private function assertEntityValues(array $expectedValues, int $entityId): void
{
/** @var TranslatableEntity|null $item */
$item = $repository->find($entityId);
$item = $this->getRepository()->find($entityId);
self::assertNotEmpty($item);
self::assertSame($expectedValues[0], $item->getTestPrivateProperty());
self::assertSame($expectedValues[1], $item->getTestPrivateProperty2());
Expand Down
15 changes: 15 additions & 0 deletions tests/Fixtures/Configuration/CsvConfiguration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace JG\BatchEntityImportBundle\Tests\Fixtures\Configuration;

use JG\BatchEntityImportBundle\Model\Configuration\CsvConfigurationInterface;

class CsvConfiguration implements CsvConfigurationInterface
{
public function getCsvDelimiter(): string
{
return ',';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use JG\BatchEntityImportBundle\Model\Configuration\AbstractImportConfiguration;
use JG\BatchEntityImportBundle\Tests\Fixtures\Entity\TranslatableEntity;
use JG\BatchEntityImportBundle\Validator\Constraints\DatabaseEntityUnique;

class TranslatableEntityBaseConfiguration extends AbstractImportConfiguration
{
Expand All @@ -19,17 +18,4 @@ public function getEntityTranslationRelationName(): ?string
{
return 'translations';
}

public function getMatrixConstraints(): array
{
return [
new DatabaseEntityUnique(['entityClassName' => $this->getEntityClassName(), 'fields' => [
'test_private_property',
'test_public_property',
]]),
new DatabaseEntityUnique(['entityClassName' => $this->getEntityClassName(), 'fields' => [
'test-private-property2',
]]),
];
}
}
35 changes: 35 additions & 0 deletions tests/Fixtures/Configuration/TranslatableEntityConfiguration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace JG\BatchEntityImportBundle\Tests\Fixtures\Configuration;

use JG\BatchEntityImportBundle\Model\Configuration\AbstractImportConfiguration;
use JG\BatchEntityImportBundle\Tests\Fixtures\Entity\TranslatableEntity;
use JG\BatchEntityImportBundle\Validator\Constraints\DatabaseEntityUnique;

class TranslatableEntityConfiguration extends AbstractImportConfiguration
{
public function getEntityClassName(): string
{
return TranslatableEntity::class;
}

public function getEntityTranslationRelationName(): ?string
{
return 'translations';
}

public function getMatrixConstraints(): array
{
return [
new DatabaseEntityUnique(['entityClassName' => $this->getEntityClassName(), 'fields' => [
'test_private_property',
'test_public_property',
]]),
new DatabaseEntityUnique(['entityClassName' => $this->getEntityClassName(), 'fields' => [
'test-private-property2',
]]),
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace JG\BatchEntityImportBundle\Tests\Fixtures\Controller;

use JG\BatchEntityImportBundle\Controller\ImportConfigurationAutoInjectInterface;
use JG\BatchEntityImportBundle\Controller\ImportConfigurationAutoInjectTrait;
use JG\BatchEntityImportBundle\Controller\ImportControllerTrait;
use JG\BatchEntityImportBundle\Tests\Fixtures\Configuration\TranslatableEntityBaseConfiguration;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

class ControllerWithBaseTranslatableConfiguration extends AbstractController implements ImportConfigurationAutoInjectInterface
{
use ImportConfigurationAutoInjectTrait;
use ImportControllerTrait;

public function import(Request $request, ValidatorInterface $validator): Response
{
return $this->doImport($request, $validator);
}

public function importSave(Request $request, TranslatorInterface $translator): Response
{
return $this->doImportSave($request, $translator);
}

protected function redirectToImport(): RedirectResponse
{
return $this->redirectToRoute('jg.batch_entity_import_bundle.test_controller.base_translatable_config.import');
}

protected function getMatrixSaveActionUrl(): string
{
return $this->generateUrl('jg.batch_entity_import_bundle.test_controller.base_translatable_config.import_save');
}

protected function getImportConfigurationClassName(): string
{
return TranslatableEntityBaseConfiguration::class;
}
}
4 changes: 2 additions & 2 deletions tests/Fixtures/Controller/ControllerWithInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use JG\BatchEntityImportBundle\Controller\ImportConfigurationAutoInjectInterface;
use JG\BatchEntityImportBundle\Controller\ImportConfigurationAutoInjectTrait;
use JG\BatchEntityImportBundle\Controller\ImportControllerTrait;
use JG\BatchEntityImportBundle\Tests\Fixtures\Configuration\TranslatableEntityBaseConfiguration;
use JG\BatchEntityImportBundle\Tests\Fixtures\Configuration\TranslatableEntityConfiguration;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
Expand Down Expand Up @@ -42,6 +42,6 @@ protected function getMatrixSaveActionUrl(): string

protected function getImportConfigurationClassName(): string
{
return TranslatableEntityBaseConfiguration::class;
return TranslatableEntityConfiguration::class;
}
}
2 changes: 2 additions & 0 deletions tests/Fixtures/Resources/test_update_single_column.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
testTranslationProperty:pl
Lorem Ipsum
Loading

0 comments on commit a78c4be

Please sign in to comment.