mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Generate export using denormalization
This commit is contained in:
parent
1f1d38acef
commit
2c812fc5fe
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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).
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
205
src/Bundle/ChillMainBundle/Tests/Export/ExportGeneratorTest.php
Normal file
205
src/Bundle/ChillMainBundle/Tests/Export/ExportGeneratorTest.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user