Generate export using denormalization

This commit is contained in:
Julien Fastré 2025-02-23 23:16:30 +01:00
parent 1f1d38acef
commit 2c812fc5fe
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
7 changed files with 457 additions and 328 deletions

View File

@ -86,7 +86,7 @@ class ExportGeneration implements TrackCreationInterface
return $this->options; return $this->options;
} }
public static function fromSavedExport(SavedExport $savedExport, null|\DateTimeImmutable $deletedAt = null): self public static function fromSavedExport(SavedExport $savedExport, ?\DateTimeImmutable $deletedAt = null): self
{ {
return new self($savedExport->getExportAlias(), $savedExport->getOptions(), $deletedAt); return new self($savedExport->getExportAlias(), $savedExport->getOptions(), $deletedAt);
} }

View File

@ -31,7 +31,13 @@ interface DirectExportInterface extends ExportElementInterface
* *
* @return FormattedExportGeneration * @return FormattedExportGeneration
*/ */
public function generate(array $acl, array $data = []): Response|FormattedExportGeneration; public function generate(array $acl, array $data, ExportGenerationContext $context): Response|FormattedExportGeneration;
public function normalizeFormData(array $formData): array;
public function denormalizeFormData(array $formData, int $fromVersion): array;
public function getVersion(): int;
/** /**
* get a description, which will be used in UI (and translated). * get a description, which will be used in UI (and translated).

View File

@ -11,42 +11,248 @@ declare(strict_types=1);
namespace Chill\MainBundle\Export; namespace Chill\MainBundle\Export;
use Chill\DocStoreBundle\Entity\StoredObject; use Chill\MainBundle\Entity\Center;
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
use Chill\MainBundle\Entity\ExportGeneration;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Doctrine\DBAL\LockMode; use Chill\MainBundle\Form\Type\Export\ExportType;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\QueryBuilder;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;
/**
* Generate a single export.
*/
final readonly class ExportGenerator final readonly class ExportGenerator
{ {
public function __construct( public function __construct(
private ExportManager $exportManager, private ExportManager $exportManager,
private StoredObjectManagerInterface $storedObjectManager, private ExportConfigNormalizer $configNormalizer,
private EntityManagerInterface $entityManager, private LoggerInterface $logger,
private ExportFormHelper $exportFormHelper,
) {} ) {}
public function generate(ExportGeneration $exportGeneration, User $user): void public function generate(string $exportAlias, array $configuration, ?User $byUser = null): FormattedExportGeneration
{ {
$this->entityManager->wrapInTransaction(function () use ($exportGeneration) { $data = $this->configNormalizer->denormalizeConfig($exportAlias, $configuration);
$object = $exportGeneration->getStoredObject(); $centers = $data['centers'];
$this->entityManager->refresh($exportGeneration, LockMode::PESSIMISTIC_WRITE);
$this->entityManager->refresh($object, LockMode::PESSIMISTIC_WRITE);
if (StoredObject::STATUS_PENDING !== $object->getStatus()) { $export = $this->exportManager->getExport($exportAlias);
return; $context = new ExportGenerationContext($byUser);
}
$generation = $this->exportManager->generateExport( if ($export instanceof DirectExportInterface) {
$exportGeneration->getExportAlias(), $generatedExport = $export->generate(
$centers = $this->exportFormHelper->savedExportDataToFormData($exportGeneration, 'centers'), $this->buildCenterReachableScopes($centers),
$this->exportFormHelper->savedExportDataToFormData($exportGeneration, 'export', ['picked_centers' => $centers]), $data['export'],
$this->exportFormHelper->savedExportDataToFormData($exportGeneration, 'formatter', ['picked_centers' => $centers]), $context,
$user,
); );
$this->storedObjectManager->write($exportGeneration->getStoredObject(), $generation->content, $generation->contentType); if ($generatedExport instanceof Response) {
}); trigger_deprecation('chill-project/chill-bundles', '3.10', 'DirectExportInterface should not return a %s instance, but a %s instance', Response::class, FormattedExportGeneration::class);
return new FormattedExportGeneration($generatedExport->getContent(), $generatedExport->headers->get('Content-Type'));
}
return $generatedExport;
}
$query = $export->initiateQuery(
$this->retrieveUsedModifiers($data),
$this->buildCenterReachableScopes($centers),
$data['export'],
);
if ($query instanceof \Doctrine\ORM\NativeQuery) {
// throw an error if the export require other modifier, which is
// not allowed when the export return a `NativeQuery`
if (\count($export->supportsModifiers()) > 0) {
throw new \LogicException("The export with alias `{$exportAlias}` return ".'a `\Doctrine\ORM\NativeQuery` and supports modifiers, which is not allowed. Either the method `supportsModifiers` should return an empty array, or return a `Doctrine\ORM\QueryBuilder`');
}
} elseif ($query instanceof QueryBuilder) {
// handle filters
$this->handleFilters($query, $data[ExportType::FILTER_KEY], $context);
// handle aggregators
$this->handleAggregators($query, $data[ExportType::AGGREGATOR_KEY], $context);
$this->logger->notice('[export] will execute this qb in export', [
'dql' => $query->getDQL(),
]);
} else {
throw new \UnexpectedValueException('The method `intiateQuery` should return a `\Doctrine\ORM\NativeQuery` or a `Doctrine\ORM\QueryBuilder` object.');
}
$result = $export->getResult($query, $data['export'], $context);
if (!is_iterable($result)) {
throw new \UnexpectedValueException(sprintf('The result of the export should be an iterable, %s given', \gettype($result)));
}
$formatter = $this->exportManager->getFormatter($data['pick_formatter']);
$filtersData = [];
$aggregatorsData = [];
if ($query instanceof QueryBuilder) {
foreach ($this->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]) as $alias => $aggregator) {
$aggregatorsData[$alias] = $data[ExportType::AGGREGATOR_KEY][$alias]['form'];
}
foreach ($this->retrieveUsedFilters($data[ExportType::FILTER_KEY]) as $alias => $filter) {
$filtersData[$alias] = $data[ExportType::FILTER_KEY][$alias]['form'];
}
}
if (method_exists($formatter, 'generate')) {
return $formatter->generate(
$result,
$data['formatter']['form'],
$exportAlias,
$data['export'],
$filtersData,
$aggregatorsData,
);
}
trigger_deprecation('chill-project/chill-bundles', '3.10', '%s should implements the "generate" method', FormatterInterface::class);
$generatedExport = $formatter->getResponse(
$result,
$data['formatter'],
$exportAlias,
$data['export']['form'],
$filtersData,
$aggregatorsData,
);
return new FormattedExportGeneration($generatedExport->getContent(), $generatedExport->headers->get('content-type'));
}
/**
* parse the data to retrieve the used filters and aggregators.
*
* @return list<string>
*/
private function retrieveUsedModifiers(mixed $data): array
{
if (null === $data) {
return [];
}
$usedTypes = array_merge(
$this->retrieveUsedFiltersType($data[ExportType::FILTER_KEY]),
$this->retrieveUsedAggregatorsType($data[ExportType::AGGREGATOR_KEY]),
);
return array_values(array_unique($usedTypes));
}
/**
* Retrieve the filter used in this export.
*
* @return list<string> an array with types
*/
private function retrieveUsedFiltersType(mixed $data): array
{
if (null === $data) {
return [];
}
$usedTypes = [];
foreach ($this->retrieveUsedFilters($data) as $filter) {
if (!\in_array($filter->applyOn(), $usedTypes, true)) {
$usedTypes[] = $filter->applyOn();
}
}
return $usedTypes;
}
/**
* @return string[]
*/
private function retrieveUsedAggregatorsType(mixed $data): array
{
if (null === $data) {
return [];
}
$usedTypes = [];
foreach ($this->retrieveUsedAggregators($data) as $alias => $aggregator) {
if (!\in_array($aggregator->applyOn(), $usedTypes, true)) {
$usedTypes[] = $aggregator->applyOn();
}
}
return $usedTypes;
}
/**
* @return iterable<string, AggregatorInterface>
*/
private function retrieveUsedAggregators(mixed $data): iterable
{
if (null === $data) {
return [];
}
foreach ($data as $alias => $aggregatorData) {
if (true === $aggregatorData['enabled']) {
yield $alias => $this->exportManager->getAggregator($alias);
}
}
}
/**
* @return iterable<string, FilterInterface>
*/
private function retrieveUsedFilters(mixed $data): iterable
{
if (null === $data) {
return [];
}
foreach ($data as $alias => $filterData) {
if (true === $filterData['enabled']) {
yield $alias => $this->exportManager->getFilter($alias);
}
}
}
/**
* Alter the query with selected aggregators.
*/
private function handleAggregators(
QueryBuilder $qb,
array $data,
ExportGenerationContext $context,
): void {
foreach ($this->retrieveUsedAggregators($data) as $alias => $aggregator) {
$formData = $data[$alias];
$aggregator->alterQuery($qb, $formData['form'], $context);
}
}
/**
* alter the query with selected filters.
*/
private function handleFilters(
QueryBuilder $qb,
mixed $data,
ExportGenerationContext $context,
): void {
foreach ($this->retrieveUsedFilters($data) as $alias => $filter) {
$formData = $data[$alias];
$filter->alterQuery($qb, $formData['form'], $context);
}
}
/**
* build the array required for defining centers and circles in the initiate
* queries of ExportElementsInterfaces.
*
* @param list<Center>
*/
private function buildCenterReachableScopes(array $centers)
{
return array_map(static fn (Center $center) => ['center' => $center, 'circles' => []], $centers);
} }
} }

View File

@ -120,7 +120,7 @@ interface ExportInterface extends ExportElementInterface
* *
* @return mixed[] an array of results * @return mixed[] an array of results
*/ */
public function getResult($query, $data); public function getResult($query, $data, ExportGenerationContext $context);
/** /**
* Return the Export's type. This will inform _on what_ export will apply. * Return the Export's type. This will inform _on what_ export will apply.

View File

@ -14,10 +14,7 @@ namespace Chill\MainBundle\Export;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\Export\ExportType; use Chill\MainBundle\Form\Type\Export\ExportType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Doctrine\ORM\QueryBuilder;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
@ -165,118 +162,6 @@ class ExportManager
$this->formatters[$alias] = $formatter; $this->formatters[$alias] = $formatter;
} }
public function generateExport(string $exportAlias, array $pickedCentersData, array $data, array $formatterData, User $byUser): FormattedExportGeneration
{
$export = $this->getExport($exportAlias);
$centers = $this->getPickedCenters($pickedCentersData);
$context = new ExportGenerationContext($byUser);
if ($export instanceof DirectExportInterface) {
$generatedExport = $export->generate(
$this->buildCenterReachableScopes($centers, $export),
$data[ExportType::EXPORT_KEY],
);
if ($generatedExport instanceof Response) {
trigger_deprecation('chill-project/chill-bundles', '3.10', 'DirectExportInterface should not return a %s instance, but a %s instance', Response::class, FormattedExportGeneration::class);
return new FormattedExportGeneration($generatedExport->getContent(), $generatedExport->headers->get('Content-Type'));
}
return $generatedExport;
}
$query = $export->initiateQuery(
$this->retrieveUsedModifiers($data),
$this->buildCenterReachableScopes($centers, $export),
$export->denormalizeFormData($data[ExportType::EXPORT_KEY], $context),
);
if ($query instanceof \Doctrine\ORM\NativeQuery) {
// throw an error if the export require other modifier, which is
// not allowed when the export return a `NativeQuery`
if (\count($export->supportsModifiers()) > 0) {
throw new \LogicException("The export with alias `{$exportAlias}` return ".'a `\Doctrine\ORM\NativeQuery` and supports modifiers, which is not allowed. Either the method `supportsModifiers` should return an empty array, or return a `Doctrine\ORM\QueryBuilder`');
}
} elseif ($query instanceof QueryBuilder) {
// handle filters
$this->handleFilters($export, $query, $data[ExportType::FILTER_KEY], $centers, $context);
// handle aggregators
$this->handleAggregators($export, $query, $data[ExportType::AGGREGATOR_KEY], $centers, $context);
$this->logger->notice('[export] will execute this qb in export', [
'dql' => $query->getDQL(),
]);
} else {
throw new \UnexpectedValueException('The method `intiateQuery` should return a `\Doctrine\ORM\NativeQuery` or a `Doctrine\ORM\QueryBuilder` object.');
}
$result = $export->getResult($query, $export->denormalizeFormData($data[ExportType::EXPORT_KEY], $context));
if (!is_iterable($result)) {
throw new \UnexpectedValueException(sprintf('The result of the export should be an iterable, %s given', \gettype($result)));
}
/** @var FormatterInterface $formatter */
$formatter = $this->getFormatter($this->getFormatterAlias($data));
$filtersData = [];
$aggregatorsData = [];
if ($query instanceof QueryBuilder) {
$aggregators = $this->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]);
foreach ($aggregators as $alias => $aggregator) {
$aggregatorsData[$alias] = $data[ExportType::AGGREGATOR_KEY][$alias]['form'];
}
}
$filters = $this->retrieveUsedFilters($data[ExportType::FILTER_KEY]);
foreach ($filters as $alias => $filter) {
$filtersData[$alias] = $data[ExportType::FILTER_KEY][$alias]['form'];
}
if (method_exists($formatter, 'generate')) {
return $formatter->generate(
$result,
$formatterData,
$exportAlias,
$data[ExportType::EXPORT_KEY],
$filtersData,
$aggregatorsData,
);
}
trigger_deprecation('chill-project/chill-bundles', '3.10', '%s should implements the "generate" method', FormatterInterface::class);
$generatedExport = $formatter->getResponse(
$result,
$formatterData,
$exportAlias,
$data[ExportType::EXPORT_KEY],
$filtersData,
$aggregatorsData,
);
return new FormattedExportGeneration($generatedExport->getContent(), $generatedExport->headers->get('content-type'));
}
/**
* Generate a response which contains the requested data.
*/
public function generate(string $exportAlias, array $pickedCentersData, array $data, array $formatterData): Response
{
$generated = $this->generateExport(
$exportAlias,
$pickedCentersData,
$data,
$formatterData,
);
return new Response($generated->content, headers: ['Content-Type' => $generated->contentType]);
}
/** /**
* @param string $alias * @param string $alias
* *
@ -467,7 +352,7 @@ class ExportManager
} }
/** /**
* get the aggregators typse used in the form export data. * get the aggregators types used in the form export data.
* *
* @param array $data the data from the export form * @param array $data the data from the export form
* *
@ -475,9 +360,15 @@ class ExportManager
*/ */
public function getUsedAggregatorsAliases(array $data): array public function getUsedAggregatorsAliases(array $data): array
{ {
$aggregators = $this->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]); $keys = [];
return array_keys(iterator_to_array($aggregators)); foreach ($data as $alias => $aggregatorData) {
if (true === $aggregatorData['enabled']) {
$keys[] = $alias;
}
}
return array_values(array_unique($keys));
} }
/** /**
@ -489,7 +380,6 @@ class ExportManager
DirectExportInterface|ExportInterface|null $export = null, DirectExportInterface|ExportInterface|null $export = null,
?array $centers = null, ?array $centers = null,
): bool { ): bool {
dump(__METHOD__, $this->tokenStorage->getToken()->getUser());
if ($element instanceof ExportInterface || $element instanceof DirectExportInterface) { if ($element instanceof ExportInterface || $element instanceof DirectExportInterface) {
$role = $element->requiredRole(); $role = $element->requiredRole();
} else { } else {
@ -510,7 +400,7 @@ class ExportManager
$role $role
); );
} }
dump($centers);
foreach ($centers as $center) { foreach ($centers as $center) {
if (false === $this->authorizationChecker->isGranted($role, $center)) { if (false === $this->authorizationChecker->isGranted($role, $center)) {
// debugging // debugging
@ -527,184 +417,4 @@ class ExportManager
return true; return true;
} }
/**
* build the array required for defining centers and circles in the initiate
* queries of ExportElementsInterfaces.
*
* @param \Chill\MainBundle\Entity\Center[] $centers
*/
private function buildCenterReachableScopes(array $centers, ExportElementInterface $element)
{
$r = [];
$user = $this->tokenStorage->getToken()->getUser();
if (!$user instanceof User) {
return [];
}
foreach ($centers as $center) {
$r[] = [
'center' => $center,
'circles' => $this->authorizationHelper->getReachableScopes(
$user,
$element->requiredRole(),
$center
),
];
}
return $r;
}
/**
* Alter the query with selected aggregators.
*
* Check for acl. If an user is not authorized to see an aggregator, throw an
* UnauthorizedException.
*
* @throw UnauthorizedHttpException if the user is not authorized
*/
private function handleAggregators(
ExportInterface $export,
QueryBuilder $qb,
array $data,
array $center,
ExportGenerationContext $context,
) {
$aggregators = $this->retrieveUsedAggregators($data);
foreach ($aggregators as $alias => $aggregator) {
$formData = $data[$alias];
$aggregator->alterQuery($qb, $aggregator->denormalizeFormData($formData['form'], $context));
}
}
/**
* alter the query with selected filters.
*
* This function check the acl.
*
* @param \Chill\MainBundle\Entity\Center[] $centers the picked centers
*
* @throw UnauthorizedHttpException if the user is not authorized
*/
private function handleFilters(
ExportInterface $export,
QueryBuilder $qb,
mixed $data,
array $centers,
ExportGenerationContext $context,
) {
$filters = $this->retrieveUsedFilters($data);
foreach ($filters as $alias => $filter) {
$formData = $data[$alias];
$this->logger->debug('alter query by filter '.$alias, [
'class' => self::class, 'function' => __FUNCTION__,
]);
$filter->alterQuery($qb, $filter->denormalizeFormData($formData['form'], $context));
}
}
/**
* @return iterable<string, AggregatorInterface>
*/
private function retrieveUsedAggregators(mixed $data): iterable
{
if (null === $data) {
return [];
}
foreach ($data as $alias => $aggregatorData) {
if (true === $aggregatorData['enabled']) {
yield $alias => $this->getAggregator($alias);
}
}
}
/**
* @return string[]
*/
private function retrieveUsedAggregatorsType(mixed $data)
{
if (null === $data) {
return [];
}
$usedTypes = [];
foreach ($this->retrieveUsedAggregators($data) as $alias => $aggregator) {
if (!\in_array($aggregator->applyOn(), $usedTypes, true)) {
$usedTypes[] = $aggregator->applyOn();
}
}
return $usedTypes;
}
private function retrieveUsedFilters(mixed $data): iterable
{
if (null === $data) {
return [];
}
foreach ($data as $alias => $filterData) {
if (true === $filterData['enabled']) {
yield $alias => $this->getFilter($alias);
}
}
}
/**
* Retrieve the filter used in this export.
*
* @return array an array with types
*/
private function retrieveUsedFiltersType(mixed $data): iterable
{
if (null === $data) {
return [];
}
$usedTypes = [];
foreach ($data as $alias => $filterData) {
if (true === $filterData['enabled']) {
$filter = $this->getFilter($alias);
if (!\in_array($filter->applyOn(), $usedTypes, true)) {
$usedTypes[] = $filter->applyOn();
}
}
}
return $usedTypes;
}
/**
* parse the data to retrieve the used filters and aggregators.
*
* @return string[]
*/
private function retrieveUsedModifiers(mixed $data)
{
if (null === $data) {
return [];
}
$usedTypes = array_merge(
$this->retrieveUsedFiltersType($data[ExportType::FILTER_KEY]),
$this->retrieveUsedAggregatorsType($data[ExportType::AGGREGATOR_KEY])
);
$this->logger->debug(
'Required types are '.implode(', ', $usedTypes),
['class' => self::class, 'function' => __FUNCTION__]
);
return array_unique($usedTypes);
}
} }

View File

@ -36,8 +36,10 @@ interface ModifierInterface extends ExportElementInterface
* *
* @param QueryBuilder $qb the QueryBuilder initiated by the Export (and eventually modified by other Modifiers) * @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) * @param mixed[] $data the data from the Form (builded by buildForm)
*
* @return void
*/ */
public function alterQuery(QueryBuilder $qb, $data/* , ExportGenerationContext $exportGenerationContext */); public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext);
/** /**
* On which type of Export this ModifiersInterface may apply. * On which type of Export this ModifiersInterface may apply.

View File

@ -0,0 +1,205 @@
<?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\Entity\User;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Export\DirectExportInterface;
use Chill\MainBundle\Export\ExportConfigNormalizer;
use Chill\MainBundle\Export\ExportGenerationContext;
use Chill\MainBundle\Export\ExportGenerator;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\ExportManager;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Export\FormattedExportGeneration;
use Chill\MainBundle\Export\FormatterInterface;
use Doctrine\ORM\NativeQuery;
use Doctrine\ORM\QueryBuilder;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Log\NullLogger;
/**
* @internal
*
* @coversNothing
*/
class ExportGeneratorTest extends TestCase
{
use ProphecyTrait;
public function testGenerateHappyScenario()
{
$initialData = ['initial' => 'test'];
$fullConfig = [
'export' => $formExportData = ['key' => 'form1'],
'filters' => [
'dummy_filter' => ['enabled' => true, 'form' => $formFilterData = ['key' => 'form2']],
'disabled_filter' => ['enabled' => false],
],
'aggregators' => [
'dummy_aggregator' => ['enabled' => true, 'form' => $formAggregatorData = ['key' => 'form3']],
'disabled_aggregator' => ['enabled' => false],
],
'pick_formatter' => 'xlsx',
'formatter' => ['form' => $formatterData = ['key' => 'form4']],
'centers' => [$centerA = new Center(), $centerB = new Center()],
];
$user = new User();
$export = $this->prophesize(ExportInterface::class);
$filter = $this->prophesize(FilterInterface::class);
$filter->applyOn()->willReturn('tagada');
$aggregator = $this->prophesize(AggregatorInterface::class);
$aggregator->applyOn()->willReturn('tsointsoin');
$formatter = $this->prophesize(FormatterInterface::class);
$query = $this->prophesize(QueryBuilder::class);
// required methods
$export->initiateQuery(
['tagada', 'tsointsoin'],
[['center' => $centerA, 'circles' => []], ['center' => $centerB, 'circles' => []]],
['key' => 'form1'],
)->shouldBeCalled()->willReturn($query->reveal());
$export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
->shouldBeCalled()->willReturn([['result0' => '0']]);
$filter->alterQuery($query->reveal(), $formFilterData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
->shouldBeCalled();
$aggregator->alterQuery($query->reveal(), $formAggregatorData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
->shouldBeCalled();
$formatter->generate(
[['result0' => '0']],
$formatterData,
'dummy',
$formExportData,
['dummy_filter' => $formFilterData],
['dummy_aggregator' => $formAggregatorData]
)
->shouldBeCalled()
->willReturn(new FormattedExportGeneration('export result', 'text/text'));
$exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class);
$exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig);
$exportManager = $this->prophesize(ExportManager::class);
$exportManager->getExport('dummy')->willReturn($export->reveal());
$exportManager->getFilter('dummy_filter')->willReturn($filter->reveal());
$exportManager->getAggregator('dummy_aggregator')->willReturn($aggregator->reveal());
$exportManager->getFormatter('xlsx')->willReturn($formatter->reveal());
$generator = new ExportGenerator($exportManager->reveal(), $exportConfigNormalizer->reveal(), new NullLogger());
$actual = $generator->generate('dummy', $initialData, $user);
self::assertInstanceOf(FormattedExportGeneration::class, $actual);
self::assertEquals('export result', $actual->content);
self::assertEquals('text/text', $actual->contentType);
}
public function testGenerateNativeSqlHappyScenario()
{
$initialData = ['initial' => 'test'];
$fullConfig = [
'export' => $formExportData = ['key' => 'form1'],
'filters' => [],
'aggregators' => [],
'pick_formatter' => 'xlsx',
'formatter' => ['form' => $formatterData = ['key' => 'form4']],
'centers' => [$centerA = new Center(), $centerB = new Center()],
];
$user = new User();
$export = $this->prophesize(ExportInterface::class);
$formatter = $this->prophesize(FormatterInterface::class);
$query = $this->prophesize(NativeQuery::class);
// required methods
$export->initiateQuery(
[],
[['center' => $centerA, 'circles' => []], ['center' => $centerB, 'circles' => []]],
['key' => 'form1'],
)->shouldBeCalled()->willReturn($query->reveal());
$export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user))
->shouldBeCalled()->willReturn([['result0' => '0']]);
$export->supportsModifiers()->willReturn([]);
$formatter->generate(
[['result0' => '0']],
$formatterData,
'dummy',
$formExportData,
[],
[]
)
->shouldBeCalled()
->willReturn(new FormattedExportGeneration('export result', 'text/text'));
$exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class);
$exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig);
$exportManager = $this->prophesize(ExportManager::class);
$exportManager->getExport('dummy')->willReturn($export->reveal());
$exportManager->getFormatter('xlsx')->willReturn($formatter->reveal());
$generator = new ExportGenerator($exportManager->reveal(), $exportConfigNormalizer->reveal(), new NullLogger());
$actual = $generator->generate('dummy', $initialData, $user);
self::assertInstanceOf(FormattedExportGeneration::class, $actual);
self::assertEquals('export result', $actual->content);
self::assertEquals('text/text', $actual->contentType);
}
public function testGenerateDirectExportHappyScenario()
{
$initialData = ['initial' => 'test'];
$fullConfig = [
'export' => $formExportData = ['key' => 'form1'],
'filters' => [],
'aggregators' => [],
'pick_formatter' => 'xlsx',
'formatter' => ['form' => $formatterData = ['key' => 'form4']],
'centers' => [$centerA = new Center(), $centerB = new Center()],
];
$user = new User();
$export = $this->prophesize(DirectExportInterface::class);
// required methods
$export->generate(
[['center' => $centerA, 'circles' => []], ['center' => $centerB, 'circles' => []]],
['key' => 'form1'],
Argument::that(static fn (ExportGenerationContext $context) => $user === $context->byUser),
)->shouldBeCalled()
->willReturn(new FormattedExportGeneration('export result', 'text/text'));
$exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class);
$exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig);
$exportManager = $this->prophesize(ExportManager::class);
$exportManager->getExport('dummy')->willReturn($export->reveal());
$generator = new ExportGenerator($exportManager->reveal(), $exportConfigNormalizer->reveal(), new NullLogger());
$actual = $generator->generate('dummy', $initialData, $user);
self::assertInstanceOf(FormattedExportGeneration::class, $actual);
self::assertEquals('export result', $actual->content);
self::assertEquals('text/text', $actual->contentType);
}
}