diff --git a/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php new file mode 100644 index 000000000..32a7cc1c9 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php @@ -0,0 +1,48 @@ +security->isGranted(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod)) { + throw new AccessDeniedHttpException("not allowed to see the documents for accompanying period"); + } + + $nb = $this->manager->countDocForAccompanyingPeriod($accompanyingPeriod); + + return new Response($nb); + } + +} diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php new file mode 100644 index 000000000..42e0fd335 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php @@ -0,0 +1,168 @@ + + */ + private array $joins = []; + + /** + * @var list + */ + private array $joinParams = []; + + /** + * @var list + */ + private array $wheres = []; + + /** + * @var list + */ + private array $whereParams = []; + + public function __construct( + private readonly string $selectKeyString, + private readonly string $selectIdentifierJsonB, + private readonly string $selectDate, + private string $from = '', + private array $selectIdentifierParams = [], + private array $selectDateParams = [], + ) { + } + + public function addJoinClause(string $sql, array $params = []): int + { + $this->joins[] = $sql; + $this->joinParams[] = $params; + + return count($this->joins) - 1; + } + + public function addWhereClause(string $sql, array $params = []): int + { + $this->wheres[] = $sql; + $this->whereParams[] = $params; + + return count($this->wheres) - 1; + } + + public function removeWhereClause(int $index): void + { + if (!array_key_exists($index, $this->wheres)) { + throw new \UnexpectedValueException("this index does not exists"); + } + + unset($this->wheres[$index], $this->whereParams[$index]); + + } + + public function removeJoinClause(int $index): void + { + if (!array_key_exists($index, $this->joins)) { + throw new \UnexpectedValueException("this index does not exists"); + } + + unset($this->joins[$index], $this->joinParams[$index]); + + } + + public function getSelectKeyString(): string + { + return $this->selectKeyString; + } + + public function getSelectIdentifierJsonB(): string + { + return $this->selectIdentifierJsonB; + } + + /** + * @inheritDoc + */ + public function getSelectIdentifierParams(): array + { + return $this->selectIdentifierParams; + } + + public function getSelectDate(): string + { + return $this->selectDate; + } + + /** + * @inheritDoc + */ + public function getSelectDateParams(): array + { + return $this->selectDateParams; + } + + public function getFromQuery(): string + { + return $this->from . " " . implode(' ', $this->joins); + } + + /** + * @inheritDoc + */ + public function getFromQueryParams(): array + { + $result = []; + + foreach ($this->joinParams as $params) { + $result = [...$result, ...$params]; + } + + return $result; + } + + public function getWhereQuery(): string + { + return implode(' AND ', $this->wheres); + } + + /** + * @inheritDoc + */ + public function getWhereQueryParams(): array + { + $result = []; + + foreach ($this->whereParams as $params) { + $result = [...$result, ...$params]; + } + + return $result; + } + + /** + * @param array $selectIdentifierParams + */ + public function setSelectIdentifierParams(array $selectIdentifierParams): void + { + $this->selectIdentifierParams = $selectIdentifierParams; + } + + /** + * @param array $selectDateParams + */ + public function setSelectDateParams(array $selectDateParams): void + { + $this->selectDateParams = $selectDateParams; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryInterface.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryInterface.php new file mode 100644 index 000000000..c262d2a5d --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryInterface.php @@ -0,0 +1,45 @@ + + */ + public function getSelectIdentifierParams(): array; + + public function getSelectDate(): string; + + /** + * @return list + */ + public function getSelectDateParams(): array; + + public function getFromQuery(): string; + + /** + * @return list + */ + public function getFromQueryParams(): array; + + public function getWhereQuery(): string; + + /** + * @return list + */ + public function getWhereQueryParams(): array; +} diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryToSqlBuilder.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryToSqlBuilder.php new file mode 100644 index 000000000..881435da7 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryToSqlBuilder.php @@ -0,0 +1,48 @@ +} + */ + public function toSql(FetchQueryInterface $query): array + { + $sql = strtr(self::SQL, [ + '{{ key }}' => $query->getSelectKeyString(), + '{{ identifiers }}' => $query->getSelectIdentifierJsonB(), + '{{ date }}' => $query->getSelectDate(), + '{{ from }}' => $query->getFromQuery(), + '{{ where }}' => $query->getWhereQuery(), + ]); + + $params = [ + ...$query->getSelectIdentifierParams(), + ...$query->getSelectDateParams(), + ...$query->getFromQueryParams(), + ...$query->getWhereQueryParams() + ]; + + return ['sql' => $sql, 'params' => $params]; + } +} diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php new file mode 100644 index 000000000..6fb6139aa --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php @@ -0,0 +1,22 @@ + + */ + private readonly iterable $providersForAccompanyingPeriod, + private readonly Connection $connection, + ) { + $this->builder = new FetchQueryToSqlBuilder(); + } + + /** + * @throws Exception + */ + public function countDocForAccompanyingPeriod( + AccompanyingPeriod $accompanyingPeriod, + ?\DateTimeImmutable $startDate = null, + ?\DateTimeImmutable $endDate = null, + ?string $content = null, + ?string $origin = null + ): int { + ['sql' => $sql, 'params' => $params] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $origin); + $countSql = "SELECT count(*) AS c FROM {$sql} AS sq"; + $result = $this->connection->executeQuery($countSql, $params); + + $number = $result->fetchOne(); + + if (false === $number) { + throw new \UnexpectedValueException("number of documents failed to load"); + } + + return $number['c']; + } + + /** + * @throws Exception + */ + public function findDocForAccompanyingPeriod( + AccompanyingPeriod $accompanyingPeriod, + int $offset = 0, + int $limit = 20, + ?\DateTimeImmutable $startDate = null, + ?\DateTimeImmutable $endDate = null, + ?string $content = null, + ?string $origin = null + ): iterable { + ['sql' => $sql, 'params' => $params] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $origin); + + $runSql = "{$sql} LIMIT ? OFFSET ?"; + $runParams = [...$params, ...[$limit, $offset]]; + + foreach($this->connection->iterateAssociative($runSql, $runParams) as $row) { + yield new GenericDocDTO($row['key'], $row['identifiers'], $row['date_doc']); + } + } + + /** + */ + private function buildUnionQuery( + AccompanyingPeriod|Person $linked, + ?\DateTimeImmutable $startDate = null, + ?\DateTimeImmutable $endDate = null, + ?string $content = null, + ?string $origin = null + ): array { + $sql = []; + $params = []; + + if ($linked instanceof AccompanyingPeriod) { + foreach ($this->providersForAccompanyingPeriod as $provider) { + ['sql' => $q, 'params' => $p ] = $this->builder + ->toSql($provider->buildFetchQueryForAccompanyingPeriod($linked, $startDate, $endDate, $content, $origin)); + $params = [...$params, ...$p]; + $sql[] = $q; + } + } + + return ['sql' => implode(' UNION ', $sql), 'params' => $params]; + } + +} diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/ProviderForAccompanyingPeriodInterface.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/ProviderForAccompanyingPeriodInterface.php new file mode 100644 index 000000000..07ca83694 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/ProviderForAccompanyingPeriodInterface.php @@ -0,0 +1,33 @@ +addJoinClause('LEFT JOIN other b ON a.id = b.foreign_id', ['foo']); + $index = $query->addJoinClause('LEFT JOIN other c ON a.id = c.foreign_id', ['bar']); + $query->addJoinClause('LEFT JOIN other d ON a.id = d.foreign_id', ['bar_baz']); + $query->removeJoinClause($index); + $query->addWhereClause('b.item = ?', ['baz']); + $index = $query->addWhereClause('b.cancel', [ 'foz']); + $query->removeWhereClause($index); + + ['sql' => $sql, 'params' => $params] = (new FetchQueryToSqlBuilder())->toSql($query); + + $filteredSql = + implode(" ", array_filter( + explode(" ", str_replace("\n", "", $sql)), + fn (string $tok) => $tok !== "" + )) + ; + + self::assertEquals( + "SELECT 'test' AS key, jsonb_build_object('id', a.column) AS identifiers, ". + "a.datecolumn AS doc_date FROM my_table a LEFT JOIN other b ON a.id = b.foreign_id LEFT JOIN other d ON a.id = d.foreign_id WHERE b.item = ?", + $filteredSql + ); + self::assertEquals(['foo', 'bar_baz', 'baz'], $params); + } + +} diff --git a/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php new file mode 100644 index 000000000..523e08c40 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Tests/GenericDoc/ManagerTest.php @@ -0,0 +1,56 @@ +em = self::$container->get(EntityManagerInterface::class); + + if (null !== $manager = self::$container->get(Manager::class)) { + $this->manager = $manager; + } else { + throw new \UnexpectedValueException("the manager was not found in the kernel"); + } + } + + public function testCountByAccompanyingPeriod(): void + { + $period = $this->em->createQuery('SELECT a FROM '.AccompanyingPeriod::class.' a') + ->setMaxResults(1) + ->getSingleResult(); + + if (null === $period) { + throw new \UnexpectedValueException("period not found"); + } + + $nb = $this->manager->countDocForAccompanyingPeriod($period); + + self::assertIsInt($nb); + } +} diff --git a/src/Bundle/ChillDocStoreBundle/config/services.yaml b/src/Bundle/ChillDocStoreBundle/config/services.yaml index 860495677..22161d2f6 100644 --- a/src/Bundle/ChillDocStoreBundle/config/services.yaml +++ b/src/Bundle/ChillDocStoreBundle/config/services.yaml @@ -45,3 +45,9 @@ services: autoconfigure: true resource: '../Service/' + Chill\DocStoreBundle\GenericDoc\Manager: + autowire: true + autoconfigure: true + arguments: + $providersForAccompanyingPeriod: !tagged_iterator chill_doc_store.generic_doc_accompanying_period_provider +