mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2026-01-29 04:27:16 +00:00
Partage d'export enregistré et génération asynchrone des exports
This commit is contained in:
273
src/Bundle/ChillMainBundle/Export/ExportGenerator.php
Normal file
273
src/Bundle/ChillMainBundle/Export/ExportGenerator.php
Normal file
@@ -0,0 +1,273 @@
|
||||
<?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\Entity\User;
|
||||
use Chill\MainBundle\Export\Exception\UnauthorizedGenerationException;
|
||||
use Chill\MainBundle\Form\Type\Export\ExportType;
|
||||
use Chill\MainBundle\Repository\CenterRepositoryInterface;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
|
||||
use Chill\MainBundle\Service\Regroupement\CenterRegroupementResolver;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Generate a single export.
|
||||
*/
|
||||
final readonly class ExportGenerator
|
||||
{
|
||||
private bool $filterStatsByCenters;
|
||||
|
||||
public function __construct(
|
||||
private ExportManager $exportManager,
|
||||
private ExportConfigNormalizer $configNormalizer,
|
||||
private LoggerInterface $logger,
|
||||
private AuthorizationHelperInterface $authorizationHelper,
|
||||
private CenterRegroupementResolver $centerRegroupementResolver,
|
||||
private ExportConfigProcessor $exportConfigProcessor,
|
||||
ParameterBagInterface $parameterBag,
|
||||
private CenterRepositoryInterface $centerRepository,
|
||||
) {
|
||||
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
|
||||
}
|
||||
|
||||
public function generate(string $exportAlias, array $configuration, ?User $byUser = null): FormattedExportGeneration
|
||||
{
|
||||
$data = $this->configNormalizer->denormalizeConfig($exportAlias, $configuration);
|
||||
$export = $this->exportManager->getExport($exportAlias);
|
||||
|
||||
$centers = $this->filterCenters($byUser, $data['centers']['centers'], $data['centers']['regroupments'], $export);
|
||||
|
||||
$context = new ExportGenerationContext($byUser);
|
||||
|
||||
if ($export instanceof DirectExportInterface) {
|
||||
$generatedExport = $export->generate(
|
||||
$this->buildCenterReachableScopes($centers),
|
||||
$data['export'],
|
||||
$context,
|
||||
);
|
||||
|
||||
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'],
|
||||
$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($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(),
|
||||
]);
|
||||
$this->logger->debug('[export] will execute this sql qb in export', [
|
||||
'sql' => $query->getQuery()->getSQL(),
|
||||
]);
|
||||
} 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);
|
||||
|
||||
$formatter = $this->exportManager->getFormatter($data['pick_formatter']);
|
||||
$filtersData = [];
|
||||
$aggregatorsData = [];
|
||||
|
||||
if ($query instanceof QueryBuilder) {
|
||||
foreach ($this->exportConfigProcessor->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]) as $alias => $aggregator) {
|
||||
$aggregatorsData[$alias] = $data[ExportType::AGGREGATOR_KEY][$alias]['form'];
|
||||
}
|
||||
foreach ($this->exportConfigProcessor->retrieveUsedFilters($data[ExportType::FILTER_KEY]) as $alias => $filter) {
|
||||
$filtersData[$alias] = $data[ExportType::FILTER_KEY][$alias]['form'];
|
||||
}
|
||||
}
|
||||
|
||||
/* @phpstan-ignore-next-line the method "generate" is not yet implemented on all formatters */
|
||||
if (method_exists($formatter, 'generate')) {
|
||||
return $formatter->generate(
|
||||
$result,
|
||||
$data['formatter'],
|
||||
$exportAlias,
|
||||
$data['export'],
|
||||
$filtersData,
|
||||
$aggregatorsData,
|
||||
$context,
|
||||
);
|
||||
}
|
||||
|
||||
trigger_deprecation('chill-project/chill-bundles', '3.10', '%s should implements the "generate" method', FormatterInterface::class);
|
||||
|
||||
/* @phpstan-ignore-next-line this is a deprecated method that we must still call */
|
||||
$generatedExport = $formatter->getResponse(
|
||||
$result,
|
||||
$data['formatter'],
|
||||
$exportAlias,
|
||||
$data['export'],
|
||||
$filtersData,
|
||||
$aggregatorsData,
|
||||
$context,
|
||||
);
|
||||
|
||||
return new FormattedExportGeneration($generatedExport->getContent(), $generatedExport->headers->get('content-type'));
|
||||
}
|
||||
|
||||
private function filterCenters(User $byUser, array $centers, array $regroupements, ExportInterface|DirectExportInterface $export): array
|
||||
{
|
||||
if (!$this->filterStatsByCenters) {
|
||||
return $this->centerRepository->findActive();
|
||||
}
|
||||
|
||||
$authorizedCenters = new ArrayCollection($this->authorizationHelper->getReachableCenters($byUser, $export->requiredRole()));
|
||||
if ($authorizedCenters->isEmpty()) {
|
||||
throw new UnauthorizedGenerationException('No authorized centers');
|
||||
}
|
||||
|
||||
$wantedCenters = $this->centerRegroupementResolver->resolveCenters($regroupements, $centers);
|
||||
|
||||
$resolvedCenters = [];
|
||||
foreach ($wantedCenters as $wantedCenter) {
|
||||
if ($authorizedCenters->contains($wantedCenter)) {
|
||||
$resolvedCenters[] = $wantedCenter;
|
||||
}
|
||||
}
|
||||
|
||||
if ([] == $resolvedCenters) {
|
||||
throw new UnauthorizedGenerationException('No common centers between wanted centers and authorized centers');
|
||||
}
|
||||
|
||||
return $resolvedCenters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->exportConfigProcessor->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->exportConfigProcessor->retrieveUsedAggregators($data) as $alias => $aggregator) {
|
||||
if (!\in_array($aggregator->applyOn(), $usedTypes, true)) {
|
||||
$usedTypes[] = $aggregator->applyOn();
|
||||
}
|
||||
}
|
||||
|
||||
return $usedTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the query with selected aggregators.
|
||||
*/
|
||||
private function handleAggregators(
|
||||
QueryBuilder $qb,
|
||||
array $data,
|
||||
ExportGenerationContext $context,
|
||||
): void {
|
||||
foreach ($this->exportConfigProcessor->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->exportConfigProcessor->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> $centers
|
||||
*/
|
||||
private function buildCenterReachableScopes(array $centers)
|
||||
{
|
||||
return array_map(static fn (Center $center) => ['center' => $center, 'circles' => []], $centers);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user