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
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/vendor/
/var/
/node_modules/
/composer.lock
/drivers/

/etc/build/*
!/etc/build/.gitignore
Expand All @@ -12,5 +12,5 @@
/tests/TestApplication/.env.*.local

/.phpunit.result.cache
/behat.yml
/behat.yaml
/phpunit.xml
7 changes: 6 additions & 1 deletion config/config/grid/process_import.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@ sylius_grid:
actions:
item:
show:
type: show
type: show
delete:
type: delete
bulk:
delete:
type: delete
3 changes: 3 additions & 0 deletions config/config/messenger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ framework:
'Sylius\ImportExport\Messenger\Command\ExportCommand': export_command
'Sylius\ImportExport\Messenger\Event\ExportProcessCompleted': export_event
buses:
sylius_import_export.import.command_bus:
middleware:
- doctrine_transaction
sylius_import_export.export.command_bus:
middleware:
- doctrine_transaction
Expand Down
28 changes: 22 additions & 6 deletions config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,29 @@
<service id="sylius_import_export.messenger.command_handler.create_import_process" class="Sylius\ImportExport\Messenger\Handler\CreateImportProcessHandler">
<argument type="service" id="sylius_import_export.factory.process" />
<argument type="service" id="sylius_import_export.repository.process_import" />
<argument type="service" id="sylius.command_bus" />
<argument type="service" id="sylius_import_export.import.command_bus" />
<argument type="service" id="sylius_import_export.importer_resolver" />

<tag name="messenger.message_handler" bus="sylius.command_bus" />
<tag name="messenger.message_handler" bus="sylius_import_export.import.command_bus" />
</service>

<service id="sylius_import_export.validator.import" class="Sylius\ImportExport\Validator\ImportValidator">
<argument type="service" id="validator" />
</service>

<service id="sylius_import_export.processor.batch" class="Sylius\ImportExport\Processor\BatchProcessor">
<argument type="service" id="sylius_import_export.denormalizer.registry" />
<argument type="service" id="doctrine.orm.entity_manager" />
<argument type="service" id="sylius.resource_registry" />
<argument type="service" id="sylius_import_export.validator.import" />
</service>

<service id="sylius_import_export.messenger.command_handler.import" class="Sylius\ImportExport\Messenger\Handler\ImportCommandHandler">
<argument type="service" id="sylius_import_export.repository.process_import" />
<argument type="service" id="sylius_import_export.denormalizer.registry" />
<argument type="service" id="doctrine.orm.entity_manager" />
<argument type="service" id="sylius_import_export.processor.batch" />

<tag name="messenger.message_handler" bus="sylius.command_bus" />
<tag name="messenger.message_handler" bus="sylius_import_export.import.command_bus" />
</service>

<service id="sylius_import_export.twig.component.export_resource" class="Sylius\ImportExport\Twig\Component\ExportResourceFormComponent">
Expand Down Expand Up @@ -100,11 +111,16 @@
<tag name="controller.service_arguments" />
</service>

<service id="sylius_import_export.uploader.import_file" class="Sylius\ImportExport\Uploader\ImportFileUploader">
<argument>%sylius_import_export.import_files_directory%</argument>
</service>

<service id="sylius_import_export.controller.import_action" class="Sylius\ImportExport\Controller\ImportAction" public="true">
<argument type="service" id="sylius.resource_registry" />
<argument type="service" id="form.factory" />
<argument type="service" id="sylius.command_bus" />
<argument type="service" id="sylius_import_export.import.command_bus" />
<argument>%sylius_import_export.import.form_class%</argument>
<argument type="service" id="sylius_import_export.uploader.import_file" />
<argument type="service" id="sylius.resource_registry" />

<tag name="controller.service_arguments" />
</service>
Expand Down
5 changes: 3 additions & 2 deletions config/services/form.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
</service>

<service id="sylius_import_export.form.type.import" class="Sylius\ImportExport\Form\Type\ImportResourceType">
<argument>%kernel.project_dir%</argument>

<argument>%sylius_import_export.import.file_max_size%</argument>
<argument>%sylius_import_export.import.allowed_mime_types%</argument>

<tag name="form.type" />
</service>
</services>
Expand Down
1 change: 1 addition & 0 deletions config/services/importer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
>
<services>
<service id="sylius_import_export.importer.json" class="Sylius\ImportExport\Importer\JsonImporter">
<argument type="service" id="serializer.encoder.json" />
<tag name="sylius.import_export.importer" format="json" />
</service>

Expand Down
Empty file added etc/import/.gitignore
Empty file.
44 changes: 27 additions & 17 deletions src/Controller/ImportAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,62 +14,72 @@
namespace Sylius\ImportExport\Controller;

use Sylius\ImportExport\Messenger\Command\CreateImportProcess;
use Sylius\ImportExport\Uploader\ImportFileUploader;
use Sylius\Resource\Metadata\RegistryInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Messenger\MessageBusInterface;

final class ImportAction
final readonly class ImportAction
{
public function __construct(
private RegistryInterface $metadataRegistry,
private FormFactoryInterface $formFactory,
private MessageBusInterface $commandBus,
private string $importForm,
private ImportFileUploader $importFileUploader,
private RegistryInterface $metadataRegistry,
) {
}

public function __invoke(Request $request, string $grid): Response
{
$request->attributes->set('_sylius', array_merge($request->attributes->get('_sylius', []), ['grid' => $grid]));
/** @var Session $session */
$session = $request->getSession();

$form = $this->formFactory->create($this->importForm);
$form->handleRequest($request);

if (!$form->isSubmitted() || !$form->isValid()) {
/** @var Session $session */
$session = $request->getSession();
$session->getFlashBag()->add('error', 'sylius_import_export.import_form_invalid');
$errors = [];
foreach ($form->getErrors(true) as $error) {
if ($error instanceof FormError) {
$errors[] = $error->getMessage();
}
}
$errorMessage = !empty($errors) ? implode(', ', $errors) : 'sylius_import_export.import_form_invalid';
$session->getFlashBag()->add('error', $errorMessage);

return new RedirectResponse($request->headers->get('referer') ?? '/');
}

$data = $form->getData();
$format = $data['format'];
$filePath = $data['filePath'];
$resourceClass = $data['resourceClass'];

// Get metadata to find the resource alias
$metadata = $this->metadataRegistry->getByClass($resourceClass);
/** @var UploadedFile $file */
$file = $data['file'];

try {
$format = $this->importFileUploader->getFormatFromMimeType($file->getMimeType());
$filePath = $this->importFileUploader->upload($file);

$metadata = $this->metadataRegistry->getByClass($resourceClass);
$resourceAlias = $metadata->getAlias();

$this->commandBus->dispatch(new CreateImportProcess(
resource: $resourceClass, // Pass the actual class, not the alias
resource: $resourceAlias,
format: $format,
filePath: $filePath,
parameters: [], // Empty for now
parameters: [],
));

/** @var Session $session */
$session = $request->getSession();
$session->getFlashBag()->add('success', 'sylius_import_export.import_started');
} catch (\Throwable $e) {
/** @var Session $session */
$session = $request->getSession();
$session->getFlashBag()->add('error', 'sylius_import_export.import_failed: ' . $e->getMessage());
$session->getFlashBag()->add('error', 'sylius_import_export.failed');
}

return new RedirectResponse($request->headers->get('referer') ?? '/');
Expand Down
9 changes: 4 additions & 5 deletions src/DependencyInjection/SyliusImportExportExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ public function load(array $configs, ContainerBuilder $container): void
$configuration = $this->processConfiguration(new Configuration(), $configs);

$this->processExportConfig($container, $configuration);
$this->processFormConfig($container);

$loader = new XmlFileLoader($container, new FileLocator(dirname(__DIR__, 2) . '/config/'));
$loader->load('services.xml');
Expand All @@ -47,10 +46,10 @@ private function processExportConfig(ContainerBuilder $container, array &$config

$container->setParameter('sylius_import_export.export.default_provider', $defaultProvider);
$container->setParameter('sylius_import_export.export.resources', $config['export']['resources']);
}

private function processFormConfig(ContainerBuilder $container): void
{
$container->setParameter('sylius_import_export.export_files_directory', '%kernel.project_dir%/var/export');
$container->setParameter('sylius_import_export.import_files_directory', '%kernel.project_dir%/var/import');

$container->setParameter('sylius_import_export.import.file_max_size', '50M');
$container->setParameter('sylius_import_export.import.allowed_mime_types', ['application/json']);
}
}
18 changes: 18 additions & 0 deletions src/Exception/ValidationFailedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\ImportExport\Exception;

class ValidationFailedException extends \Exception
{
}
31 changes: 20 additions & 11 deletions src/Form/Type/ImportResourceType.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,39 @@
namespace Sylius\ImportExport\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\File;
use Symfony\Component\Validator\Constraints\NotBlank;

final class ImportResourceType extends AbstractType
{
public function __construct(
private string $projectDir,
private string $fileMaxSize,
private array $allowedMimeTypes,
) {
}

public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('format', ChoiceType::class, [
'label' => 'sylius_import_export.grid.form.format',
'choices' => [
'json' => 'json',
'csv' => 'csv',
->add('file', FileType::class, [
'label' => 'sylius_import_export.grid.form.file',
'required' => true,
'constraints' => [
new NotBlank([
'message' => 'sylius_import_export.validation.import.file.not_blank',
'groups' => ['sylius'],
]),
new File([
'maxSize' => $this->fileMaxSize,
'mimeTypes' => $this->allowedMimeTypes,
'mimeTypesMessage' => 'sylius_import_export.validation.import.file.invalid_type',
'maxSizeMessage' => 'sylius_import_export.validation.import.file.max_size',
'groups' => ['sylius'],
]),
],
'data' => 'json', // Default to JSON for testing
])
->add('filePath', HiddenType::class, [
'data' => $this->projectDir . '/var/exported/export.json', // Hardcoded path
])
->add('resourceClass', HiddenType::class)
;
Expand Down
2 changes: 0 additions & 2 deletions src/Grid/Listener/ImportActionAdminGridListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,13 @@ public function addImportActions(GridDefinitionConverterEvent $event): void
return;
}

// For now, use same logic as export to determine if import is allowed
if (
!$this->exportableChecker->canBeExported($grid, $this->sectionProvider?->getSection()) &&
!$this->exportableChecker->canBeExported($grid, $this->getRouteSection())
) {
return;
}

// Add import action only to main group for now
$this->addInActionGroup($grid, ActionGroupInterface::MAIN_GROUP);
}

Expand Down
8 changes: 7 additions & 1 deletion src/Importer/JsonImporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@
namespace Sylius\ImportExport\Importer;

use Sylius\ImportExport\Exception\ImportFailedException;
use Symfony\Component\Serializer\Encoder\JsonEncoder;

final class JsonImporter implements ImporterInterface
{
public const FORMAT = 'json';

public function __construct(
private JsonEncoder $jsonEncoder,
) {
}

public function getConfig(): array
{
return [
Expand All @@ -35,7 +41,7 @@ public function import(string $filePath): array
throw new \InvalidArgumentException();
}

return json_decode($content, true, 512, \JSON_THROW_ON_ERROR);
return $this->jsonEncoder->decode($content, self::FORMAT);
} catch (\Throwable $exception) {
throw new ImportFailedException(sprintf('Failed to import from "%s": %s', $filePath, $exception->getMessage()));
}
Expand Down
26 changes: 12 additions & 14 deletions src/Messenger/Handler/ImportCommandHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@
namespace Sylius\ImportExport\Messenger\Handler;

use Doctrine\ORM\EntityManagerInterface;
use Sylius\ImportExport\Denormalizer\DenormalizerRegistryInterface;
use Sylius\ImportExport\Entity\ImportProcessInterface;
use Sylius\ImportExport\Exception\ImportFailedException;
use Sylius\ImportExport\Exception\ValidationFailedException;
use Sylius\ImportExport\Messenger\Command\ImportCommand;
use Sylius\ImportExport\Processor\BatchProcessor;
use Sylius\Resource\Doctrine\Persistence\RepositoryInterface;

class ImportCommandHandler
{
/** @param RepositoryInterface<ImportProcessInterface> $processRepository */
public function __construct(
protected RepositoryInterface $processRepository,
protected DenormalizerRegistryInterface $denormalizerRegistry,
protected EntityManagerInterface $entityManager,
protected BatchProcessor $batchProcessor,
) {
}

Expand All @@ -38,25 +39,22 @@ public function __invoke(ImportCommand $command): void
}

try {
$importedCount = 0;
$resourceClass = $process->getResource();
$denormalizer = $this->denormalizerRegistry->get($resourceClass);

foreach ($command->batchData as $recordData) {
$entity = $denormalizer->denormalize($recordData, $resourceClass);
$this->entityManager->persist($entity);

++$importedCount;
}

$this->entityManager->flush();
$importedCount = $this->batchProcessor->processBatch($process, $command->batchData);

$process->setBatchesCount($process->getBatchesCount() - 1);
$process->setImportedCount($process->getImportedCount() + $importedCount);

if ($process->getBatchesCount() <= 0) {
$process->setStatus('success');
}
} catch (ValidationFailedException $e) {
$this->entityManager->clear();
$process = $this->processRepository->find($command->processId);
if (null === $process) {
throw new ImportFailedException(sprintf('Process with uuid "%s" not found after validation failure.', $command->processId));
}
$process->setStatus('failed');
$process->setErrorMessage($e->getMessage());
} catch (\Throwable $e) {
$process->setStatus('failed');
$process->setErrorMessage($e->getMessage());
Expand Down
Loading