infrastructure for normalizing form config [WIP]

This commit is contained in:
Julien Fastré 2025-02-21 13:49:35 +01:00
parent 057c34610d
commit 1f1d38acef
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
10 changed files with 336 additions and 5 deletions

View File

@ -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());
}

View File

@ -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.

View File

@ -0,0 +1,123 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Export;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Form\Type\Export\AggregatorType;
use Chill\MainBundle\Form\Type\Export\ExportType;
use Chill\MainBundle\Form\Type\Export\FilterType;
use Chill\MainBundle\Repository\CenterRepositoryInterface;
/**
* @phpstan-type NormalizedData array{centers: list<int>, export: array{form: array<string, mixed>, version: int}, filters: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, aggregators: array<string, array{enabled: boolean, form: array<string, mixed>, version: int}>, pick_formatter: string, formatter: array{form: array<string, mixed>, 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),
];
}
}

View File

@ -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.

View File

@ -510,7 +510,7 @@ class ExportManager
$role
);
}
dump($centers);
dump($centers);
foreach ($centers as $center) {
if (false === $this->authorizationChecker->isGranted($role, $center)) {
// debugging

View File

@ -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.
*

View File

@ -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;
}

View File

@ -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.

View File

@ -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,
]);

View File

@ -0,0 +1,181 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Tests\Export;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Export\ExportConfigNormalizer;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\ExportManager;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Repository\CenterRepositoryInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
/**
* @internal
*
* @coversNothing
*/
class ExportConfigNormalizerTest extends TestCase
{
use ProphecyTrait;
public function testNormalizeConfig(): void
{
$filterEnabled = $this->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);
}
}