mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
bootstrap generic doc manager and associated services
This commit is contained in:
parent
977299192f
commit
afcd6e0605
@ -0,0 +1,48 @@
|
||||
<?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\Controller;
|
||||
|
||||
use Chill\DocStoreBundle\GenericDoc\Manager;
|
||||
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
final readonly class GenericDocForAccompanyingPeriodController
|
||||
{
|
||||
public function __construct(
|
||||
private Security $security,
|
||||
private Manager $manager
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccompanyingPeriod $accompanyingPeriod
|
||||
* @return Response
|
||||
* @throws \Doctrine\DBAL\Exception
|
||||
*
|
||||
* @Route("/{_locale}/doc-store/generic-doc/by-period/{id}/index", name="chill_docstore_generic-doc_by-period_index")
|
||||
*/
|
||||
public function list(AccompanyingPeriod $accompanyingPeriod): Response
|
||||
{
|
||||
if (!$this->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);
|
||||
}
|
||||
|
||||
}
|
168
src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php
Normal file
168
src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php
Normal file
@ -0,0 +1,168 @@
|
||||
<?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;
|
||||
|
||||
use Nelmio\Alice\Throwable\Exception\FixtureBuilder\Denormalizer\UnexpectedValueException;
|
||||
|
||||
class FetchQuery implements FetchQueryInterface
|
||||
{
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private array $joins = [];
|
||||
|
||||
/**
|
||||
* @var list<mixed>
|
||||
*/
|
||||
private array $joinParams = [];
|
||||
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private array $wheres = [];
|
||||
|
||||
/**
|
||||
* @var list<mixed>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
<?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;
|
||||
|
||||
interface FetchQueryInterface
|
||||
{
|
||||
public function getSelectKeyString(): string;
|
||||
|
||||
public function getSelectIdentifierJsonB(): string;
|
||||
|
||||
/**
|
||||
* @return list<mixed>
|
||||
*/
|
||||
public function getSelectIdentifierParams(): array;
|
||||
|
||||
public function getSelectDate(): string;
|
||||
|
||||
/**
|
||||
* @return list<mixed>
|
||||
*/
|
||||
public function getSelectDateParams(): array;
|
||||
|
||||
public function getFromQuery(): string;
|
||||
|
||||
/**
|
||||
* @return list<mixed>
|
||||
*/
|
||||
public function getFromQueryParams(): array;
|
||||
|
||||
public function getWhereQuery(): string;
|
||||
|
||||
/**
|
||||
* @return list<mixed>
|
||||
*/
|
||||
public function getWhereQueryParams(): array;
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
<?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;
|
||||
|
||||
final readonly class FetchQueryToSqlBuilder
|
||||
{
|
||||
private const SQL = <<<'SQL'
|
||||
SELECT
|
||||
'{{ key }}' AS key,
|
||||
{{ identifiers }} AS identifiers,
|
||||
{{ date }} AS doc_date
|
||||
FROM {{ from }}
|
||||
WHERE {{ where }}
|
||||
SQL;
|
||||
|
||||
/**
|
||||
* @param FetchQueryInterface $query
|
||||
* @return array{sql: string, params: list<mixed>}
|
||||
*/
|
||||
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];
|
||||
}
|
||||
}
|
22
src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php
Normal file
22
src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?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;
|
||||
|
||||
class GenericDocDTO
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $key,
|
||||
public readonly array $identifiers,
|
||||
public readonly \DateTimeImmutable $docDate
|
||||
) {
|
||||
}
|
||||
}
|
102
src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php
Normal file
102
src/Bundle/ChillDocStoreBundle/GenericDoc/Manager.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?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;
|
||||
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Exception;
|
||||
|
||||
class Manager
|
||||
{
|
||||
private readonly FetchQueryToSqlBuilder $builder;
|
||||
|
||||
public function __construct(
|
||||
/**
|
||||
* @var iterable<ProviderForAccompanyingPeriodInterface>
|
||||
*/
|
||||
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];
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
<?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;
|
||||
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
|
||||
interface ProviderForAccompanyingPeriodInterface
|
||||
{
|
||||
public function buildFetchQueryForAccompanyingPeriod(
|
||||
AccompanyingPeriod $accompanyingPeriod,
|
||||
?\DateTimeImmutable $startDate = null,
|
||||
?\DateTimeImmutable $endDate = null,
|
||||
?string $content = null,
|
||||
?string $origin = null
|
||||
): FetchQueryInterface;
|
||||
|
||||
/**
|
||||
* Return true if the user is allowed to see some documents for this provider.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAllowed(): bool;
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
<?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\Tests\GenericDoc;
|
||||
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQueryToSqlBuilder;
|
||||
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class FetchQueryToSqlBuilderTest extends KernelTestCase
|
||||
{
|
||||
public function testToSql(): void
|
||||
{
|
||||
$query = new FetchQuery(
|
||||
'test',
|
||||
'jsonb_build_object(\'id\', a.column)',
|
||||
'a.datecolumn',
|
||||
'my_table a'
|
||||
);
|
||||
$query->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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
<?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\Tests\GenericDoc;
|
||||
|
||||
use Chill\DocStoreBundle\GenericDoc\Manager;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
class ManagerTest extends KernelTestCase
|
||||
{
|
||||
private Manager $manager;
|
||||
|
||||
private EntityManagerInterface $em;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
|
||||
$this->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);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user