GenericDoc: add provider for AccompanyingCourseDocument, without filtering

This commit is contained in:
2023-05-24 11:42:30 +02:00
parent afcd6e0605
commit 8dbe2d6ec2
12 changed files with 414 additions and 46 deletions

View File

@@ -11,7 +11,7 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\GenericDoc;
use Nelmio\Alice\Throwable\Exception\FixtureBuilder\Denormalizer\UnexpectedValueException;
use Doctrine\DBAL\Types\Types;
class FetchQuery implements FetchQueryInterface
{
@@ -21,42 +21,56 @@ class FetchQuery implements FetchQueryInterface
private array $joins = [];
/**
* @var list<mixed>
* @var list<list<mixed>>
*/
private array $joinParams = [];
/**
* @var list<string>
* @var array<list<Types::*>>
*/
private array $joinTypes = [];
/**
* @var array<string>
*/
private array $wheres = [];
/**
* @var list<mixed>
* @var array<list<mixed>>
*/
private array $whereParams = [];
/**
* @var array<list<Types::*>>
*/
private array $whereTypes = [];
public function __construct(
private readonly string $selectKeyString,
private readonly string $selectIdentifierJsonB,
private readonly string $selectDate,
private string $from = '',
private array $selectIdentifierParams = [],
private array $selectIdentifierTypes = [],
private array $selectDateParams = [],
private array $selectDateTypes = [],
) {
}
public function addJoinClause(string $sql, array $params = []): int
public function addJoinClause(string $sql, array $params = [], array $types = []): int
{
$this->joins[] = $sql;
$this->joinParams[] = $params;
$this->joinTypes[] = $types;
return count($this->joins) - 1;
}
public function addWhereClause(string $sql, array $params = []): int
public function addWhereClause(string $sql, array $params = [], array $types = []): int
{
$this->wheres[] = $sql;
$this->whereParams[] = $params;
$this->whereTypes[] = $types;
return count($this->wheres) - 1;
}
@@ -67,7 +81,7 @@ class FetchQuery implements FetchQueryInterface
throw new \UnexpectedValueException("this index does not exists");
}
unset($this->wheres[$index], $this->whereParams[$index]);
unset($this->wheres[$index], $this->whereParams[$index], $this->whereTypes[$index]);
}
@@ -77,7 +91,7 @@ class FetchQuery implements FetchQueryInterface
throw new \UnexpectedValueException("this index does not exists");
}
unset($this->joins[$index], $this->joinParams[$index]);
unset($this->joins[$index], $this->joinParams[$index], $this->joinTypes[$index]);
}
@@ -99,11 +113,21 @@ class FetchQuery implements FetchQueryInterface
return $this->selectIdentifierParams;
}
public function getSelectIdentifiersTypes(): array
{
return $this->selectIdentifierTypes;
}
public function getSelectDate(): string
{
return $this->selectDate;
}
public function getSelectDateTypes(): array
{
return $this->selectDateTypes;
}
/**
* @inheritDoc
*/
@@ -131,6 +155,17 @@ class FetchQuery implements FetchQueryInterface
return $result;
}
public function getFromQueryTypes(): array
{
$result = [];
foreach ($this->joinTypes as $types) {
$result = [...$result, ...$types];
}
return $result;
}
public function getWhereQuery(): string
{
return implode(' AND ', $this->wheres);
@@ -150,19 +185,49 @@ class FetchQuery implements FetchQueryInterface
return $result;
}
/**
* @param array $selectIdentifierParams
*/
public function setSelectIdentifierParams(array $selectIdentifierParams): void
public function getWhereQueryTypes(): array
{
$this->selectIdentifierParams = $selectIdentifierParams;
$result = [];
foreach ($this->whereTypes as $types) {
$result = [...$result, ...$types];
}
return $result;
}
/**
* @param array $selectDateParams
*/
public function setSelectDateParams(array $selectDateParams): void
public function setSelectIdentifierParams(array $selectIdentifierParams): self
{
$this->selectIdentifierParams = $selectIdentifierParams;
return $this;
}
public function setSelectDateParams(array $selectDateParams): self
{
$this->selectDateParams = $selectDateParams;
return $this;
}
public function setFrom(string $from): self
{
$this->from = $from;
return $this;
}
public function setSelectIdentifierTypes(array $selectIdentifierTypes): self
{
$this->selectIdentifierTypes = $selectIdentifierTypes;
return $this;
}
public function setSelectDateTypes(array $selectDateTypes): self
{
$this->selectDateTypes = $selectDateTypes;
return $this;
}
}

View File

@@ -11,6 +11,8 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\GenericDoc;
use Doctrine\DBAL\Types\Types;
interface FetchQueryInterface
{
public function getSelectKeyString(): string;
@@ -22,6 +24,11 @@ interface FetchQueryInterface
*/
public function getSelectIdentifierParams(): array;
/**
* @return list<Types::*>
*/
public function getSelectIdentifiersTypes(): array;
public function getSelectDate(): string;
/**
@@ -29,6 +36,11 @@ interface FetchQueryInterface
*/
public function getSelectDateParams(): array;
/**
* @return list<Types::*>
*/
public function getSelectDateTypes(): array;
public function getFromQuery(): string;
/**
@@ -36,10 +48,20 @@ interface FetchQueryInterface
*/
public function getFromQueryParams(): array;
/**
* @return list<Types::*>
*/
public function getFromQueryTypes(): array;
public function getWhereQuery(): string;
/**
* @return list<mixed>
*/
public function getWhereQueryParams(): array;
/**
* @return list<Types::*>
*/
public function getWhereQueryTypes(): array;
}

View File

@@ -11,20 +11,22 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\GenericDoc;
use Doctrine\DBAL\Types\Types;
final readonly class FetchQueryToSqlBuilder
{
private const SQL = <<<'SQL'
SELECT
'{{ key }}' AS key,
{{ identifiers }} AS identifiers,
{{ date }} AS doc_date
{{ date }}::date AS doc_date
FROM {{ from }}
WHERE {{ where }}
{{ where }}
SQL;
/**
* @param FetchQueryInterface $query
* @return array{sql: string, params: list<mixed>}
* @return array{sql: string, params: list<mixed>, types: list<Types::*>}
*/
public function toSql(FetchQueryInterface $query): array
{
@@ -33,7 +35,7 @@ final readonly class FetchQueryToSqlBuilder
'{{ identifiers }}' => $query->getSelectIdentifierJsonB(),
'{{ date }}' => $query->getSelectDate(),
'{{ from }}' => $query->getFromQuery(),
'{{ where }}' => $query->getWhereQuery(),
'{{ where }}' => '' === ($w = $query->getWhereQuery()) ? '' : 'WHERE ' . $w,
]);
$params = [
@@ -43,6 +45,13 @@ final readonly class FetchQueryToSqlBuilder
...$query->getWhereQueryParams()
];
return ['sql' => $sql, 'params' => $params];
$types = [
...$query->getSelectIdentifiersTypes(),
...$query->getSelectDateTypes(),
...$query->getFromQueryTypes(),
...$query->getWhereQueryTypes(),
];
return ['sql' => $sql, 'params' => $params, 'types' => $types];
}
}

View File

@@ -15,6 +15,7 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Types\Types;
class Manager
{
@@ -41,7 +42,12 @@ class Manager
?string $origin = null
): int {
['sql' => $sql, 'params' => $params] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $origin);
$countSql = "SELECT count(*) AS c FROM {$sql} AS sq";
if ($sql === '') {
return 0;
}
$countSql = "SELECT count(*) AS c FROM ({$sql}) AS sq";
$result = $this->connection->executeQuery($countSql, $params);
$number = $result->fetchOne();
@@ -50,7 +56,7 @@ class Manager
throw new \UnexpectedValueException("number of documents failed to load");
}
return $number['c'];
return $number;
}
/**
@@ -65,13 +71,18 @@ class Manager
?string $content = null,
?string $origin = null
): iterable {
['sql' => $sql, 'params' => $params] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $origin);
['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $origin);
$runSql = "{$sql} LIMIT ? OFFSET ?";
$runParams = [...$params, ...[$limit, $offset]];
$runTypes = [...$types, ...[Types::INTEGER, Types::INTEGER]];
foreach($this->connection->iterateAssociative($runSql, $runParams) as $row) {
yield new GenericDocDTO($row['key'], $row['identifiers'], $row['date_doc']);
foreach($this->connection->iterateAssociative($runSql, $runParams, $runTypes) as $row) {
yield new GenericDocDTO(
$row['key'],
json_decode($row['identifiers'], true, JSON_THROW_ON_ERROR),
new \DateTimeImmutable($row['doc_date'])
);
}
}
@@ -86,17 +97,24 @@ class Manager
): array {
$sql = [];
$params = [];
$types = [];
if ($linked instanceof AccompanyingPeriod) {
foreach ($this->providersForAccompanyingPeriod as $provider) {
['sql' => $q, 'params' => $p ] = $this->builder
if (!$provider->isAllowedForAccompanyingPeriod($linked)) {
continue;
}
['sql' => $q, 'params' => $p, 'types' => $t ] = $this->builder
->toSql($provider->buildFetchQueryForAccompanyingPeriod($linked, $startDate, $endDate, $content, $origin));
$params = [...$params, ...$p];
$types = [...$types, ...$t];
$sql[] = $q;
}
}
return ['sql' => implode(' UNION ', $sql), 'params' => $params];
return ['sql' => implode(' UNION ', $sql), 'params' => $params, 'types' => $types];
}
}

View File

@@ -28,6 +28,6 @@ interface ProviderForAccompanyingPeriodInterface
*
* @return bool
*/
public function isAllowed(): bool;
public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool;
}

View File

@@ -0,0 +1,84 @@
<?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\DocStoreBundle\GenericDoc\Providers;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
use Chill\DocStoreBundle\GenericDoc\ProviderForAccompanyingPeriodInterface;
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Security;
final readonly class AccompanyingCourseDocumentProvider implements ProviderForAccompanyingPeriodInterface
{
public function __construct(
private Security $security,
private EntityManagerInterface $entityManager,
) {
}
public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
{
$classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class);
$query = new FetchQuery(
'accompanying_course_document',
sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]),
sprintf($classMetadata->getColumnName('date')),
$classMetadata->getSchemaName() . '.' . $classMetadata->getTableName()
);
$query->addWhereClause(
sprintf('%s = ?', $classMetadata->getSingleAssociationJoinColumnName('course')),
[$accompanyingPeriod->getId()],
[Types::INTEGER]
);
if (null !== $startDate) {
$query->addWhereClause(
sprintf('? >= %s', $classMetadata->getColumnName('date')),
[$startDate],
[Types::DATE_IMMUTABLE]
);
}
if (null !== $endDate) {
$query->addWhereClause(
sprintf('? < %s', $classMetadata->getColumnName('date')),
[$endDate],
[Types::DATE_IMMUTABLE]
);
}
if (null !== $content) {
$query->addWhereClause(
sprintf(
'(%s ilike ? OR %s ilike ?)',
$classMetadata->getColumnName('title'),
$classMetadata->getColumnName('description')
),
[$content, $content],
[Types::STRING, Types::STRING]
);
}
return $query;
}
public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool
{
return $this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod);
}
}