From 1f1d38aceffed77192655b4120f4ff96e2d8b361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 21 Feb 2025 13:49:35 +0100 Subject: [PATCH] infrastructure for normalizing form config [WIP] --- .../Controller/ExportController.php | 3 +- .../Export/AggregatorInterface.php | 6 + .../Export/ExportConfigNormalizer.php | 123 ++++++++++++ .../Export/ExportInterface.php | 8 +- .../ChillMainBundle/Export/ExportManager.php | 2 +- .../Export/FilterInterface.php | 6 + .../Export/FormatterInterface.php | 6 + .../Export/ModifierInterface.php | 2 +- .../Form/Type/Export/AggregatorType.php | 4 +- .../Export/ExportConfigNormalizerTest.php | 181 ++++++++++++++++++ 10 files changed, 336 insertions(+), 5 deletions(-) create mode 100644 src/Bundle/ChillMainBundle/Export/ExportConfigNormalizer.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Export/ExportConfigNormalizerTest.php diff --git a/src/Bundle/ChillMainBundle/Controller/ExportController.php b/src/Bundle/ChillMainBundle/Controller/ExportController.php index 28f2bbe1a..0b45d5a9a 100644 --- a/src/Bundle/ChillMainBundle/Controller/ExportController.php +++ b/src/Bundle/ChillMainBundle/Controller/ExportController.php @@ -148,7 +148,8 @@ class ExportController extends AbstractController new Envelope( new ExportRequestGenerationMessage($exportGeneration), [new AuthenticationStamp($this->security->getUser())] - )); + ) + ); return new Response('Ok: '.$exportGeneration->getId()->toString()); } diff --git a/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php b/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php index e849dec07..ab6017d6a 100644 --- a/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php +++ b/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php @@ -31,6 +31,12 @@ interface AggregatorInterface extends ModifierInterface */ public function getFormDefaultData(): array; + public function normalizeFormData(array $formData): array; + + public function denormalizeFormData(array $formData, int $fromVersion): array; + + public function getVersion(): int; + /** * get a callable which will be able to transform the results into * viewable and understable string. diff --git a/src/Bundle/ChillMainBundle/Export/ExportConfigNormalizer.php b/src/Bundle/ChillMainBundle/Export/ExportConfigNormalizer.php new file mode 100644 index 000000000..fddbac7f0 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/ExportConfigNormalizer.php @@ -0,0 +1,123 @@ +, export: array{form: array, version: int}, filters: array, version: int}>, aggregators: array, version: int}>, pick_formatter: string, formatter: array{form: array, version: int}} + */ +class ExportConfigNormalizer +{ + public function __construct( + private readonly ExportManager $exportManager, + private readonly CenterRepositoryInterface $centerRepository, + ) {} + + /** + * @return NormalizedData + */ + public function normalizeConfig(string $exportAlias, array $formData): array + { + $exportData = $formData[ExportType::EXPORT_KEY]; + $export = $this->exportManager->getExport($exportAlias); + + $serialized = [ + 'export' => [ + 'form' => $export->normalizeFormData($exportData), + 'version' => $export->getVersion(), + ], + ]; + + $serialized['centers'] = array_values( + array_map(static fn (Center $center) => $center->getId(), $formData['centers']) + ); + + + $filtersSerialized = []; + foreach ($formData[ExportType::FILTER_KEY] as $alias => $filterData) { + $filter = $this->exportManager->getFilter($alias); + $filtersSerialized[$alias][FilterType::ENABLED_FIELD] = (bool) $filterData[FilterType::ENABLED_FIELD]; + if ($filterData[FilterType::ENABLED_FIELD]) { + $filtersSerialized[$alias]['form'] = $filter->normalizeFormData($filterData['form']); + $filtersSerialized[$alias]['version'] = $filter->getVersion(); + + } + } + $serialized['filters'] = $filtersSerialized; + + $aggregatorsSerialized = []; + foreach ($formData[ExportType::AGGREGATOR_KEY] as $alias => $aggregatorData) { + $aggregator = $this->exportManager->getAggregator($alias); + $aggregatorsSerialized[$alias][FilterType::ENABLED_FIELD] = (bool) $aggregatorData[AggregatorType::ENABLED_FIELD]; + if ($aggregatorData[AggregatorType::ENABLED_FIELD]) { + $aggregatorsSerialized[$alias]['form'] = $aggregator->normalizeFormData($aggregatorData['form']); + $aggregatorsSerialized[$alias]['version'] = $aggregator->getVersion(); + } + } + $serialized['aggregators'] = $aggregatorsSerialized; + + $serialized['pick_formatter'] = $formData['pick_formatter']; + $formatter = $this->exportManager->getFormatter($formData['pick_formatter']); + $serialized['formatter']['form'] = $formatter->normalizeFormData($formData['formatter']['form']); + $serialized['formatter']['version'] = $formatter->getVersion(); + + return $serialized; + } + + /** + * @param NormalizedData $serializedData + * @param bool $replaceDisabledByDefaultData if true, when a filter is not enabled, the formDefaultData is set + */ + public function denormalizeConfig(string $exportAlias, array $serializedData, bool $replaceDisabledByDefaultData = false): array + { + $export = $this->exportManager->getExport($exportAlias); + $formater = $this->exportManager->getFormatter($serializedData['pick_formatter']); + + $filtersConfig = []; + foreach ($serializedData['filters'] as $alias => $filterData) { + $aggregator = $this->exportManager->getFilter($alias); + $filtersConfig[$alias]['enabled'] = $filterData['enabled']; + + if ($filterData['enabled']) { + $filtersConfig[$alias]['form'] = $aggregator->denormalizeFormData($filterData['form'], $filterData['version']); + } elseif ($replaceDisabledByDefaultData) { + $filtersConfig[$alias]['form'] = $aggregator->getFormDefaultData(); + } + } + + $aggregatorsConfig = []; + foreach ($serializedData['aggregators'] as $alias => $aggregatorData) { + $aggregator = $this->exportManager->getAggregator($alias); + $aggregatorsConfig[$alias]['enabled'] = $aggregatorData['enabled']; + + if ($aggregatorData['enabled']) { + $aggregatorsConfig[$alias]['form'] = $aggregator->denormalizeFormData($aggregatorData['form'], $aggregatorData['version']); + } elseif ($replaceDisabledByDefaultData) { + $aggregatorsConfig[$alias]['form'] = $aggregator->getFormDefaultData(); + } + } + + return [ + 'export' => $export->denormalizeFormData($serializedData['export']['form'], $serializedData['export']['version']), + 'filters' => $filtersConfig, + 'aggregators' => $aggregatorsConfig, + 'pick_formatter' => $serializedData['pick_formatter'], + 'formatter' => ['form' => $formater->denormalizeFormData($serializedData['formatter']['form'], $serializedData['formatter']['version'])], + 'centers' => array_filter(array_map(fn (int $id) => $this->centerRepository->find($id), $serializedData['centers']), fn ($item) => null !== $item), + ]; + } +} diff --git a/src/Bundle/ChillMainBundle/Export/ExportInterface.php b/src/Bundle/ChillMainBundle/Export/ExportInterface.php index a9d3efd13..e5c2ce9eb 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ExportInterface.php @@ -147,7 +147,13 @@ interface ExportInterface extends ExportElementInterface * * @return Q the query to execute */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []); + public function initiateQuery(array $requiredModifiers, array $acl, array $data/* , ExportGenerationContext $context */); + + public function normalizeFormData(array $formData): array; + + public function denormalizeFormData(array $formData, int $fromVersion): array; + + public function getVersion(): int; /** * Return the required Role to execute the Export. diff --git a/src/Bundle/ChillMainBundle/Export/ExportManager.php b/src/Bundle/ChillMainBundle/Export/ExportManager.php index 103441408..da4d68d93 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportManager.php +++ b/src/Bundle/ChillMainBundle/Export/ExportManager.php @@ -510,7 +510,7 @@ class ExportManager $role ); } -dump($centers); + dump($centers); foreach ($centers as $center) { if (false === $this->authorizationChecker->isGranted($role, $center)) { // debugging diff --git a/src/Bundle/ChillMainBundle/Export/FilterInterface.php b/src/Bundle/ChillMainBundle/Export/FilterInterface.php index 7786a5e23..813f9ac87 100644 --- a/src/Bundle/ChillMainBundle/Export/FilterInterface.php +++ b/src/Bundle/ChillMainBundle/Export/FilterInterface.php @@ -38,6 +38,12 @@ interface FilterInterface extends ModifierInterface */ public function getFormDefaultData(): array; + public function normalizeFormData(array $formData): array; + + public function denormalizeFormData(array $formData, int $fromVersion): array; + + public function getVersion(): int; + /** * Describe the filtering action. * diff --git a/src/Bundle/ChillMainBundle/Export/FormatterInterface.php b/src/Bundle/ChillMainBundle/Export/FormatterInterface.php index 7b108b376..02e073f65 100644 --- a/src/Bundle/ChillMainBundle/Export/FormatterInterface.php +++ b/src/Bundle/ChillMainBundle/Export/FormatterInterface.php @@ -65,4 +65,10 @@ interface FormatterInterface ); public function getType(); + + public function normalizeFormData(array $formData): array; + + public function denormalizeFormData(array $formData, int $fromVersion): array; + + public function getVersion(): int; } diff --git a/src/Bundle/ChillMainBundle/Export/ModifierInterface.php b/src/Bundle/ChillMainBundle/Export/ModifierInterface.php index d3ec9de83..18e642050 100644 --- a/src/Bundle/ChillMainBundle/Export/ModifierInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ModifierInterface.php @@ -37,7 +37,7 @@ interface ModifierInterface extends ExportElementInterface * @param QueryBuilder $qb the QueryBuilder initiated by the Export (and eventually modified by other Modifiers) * @param mixed[] $data the data from the Form (builded by buildForm) */ - public function alterQuery(QueryBuilder $qb, $data); + public function alterQuery(QueryBuilder $qb, $data/* , ExportGenerationContext $exportGenerationContext */); /** * On which type of Export this ModifiersInterface may apply. diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/AggregatorType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/AggregatorType.php index 72e501108..6ee61d945 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/AggregatorType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/AggregatorType.php @@ -21,13 +21,15 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class AggregatorType extends AbstractType { + public const ENABLED_FIELD = 'enabled'; + public function buildForm(FormBuilderInterface $builder, array $options): void { $exportManager = $options['export_manager']; $aggregator = $exportManager->getAggregator($options['aggregator_alias']); $builder - ->add('enabled', CheckboxType::class, [ + ->add(self::ENABLED_FIELD, CheckboxType::class, [ 'value' => true, 'required' => false, ]); diff --git a/src/Bundle/ChillMainBundle/Tests/Export/ExportConfigNormalizerTest.php b/src/Bundle/ChillMainBundle/Tests/Export/ExportConfigNormalizerTest.php new file mode 100644 index 000000000..3a1d9a5d6 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/ExportConfigNormalizerTest.php @@ -0,0 +1,181 @@ +prophesize(FilterInterface::class); + $filterEnabled->normalizeFormData(['test' => '0'])->shouldBeCalled()->willReturn(['test' => '0']); + $filterEnabled->getVersion()->willReturn(1); + $filterDisabled = $this->prophesize(FilterInterface::class); + $filterDisabled->normalizeFormData(['default' => '0'])->shouldNotBeCalled(); + + $aggregatorEnabled = $this->prophesize(AggregatorInterface::class); + $aggregatorEnabled->normalizeFormData(['test' => '0'])->shouldBeCalled()->willReturn(['test' => '0']); + $aggregatorEnabled->getVersion()->willReturn(1); + $aggregatorDisabled = $this->prophesize(AggregatorInterface::class); + $aggregatorDisabled->normalizeFormData(['default' => '0'])->shouldNotBeCalled(); + + $export = $this->prophesize(ExportInterface::class); + $export->normalizeFormData(['test' => '0'])->shouldBeCalled()->willReturn(['test' => '0']); + $export->getVersion()->willReturn(1); + + $formatter = $this->prophesize(FormatterInterface::class); + $formatter->normalizeFormData(['test' => '0'])->shouldBeCalled()->willReturn(['test' => '0']); + $formatter->getVersion()->willReturn(1); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getFormatter('xlsx')->shouldBeCalled()->willReturn($formatter->reveal()); + $exportManager->getFilter('filterEnabled')->willReturn($filterEnabled->reveal()); + $exportManager->getFilter('filterDisabled')->willReturn($filterDisabled->reveal()); + $exportManager->getAggregator('aggregatorEnabled')->willReturn($aggregatorEnabled->reveal()); + $exportManager->getAggregator('aggregatorDisabled')->willReturn($aggregatorDisabled->reveal()); + $exportManager->getExport('export')->willReturn($export->reveal()); + + $center = $this->prophesize(Center::class); + $center->getId()->willReturn(10); + + $formData = [ + 'centers' => [$center->reveal()], + 'export' => ['test' => '0'], + 'filters' => [ + 'filterEnabled' => ['enabled' => true, 'form' => ['test' => '0']], + 'filterDisabled' => ['enabled' => false, 'form' => ['default' => '0']], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => ['test' => '0']], + 'aggregatorDisabled' => ['enabled' => false, 'form' => ['default' => '0']], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => [ + 'form' => ['test' => '0'], + ], + ]; + + $expected = [ + 'export' => ['form' => ['test' => '0'], 'version' => 1], + 'filters' => [ + 'filtersEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1], + 'filterDisabled' => ['enabled' => false], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1], + 'aggregatorDisabled' => ['enabled' => false], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => [ + 'form' => ['test' => '0'], + 'version' => 1, + ], + 'centers' => [10], + ]; + + $exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $this->prophesize(CenterRepositoryInterface::class)->reveal()); + + $actual = $exportConfigNormalizer->normalizeConfig('export', $formData); + + self::assertEqualsCanonicalizing($expected, $actual); + } + + public function testDenormalizeConfig(): void + { + $filterEnabled = $this->prophesize(FilterInterface::class); + $filterEnabled->denormalizeFormData(['test' => '0'], 1)->shouldBeCalled()->willReturn(['test' => '0']); + $filterDisabled = $this->prophesize(FilterInterface::class); + $filterDisabled->denormalizeFormData(Argument::any(), Argument::type('int'))->shouldNotBeCalled(); + $filterDisabled->getFormDefaultData()->willReturn(['default' => '0']); + + $aggregatorEnabled = $this->prophesize(AggregatorInterface::class); + $aggregatorEnabled->denormalizeFormData(['test' => '0'], 1)->shouldBeCalled()->willReturn(['test' => '0']); + $aggregatorDisabled = $this->prophesize(AggregatorInterface::class); + $aggregatorDisabled->denormalizeFormData(Argument::any(), Argument::type('int'))->shouldNotBeCalled(); + $aggregatorDisabled->getFormDefaultData()->willReturn(['default' => '0']); + + $export = $this->prophesize(ExportInterface::class); + $export->denormalizeFormData(['test' => '0'], 1)->shouldBeCalled()->willReturn(['test' => '0']); + + $formatter = $this->prophesize(FormatterInterface::class); + $formatter->denormalizeFormData(['test' => '0'], 1)->shouldBeCalled()->willReturn(['test' => '0']); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getFormatter('xlsx')->shouldBeCalled()->willReturn($formatter->reveal()); + $exportManager->getFilter('filterEnabled')->willReturn($filterEnabled->reveal()); + $exportManager->getFilter('filterDisabled')->willReturn($filterDisabled->reveal()); + $exportManager->getAggregator('aggregatorEnabled')->willReturn($aggregatorEnabled->reveal()); + $exportManager->getAggregator('aggregatorDisabled')->willReturn($aggregatorDisabled->reveal()); + $exportManager->getExport('export')->willReturn($export->reveal()); + + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $centerRepository->find(10)->willReturn($center = new Center()); + + $serialized = [ + 'centers' => [10], + 'export' => ['form' => ['test' => '0'], 'version' => 1], + 'filters' => [ + 'filterEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1], + 'filterDisabled' => ['enabled' => false], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1], + 'aggregatorDisabled' => ['enabled' => false], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => [ + 'form' => ['test' => '0'], + 'version' => 1, + ], + ]; + + $expected = [ + 'export' => ['test' => '0'], + 'filters' => [ + 'filterEnabled' => ['enabled' => true, 'form' => ['test' => '0']], + 'filterDisabled' => ['enabled' => false, 'form' => ['default' => '0']], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => ['test' => '0']], + 'aggregatorDisabled' => ['enabled' => false, 'form' => ['default' => '0']], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => [ + 'form' => ['test' => '0'], + ], + 'centers' => [$center], + ]; + + $exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $centerRepository->reveal()); + $actual = $exportConfigNormalizer->denormalizeConfig('export', $serialized, true); + + self::assertEquals($expected, $actual); + } +}