diff --git a/config/services.xml b/config/services.xml index 225a6c2..c44ec90 100644 --- a/config/services.xml +++ b/config/services.xml @@ -5,70 +5,21 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd" > - - Sylius\GridImportExport\Form\Type\ExportResourceType - %kernel.project_dir%/etc/export - - - - admin - - - sylius.product - sylius.payment - sylius.shipment - sylius.order - sylius.customer - sylius_grid_import_export.process - - - + + + - - %sylius_grid_import_export.export_files_directory% - - - - - %sylius_grid_import_export.export_files_directory% - - - - - - - - - - - - - - - - - - + - + - - - - - - - - - - %sylius_import_export.export.form_class% @@ -93,7 +44,9 @@ - + + + %sylius_import_export.export.form_class% @@ -105,28 +58,6 @@ - - - - %sylius_import_export.grid.export.allowed_sections% - %sylius_import_export.grid.export.allowed_resources% - - - - - - - - - - - - - diff --git a/config/services/exporter.xml b/config/services/exporter.xml new file mode 100644 index 0000000..41cb15a --- /dev/null +++ b/config/services/exporter.xml @@ -0,0 +1,28 @@ + + + + + %kernel.project_dir%/etc/export + + + + + %sylius_grid_import_export.export_files_directory% + + + + + %sylius_grid_import_export.export_files_directory% + + + + + + + + + diff --git a/config/services/form.xml b/config/services/form.xml new file mode 100644 index 0000000..bfbb97b --- /dev/null +++ b/config/services/form.xml @@ -0,0 +1,23 @@ + + + + + Sylius\GridImportExport\Form\Type\ExportResourceType + + + + + + + + + + + + + + diff --git a/config/services/grid.xml b/config/services/grid.xml new file mode 100644 index 0000000..226d51e --- /dev/null +++ b/config/services/grid.xml @@ -0,0 +1,31 @@ + + + + + + + %sylius_import_export.export.resources% + + + + + + + + + + + + + + + + diff --git a/config/services/provider.xml b/config/services/provider.xml new file mode 100644 index 0000000..0e1bdc9 --- /dev/null +++ b/config/services/provider.xml @@ -0,0 +1,43 @@ + + + + + + %sylius_import_export.export.resources% + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/phpstan.neon b/phpstan.neon index d543272..5a215c4 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,3 +7,6 @@ parameters: ignoreErrors: - identifier: missingType.generics - identifier: missingType.iterableValue + + excludePaths: + - '%currentWorkingDirectory%/src/DependencyInjection/Configuration.php' diff --git a/src/Controller/ExportAction.php b/src/Controller/ExportAction.php index 4137e52..5da76f4 100644 --- a/src/Controller/ExportAction.php +++ b/src/Controller/ExportAction.php @@ -13,8 +13,10 @@ namespace Sylius\GridImportExport\Controller; +use Sylius\Bundle\ResourceBundle\Controller\ParametersParserInterface; +use Sylius\Component\Grid\Provider\GridProviderInterface; use Sylius\GridImportExport\Messenger\Command\ExportCommand; -use Sylius\GridImportExport\Provider\ResourcesIdsProviderInterface; +use Sylius\GridImportExport\Provider\ResourceIds\ResourcesIdsProviderInterface; use Sylius\Resource\Metadata\RegistryInterface; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -26,6 +28,8 @@ final class ExportAction { public function __construct( private RegistryInterface $metadataRegistry, + private GridProviderInterface $gridProvider, + private ParametersParserInterface $parametersParser, private ResourcesIdsProviderInterface $resourcesIdsProvider, private FormFactoryInterface $formFactory, private MessageBusInterface $commandBus, @@ -44,28 +48,27 @@ public function __invoke(Request $request, string $grid): Response $format = $data['format']; $resourceClass = $data['resourceClass']; - $resourceIds = $this->resolveResourceIds($request, $data); + $metadata = $this->metadataRegistry->getByClass($resourceClass); + $gridConfiguration = $this->gridProvider->get($grid); + + $resourceIds = $this->resourcesIdsProvider->getResourceIds( + metadata: $metadata, + context: ['request' => $request, 'ids' => $data['ids'] ?? []], + ); + + $parameters = $this->parametersParser->parseRequestValues( + $gridConfiguration->getDriverConfiguration(), + $request, + ); $this->commandBus->dispatch(new ExportCommand( - resource: $resourceClass, + resource: $metadata->getAlias(), + grid: $grid, format: $format, resourceIds: $resourceIds, + parameters: $parameters, )); return new RedirectResponse($request->headers->get('referer') ?? '/'); } - - private function resolveResourceIds(Request $request, array $formData): array - { - if (isset($formData['ids']) && [] !== $formData['ids']) { - return $formData['ids']; - } - - $metadata = $this->metadataRegistry->getByClass($formData['resourceClass']); - - return $this->resourcesIdsProvider->getResourceIds( - metadata: $metadata, - context: ['request' => $request], - ); - } } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php new file mode 100644 index 0000000..4ff61d1 --- /dev/null +++ b/src/DependencyInjection/Configuration.php @@ -0,0 +1,75 @@ +getRootNode(); + + $this->addExportConfiguration($rootNode); + + return $treeBuilder; + } + + private function addExportConfiguration(ArrayNodeDefinition $node): void + { + $node + ->children() + ->arrayNode('export') + ->isRequired() + ->children() + ->scalarNode('default_provider') + ->defaultValue('sylius_import_export.provider.resource_data.grid') + ->cannotBeEmpty() + ->end() + ->scalarNode('default_section') + ->defaultValue('admin') + ->cannotBeEmpty() + ->end() + ->arrayNode('resources') + ->useAttributeAsKey('name') + ->normalizeKeys(false) + ->arrayPrototype() + ->beforeNormalization() + ->ifNull() + ->then(function () { + return []; + }) + ->end() + ->children() + ->scalarNode('provider') + ->defaultNull() + ->end() + ->arrayNode('sections') + ->scalarPrototype()->end() + ->defaultValue([]) + ->end() + ->end() + ->end() + ->end() // resources + ->end() + ->end() // export + ->end() + ; + } +} diff --git a/src/DependencyInjection/SyliusGridImportExportExtension.php b/src/DependencyInjection/SyliusGridImportExportExtension.php index ba11ba6..bf27a71 100644 --- a/src/DependencyInjection/SyliusGridImportExportExtension.php +++ b/src/DependencyInjection/SyliusGridImportExportExtension.php @@ -22,7 +22,29 @@ final class SyliusGridImportExportExtension extends Extension { public function load(array $configs, ContainerBuilder $container): void { + $configuration = $this->processConfiguration(new Configuration(), $configs); + + $this->processExportConfig($container, $configuration); + $loader = new XmlFileLoader($container, new FileLocator(dirname(__DIR__, 2) . '/config/')); $loader->load('services.xml'); } + + private function processExportConfig(ContainerBuilder $container, array &$config): void + { + $defaultProvider = $config['export']['default_provider']; + $defaultSection = $config['export']['default_section']; + + foreach ($config['export']['resources'] as $name => &$resource) { + if (null === $resource['provider']) { + $resource['provider'] = $defaultProvider; + } + if ([] === $resource['sections']) { + $resource['sections'][] = $defaultSection; + } + } + + $container->setParameter('sylius_import_export.export.default_provider', $defaultProvider); + $container->setParameter('sylius_import_export.export.resources', $config['export']['resources']); + } } diff --git a/src/Grid/Checker/ExportableChecker.php b/src/Grid/Checker/ExportableChecker.php index 73f25af..6887f29 100644 --- a/src/Grid/Checker/ExportableChecker.php +++ b/src/Grid/Checker/ExportableChecker.php @@ -15,46 +15,40 @@ use Sylius\Component\Grid\Definition\Grid; use Sylius\Resource\Metadata\RegistryInterface; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\RequestStack; -final class ExportableChecker implements ExportableCheckerInterface +final readonly class ExportableChecker implements ExportableCheckerInterface { - /** - * @param array $allowedSections - * @param array $allowedResources - */ + /** @param array $exportResourcesConfig */ public function __construct( - private RequestStack $requestStack, private RegistryInterface $resourceRegistry, - private array $allowedSections, - private array $allowedResources, + private array $exportResourcesConfig, ) { } - public function canBeExported(Grid $grid): bool + public function canBeExported(Grid $grid, object|string|null $section): bool { $resourceClass = $grid->getDriverConfiguration()['class'] ?? null; if (null === $resourceClass) { return false; } - $request = $this->requestStack->getMainRequest(); - if (!$request instanceof Request) { - return false; + if (null === $section) { + return true; } - if (!$request->attributes->has('_sylius')) { - return false; - } + $resourceAlias = $this->resourceRegistry->getByClass($resourceClass)->getAlias(); - $syliusAttributes = $request->attributes->all()['_sylius']; - if (!in_array($syliusAttributes['section'] ?? null, $this->allowedSections)) { + $resourceConfig = $this->exportResourcesConfig[$resourceAlias] ?? false; + if (false === $resourceConfig) { return false; } - $resourceMetadata = $this->resourceRegistry->getByClass($resourceClass); + foreach ($resourceConfig['sections'] as $configSection) { + if (is_a($section, $configSection, true) || $section === $configSection) { + return true; + } + } - return in_array($resourceMetadata->getAlias(), $this->allowedResources); + return false; } } diff --git a/src/Grid/Checker/ExportableCheckerInterface.php b/src/Grid/Checker/ExportableCheckerInterface.php index 0c8abeb..10da387 100644 --- a/src/Grid/Checker/ExportableCheckerInterface.php +++ b/src/Grid/Checker/ExportableCheckerInterface.php @@ -17,5 +17,5 @@ interface ExportableCheckerInterface { - public function canBeExported(Grid $grid): bool; + public function canBeExported(Grid $grid, object|string|null $section): bool; } diff --git a/src/Grid/Listener/ExportActionAdminGridListener.php b/src/Grid/Listener/ExportActionAdminGridListener.php index 543f39c..19ff001 100644 --- a/src/Grid/Listener/ExportActionAdminGridListener.php +++ b/src/Grid/Listener/ExportActionAdminGridListener.php @@ -13,6 +13,7 @@ namespace Sylius\GridImportExport\Grid\Listener; +use Sylius\Bundle\CoreBundle\SectionResolver\SectionProviderInterface; use Sylius\Bundle\GridBundle\Builder\ActionGroup\ActionGroupInterface; use Sylius\Bundle\GridBundle\Doctrine\ORM\Driver as ORMDriver; use Sylius\Component\Grid\Definition\Action; @@ -20,12 +21,15 @@ use Sylius\Component\Grid\Definition\Grid; use Sylius\Component\Grid\Event\GridDefinitionConverterEvent; use Sylius\GridImportExport\Grid\Checker\ExportableCheckerInterface; +use Symfony\Component\HttpFoundation\RequestStack; final readonly class ExportActionAdminGridListener { private const EXPORT_ACTION_NAME = 'export'; public function __construct( + private RequestStack $requestStack, + private ?SectionProviderInterface $sectionProvider, private ExportableCheckerInterface $exportableChecker, ) { } @@ -37,10 +41,14 @@ public function addExportActions(GridDefinitionConverterEvent $event): void return; } - if (!$this->exportableChecker->canBeExported($grid)) { + if ( + !$this->exportableChecker->canBeExported($grid, $this->sectionProvider?->getSection()) && + !$this->exportableChecker->canBeExported($grid, $this->getRouteSection()) + ) { return; } + // TODO: Introduce config for each case, default both // $this->addInActionGroup($grid, ActionGroupInterface::MAIN_GROUP); $this->addInActionGroup($grid, ActionGroupInterface::BULK_GROUP); } @@ -60,4 +68,14 @@ private function addInActionGroup(Grid $grid, string $groupName): void $actionGroup->addAction($action); } + + private function getRouteSection(): ?string + { + $request = $this->requestStack->getMainRequest(); + if (null === $request) { + return null; + } + + return $request->attributes->all()['_sylius']['section'] ?? null; + } } diff --git a/src/Messenger/Command/ExportCommand.php b/src/Messenger/Command/ExportCommand.php index 23a2249..59c4cd4 100644 --- a/src/Messenger/Command/ExportCommand.php +++ b/src/Messenger/Command/ExportCommand.php @@ -15,11 +15,12 @@ class ExportCommand { - /** @param class-string $resource */ public function __construct( public string $resource, + public string $grid, public string $format, public array $resourceIds, + public array $parameters, ) { } } diff --git a/src/Messenger/Handler/ExportCommandHandler.php b/src/Messenger/Handler/ExportCommandHandler.php index ce0322e..2da4f34 100644 --- a/src/Messenger/Handler/ExportCommandHandler.php +++ b/src/Messenger/Handler/ExportCommandHandler.php @@ -17,9 +17,10 @@ use Sylius\GridImportExport\Exception\ExportFailedException; use Sylius\GridImportExport\Factory\ProcessFactoryInterface; use Sylius\GridImportExport\Messenger\Command\ExportCommand; -use Sylius\GridImportExport\Provider\ResourceDataProviderInterface; +use Sylius\GridImportExport\Provider\Registry\ResourceDataProviderRegistryInterface; use Sylius\GridImportExport\Resolver\ExporterResolverInterface; use Sylius\Resource\Doctrine\Persistence\RepositoryInterface; +use Sylius\Resource\Metadata\RegistryInterface; class ExportCommandHandler { @@ -27,9 +28,10 @@ class ExportCommandHandler * @param RepositoryInterface $processRepository */ public function __construct( + public RegistryInterface $metadataRegistry, public ProcessFactoryInterface $processFactory, public RepositoryInterface $processRepository, - public ResourceDataProviderInterface $resourceDataProvider, + public ResourceDataProviderRegistryInterface $dataProviderRegistry, public ExporterResolverInterface $exporterResolver, ) { } @@ -42,10 +44,12 @@ public function __invoke(ExportCommand $command): void $this->processRepository->add($process); - $data = $this->resourceDataProvider->getData( - $command->resource, - $command->resourceIds, - ); + $resourceMetadata = $this->metadataRegistry->get($command->resource); + + $data = $this->dataProviderRegistry + ->getProvider($resourceMetadata) + ->getData($resourceMetadata, $command->grid, $command->resourceIds, $command->parameters) + ; try { $outputPath = $resolver->export($data); diff --git a/src/Provider/Registry/ResourceDataProviderRegistry.php b/src/Provider/Registry/ResourceDataProviderRegistry.php new file mode 100644 index 0000000..42e387a --- /dev/null +++ b/src/Provider/Registry/ResourceDataProviderRegistry.php @@ -0,0 +1,51 @@ + $exportResourcesConfig + * @param iterable $resourceDataProviders + */ + public function __construct( + private array $exportResourcesConfig, + private iterable $resourceDataProviders, + ) { + } + + public function getProvider(MetadataInterface $resourceMetadata): ResourceDataProviderInterface + { + $resourceAlias = $resourceMetadata->getAlias(); + $resourceConfig = $this->exportResourcesConfig[$resourceAlias] ?? null; + if (null === $resourceConfig) { + throw new ProviderException(sprintf( + 'Provider configuration for resource "%s" is missing', + $resourceAlias, + )); + } + + foreach ($this->resourceDataProviders as $serviceId => $provider) { + if ($serviceId === $resourceConfig['provider']) { + return $provider; + } + } + + throw new ProviderException(sprintf('There is not data provider for resource "%s"', $resourceAlias)); + } +} diff --git a/src/Provider/Registry/ResourceDataProviderRegistryInterface.php b/src/Provider/Registry/ResourceDataProviderRegistryInterface.php new file mode 100644 index 0000000..462d21f --- /dev/null +++ b/src/Provider/Registry/ResourceDataProviderRegistryInterface.php @@ -0,0 +1,22 @@ +> */ private static array $resourceFieldsMetadata = []; @@ -30,10 +31,10 @@ public function __construct( ) { } - public function getData(string $resource, array $resourceIds): array + public function getData(MetadataInterface $resource, string $gridCode, array $resourceIds, array $parameters): array { - $metadata = $this->getResourceMetadata($resource); - $scalarFieldsMetadata = $this->getResourceScalarFieldsData($metadata, $resource); + $metadata = $this->getResourceMetadata($resource->getClass('model')); + $scalarFieldsMetadata = $this->getResourceScalarFieldsData($metadata, $resource->getClass('model')); if (empty($scalarFieldsMetadata)) { return []; } diff --git a/src/Provider/ResourceData/GridResourceDataProvider.php b/src/Provider/ResourceData/GridResourceDataProvider.php new file mode 100644 index 0000000..050e7fe --- /dev/null +++ b/src/Provider/ResourceData/GridResourceDataProvider.php @@ -0,0 +1,52 @@ +gridProvider->get($gridCode); + if (ORMDriver::NAME !== $grid->getDriver()) { + throw new ProviderException(sprintf( + 'This provider supports only the "%s" grid driver, "%s" configured for grid "%s".', + ORMDriver::NAME, + $grid->getDriver(), + $gridCode, + )); + } + + $grid->setDriverConfiguration($parameters); + + /** @var ORMDataSource $dataSource */ + $dataSource = $this->gridDataSourceProvider->getDataSource($grid, new Parameters($parameters)); + $dataSource->restrict($dataSource->getExpressionBuilder()->in('id', $resourceIds)); + + return $dataSource->getQueryBuilder()->getQuery()->getArrayResult(); + } +} diff --git a/src/Provider/ResourceDataProviderInterface.php b/src/Provider/ResourceData/ResourceDataProviderInterface.php similarity index 65% rename from src/Provider/ResourceDataProviderInterface.php rename to src/Provider/ResourceData/ResourceDataProviderInterface.php index 5a159e0..d4635e9 100644 --- a/src/Provider/ResourceDataProviderInterface.php +++ b/src/Provider/ResourceData/ResourceDataProviderInterface.php @@ -11,19 +11,20 @@ declare(strict_types=1); -namespace Sylius\GridImportExport\Provider; +namespace Sylius\GridImportExport\Provider\ResourceData; use Sylius\GridImportExport\Exception\ProviderException; +use Sylius\Resource\Metadata\MetadataInterface; interface ResourceDataProviderInterface { /** - * @param class-string $resource * @param mixed[] $resourceIds + * @param mixed[] $parameters * * @return array> * * @throws ProviderException */ - public function getData(string $resource, array $resourceIds): array; + public function getData(MetadataInterface $resource, string $gridCode, array $resourceIds, array $parameters): array; } diff --git a/src/Provider/ResourceIds/CompositeResourceIdsProvider.php b/src/Provider/ResourceIds/CompositeResourceIdsProvider.php new file mode 100644 index 0000000..cbc1f05 --- /dev/null +++ b/src/Provider/ResourceIds/CompositeResourceIdsProvider.php @@ -0,0 +1,45 @@ + $resourceIdsProviders */ + public function __construct(private iterable $resourceIdsProviders) + { + } + + public function getResourceIds(MetadataInterface $metadata, array $context = []): array + { + foreach ($this->resourceIdsProviders as $idsProvider) { + if ($idsProvider->supports($metadata, $context)) { + return $idsProvider->getResourceIds($metadata, $context); + } + } + + throw new ProviderException(sprintf( + 'There is no resources ids provider for resource %s and context %s', + $metadata->getAlias(), + json_encode($context), + )); + } + + public function supports(MetadataInterface $metadata, array $context = []): bool + { + return true; + } +} diff --git a/src/Provider/RequestBasedResourcesIdsProvider.php b/src/Provider/ResourceIds/RequestBasedResourcesIdsProvider.php similarity index 86% rename from src/Provider/RequestBasedResourcesIdsProvider.php rename to src/Provider/ResourceIds/RequestBasedResourcesIdsProvider.php index 7e290fc..8378ea1 100644 --- a/src/Provider/RequestBasedResourcesIdsProvider.php +++ b/src/Provider/ResourceIds/RequestBasedResourcesIdsProvider.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Sylius\GridImportExport\Provider; +namespace Sylius\GridImportExport\Provider\ResourceIds; use Doctrine\ORM\EntityManagerInterface; use Pagerfanta\Pagerfanta; @@ -32,13 +32,19 @@ public function __construct( ) { } + /** @param array|array{request: Request} $context */ public function getResourceIds(MetadataInterface $metadata, array $context = []): array { - if (!isset($context['request']) || !($request = $context['request']) instanceof Request) { + if (!$this->supports($metadata, $context)) { throw new ProviderException('Request is missing from the context.'); } - return $this->doGetResourceIds($metadata, $request); + return $this->doGetResourceIds($metadata, $context['request']); + } + + public function supports(MetadataInterface $metadata, array $context = []): bool + { + return isset($context['request']) && $context['request'] instanceof Request; } private function doGetResourceIds(MetadataInterface $metadata, Request $request): array diff --git a/src/Provider/ResourcesIdsProviderInterface.php b/src/Provider/ResourceIds/ResourcesIdsProviderInterface.php similarity index 64% rename from src/Provider/ResourcesIdsProviderInterface.php rename to src/Provider/ResourceIds/ResourcesIdsProviderInterface.php index 97920d6..ce8286e 100644 --- a/src/Provider/ResourcesIdsProviderInterface.php +++ b/src/Provider/ResourceIds/ResourcesIdsProviderInterface.php @@ -11,11 +11,15 @@ declare(strict_types=1); -namespace Sylius\GridImportExport\Provider; +namespace Sylius\GridImportExport\Provider\ResourceIds; +use Sylius\GridImportExport\Exception\ProviderException; use Sylius\Resource\Metadata\MetadataInterface; interface ResourcesIdsProviderInterface { + /** @throws ProviderException */ public function getResourceIds(MetadataInterface $metadata, array $context = []): array; + + public function supports(MetadataInterface $metadata, array $context = []): bool; } diff --git a/src/Provider/ResourceIds/StaticResourceIdsProvider.php b/src/Provider/ResourceIds/StaticResourceIdsProvider.php new file mode 100644 index 0000000..b592bf6 --- /dev/null +++ b/src/Provider/ResourceIds/StaticResourceIdsProvider.php @@ -0,0 +1,30 @@ +|array{ids: array} $context */ + public function getResourceIds(MetadataInterface $metadata, array $context = []): array + { + return $context['ids']; + } + + public function supports(MetadataInterface $metadata, array $context = []): bool + { + return isset($context['ids']) && [] !== $context['ids']; + } +} diff --git a/tests/TestApplication/config/config.yaml b/tests/TestApplication/config/config.yaml index f07d71b..f1187d6 100644 --- a/tests/TestApplication/config/config.yaml +++ b/tests/TestApplication/config/config.yaml @@ -8,3 +8,18 @@ parameters: twig: paths: '%kernel.project_dir%/../../../tests/TestApplication/templates': ~ + +sylius_grid_import_export: + export: + default_provider: 'sylius_import_export.provider.resource_data.grid' + default_section: 'admin' + resources: + sylius.order: + sections: + - 'Sylius\Bundle\AdminBundle\SectionResolver\AdminSection' + sylius.customer: + provider: 'sylius_import_export.provider.resource_data.dbal' + sylius.promotion: ~ + sylius.product: ~ + sylius.shipment: ~ + sylius_grid_import_export.process: ~