mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-21 07:03:49 +00:00
Refactor PersonSearch and create PersonACLAwareRepository
The search api delegates the query to a person acl aware "repository" (although this does not implements ObjectRepository interface).
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Repository\CountryRepository;
|
||||
use Chill\MainBundle\Search\ParsingException;
|
||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\NonUniqueResultException;
|
||||
use Doctrine\ORM\NoResultException;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\Security\Core\Role\Role;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
|
||||
final class PersonACLAwareRepository implements PersonACLAwareRepositoryInterface
|
||||
{
|
||||
private Security $security;
|
||||
|
||||
private EntityManagerInterface $em;
|
||||
|
||||
private CountryRepository $countryRepository;
|
||||
|
||||
private AuthorizationHelper $authorizationHelper;
|
||||
|
||||
public function __construct(
|
||||
Security $security,
|
||||
EntityManagerInterface $em,
|
||||
CountryRepository $countryRepository,
|
||||
AuthorizationHelper $authorizationHelper
|
||||
) {
|
||||
$this->security = $security;
|
||||
$this->em = $em;
|
||||
$this->countryRepository = $countryRepository;
|
||||
$this->authorizationHelper = $authorizationHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|Person[]
|
||||
* @throws NonUniqueResultException
|
||||
* @throws ParsingException
|
||||
*/
|
||||
public function findBySearchCriteria(
|
||||
int $start,
|
||||
int $limit,
|
||||
bool $simplify = false,
|
||||
string $default = null,
|
||||
string $firstname = null,
|
||||
string $lastname = null,
|
||||
?\DateTime $birthdate = null,
|
||||
?\DateTime $birthdateBefore = null,
|
||||
?\DateTime $birthdateAfter = null,
|
||||
string $gender = null,
|
||||
string $countryCode = null
|
||||
): array {
|
||||
$qb = $this->createSearchQuery($default, $firstname, $lastname,
|
||||
$birthdate, $birthdateBefore, $birthdateAfter, $gender,
|
||||
$countryCode);
|
||||
$this->addACLClauses($qb, 'p');
|
||||
|
||||
if ($simplify) {
|
||||
$qb->select(
|
||||
'p.id',
|
||||
$qb->expr()->concat(
|
||||
'p.firstName',
|
||||
$qb->expr()->literal(' '),
|
||||
'p.lastName'
|
||||
).'AS text'
|
||||
);
|
||||
} else {
|
||||
$qb->select('p');
|
||||
}
|
||||
|
||||
$qb
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult($start);
|
||||
|
||||
//order by firstname, lastname
|
||||
$qb
|
||||
->orderBy('p.firstName')
|
||||
->addOrderBy('p.lastName');
|
||||
|
||||
if ($simplify) {
|
||||
return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
|
||||
} else {
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
}
|
||||
|
||||
public function countBySearchCriteria(
|
||||
string $default = null,
|
||||
string $firstname = null,
|
||||
string $lastname = null,
|
||||
?\DateTime $birthdate = null,
|
||||
?\DateTime $birthdateBefore = null,
|
||||
?\DateTime $birthdateAfter = null,
|
||||
string $gender = null,
|
||||
string $countryCode = null
|
||||
): int {
|
||||
$qb = $this->createSearchQuery($default, $firstname, $lastname,
|
||||
$birthdate, $birthdateBefore, $birthdateAfter, $gender,
|
||||
$countryCode);
|
||||
$this->addACLClauses($qb, 'p');
|
||||
|
||||
$qb->select('COUNT(p.id)');
|
||||
|
||||
return $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function findBySimilaritySearch(string $pattern, int $firstResult,
|
||||
int $maxResult, bool $simplify = false)
|
||||
{
|
||||
$qb = $this->createSimilarityQuery($pattern);
|
||||
$this->addACLClauses($qb, 'sp');
|
||||
|
||||
if ($simplify) {
|
||||
$qb->select(
|
||||
'sp.id',
|
||||
$qb->expr()->concat(
|
||||
'sp.firstName',
|
||||
$qb->expr()->literal(' '),
|
||||
'sp.lastName'
|
||||
).'AS text'
|
||||
);
|
||||
} else {
|
||||
$qb->select('p');
|
||||
}
|
||||
|
||||
$qb
|
||||
->setMaxResults($maxResult)
|
||||
->setFirstResult($firstResult);
|
||||
|
||||
//order by firstname, lastname
|
||||
$qb
|
||||
->orderBy('sp.firstName')
|
||||
->addOrderBy('sp.lastName');
|
||||
|
||||
if ($simplify) {
|
||||
return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
|
||||
} else {
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
}
|
||||
|
||||
public function countBySimilaritySearch(string $pattern)
|
||||
{
|
||||
$qb = $this->createSimilarityQuery($pattern);
|
||||
$this->addACLClauses($qb, 'sp');
|
||||
|
||||
$qb->select('COUNT(sp.id)');
|
||||
|
||||
return $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
private function createSearchQuery(
|
||||
string $default = null,
|
||||
string $firstname = null,
|
||||
string $lastname = null,
|
||||
?\DateTime $birthdate = null,
|
||||
?\DateTime $birthdateBefore = null,
|
||||
?\DateTime $birthdateAfter = null,
|
||||
string $gender = null,
|
||||
string $countryCode = null
|
||||
): QueryBuilder {
|
||||
|
||||
if (!$this->security->getUser() instanceof User) {
|
||||
throw new \RuntimeException("Search must be performed by a valid user");
|
||||
}
|
||||
$qb = $this->em->createQueryBuilder();
|
||||
$qb->from(Person::class, 'p');
|
||||
|
||||
if (NULL !== $firstname) {
|
||||
$qb->andWhere($qb->expr()->like('UNACCENT(LOWER(p.firstName))', ':firstname'))
|
||||
->setParameter('firstname', '%'.$firstname.'%');
|
||||
}
|
||||
|
||||
if (NULL !== $lastname) {
|
||||
$qb->andWhere($qb->expr()->like('UNACCENT(LOWER(p.lastName))', ':lastname'))
|
||||
->setParameter('lastname', '%'.$lastname.'%');
|
||||
}
|
||||
|
||||
if (NULL !== $birthdate) {
|
||||
$qb->andWhere($qb->expr()->eq('s.birthdate', ':birthdate'))
|
||||
->setParameter('birthdate', $birthdate);
|
||||
}
|
||||
|
||||
if (NULL !== $birthdateAfter) {
|
||||
$qb->andWhere($qb->expr()->gt('p.birthdate', ':birthdateafter'))
|
||||
->setParameter('birthdateafter', $birthdateAfter);
|
||||
}
|
||||
|
||||
if (NULL !== $birthdateBefore) {
|
||||
$qb->andWhere($qb->expr()->lt('p.birthdate', ':birthdatebefore'))
|
||||
->setParameter('birthdatebefore', $birthdateBefore);
|
||||
}
|
||||
|
||||
if (NULL !== $gender) {
|
||||
$qb->andWhere($qb->expr()->eq('p.gender', ':gender'))
|
||||
->setParameter('gender', $gender);
|
||||
}
|
||||
|
||||
if (NULL !== $countryCode) {
|
||||
try {
|
||||
$country = $this->countryRepository->findOneBy(['countryCode' => $countryCode]);
|
||||
} catch (NoResultException $ex) {
|
||||
throw new ParsingException('The country code "'.$countryCode.'" '
|
||||
. ', used in nationality, is unknow', 0, $ex);
|
||||
} catch (NonUniqueResultException $e) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$qb->andWhere($qb->expr()->eq('p.nationality', ':nationality'))
|
||||
->setParameter('nationality', $country);
|
||||
}
|
||||
|
||||
if (NULL !== $default) {
|
||||
$grams = explode(' ', $default);
|
||||
|
||||
foreach($grams as $key => $gram) {
|
||||
$qb->andWhere($qb->expr()
|
||||
->like('p.fullnameCanonical', 'UNACCENT(LOWER(:default_'.$key.'))'))
|
||||
->setParameter('default_'.$key, '%'.$gram.'%');
|
||||
}
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
private function addACLClauses(QueryBuilder $qb, string $personAlias): void
|
||||
{
|
||||
// restrict center for security
|
||||
$reachableCenters = $this->authorizationHelper
|
||||
->getReachableCenters($this->security->getUser(), 'CHILL_PERSON_SEE');
|
||||
$qb->andWhere(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()
|
||||
->in($personAlias.'.center', ':centers'),
|
||||
$qb->expr()
|
||||
->isNull($personAlias.'.center')
|
||||
)
|
||||
);
|
||||
$qb->setParameter('centers', $reachableCenters);
|
||||
}
|
||||
|
||||
private function createSimilarityQuery($pattern): QueryBuilder
|
||||
{
|
||||
$qb = $this->em->createQueryBuilder();
|
||||
|
||||
$qb->from(Person::class, 'sp');
|
||||
|
||||
$grams = explode(' ', $pattern);
|
||||
|
||||
foreach($grams as $key => $gram) {
|
||||
$qb->andWhere('SIMILARITY(sp.fullnameCanonical, UNACCENT(LOWER(:default_'.$key.')) ) >= 0.15')
|
||||
->setParameter('default_'.$key, '%'.$gram.'%');
|
||||
}
|
||||
|
||||
$qb->andWhere($qb->expr()
|
||||
->notIn(
|
||||
'sp.id',
|
||||
$this->createSearchQuery($pattern)
|
||||
->addSelect('p.id')
|
||||
->getDQL()
|
||||
)
|
||||
);
|
||||
|
||||
return $qb;
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\PersonBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Search\ParsingException;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Doctrine\ORM\NonUniqueResultException;
|
||||
|
||||
interface PersonACLAwareRepositoryInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @return array|Person[]
|
||||
* @throws NonUniqueResultException
|
||||
* @throws ParsingException
|
||||
*/
|
||||
public function findBySearchCriteria(
|
||||
int $start,
|
||||
int $limit,
|
||||
bool $simplify = false,
|
||||
string $default = null,
|
||||
string $firstname = null,
|
||||
string $lastname = null,
|
||||
?\DateTime $birthdate = null,
|
||||
?\DateTime $birthdateBefore = null,
|
||||
?\DateTime $birthdateAfter = null,
|
||||
string $gender = null,
|
||||
string $countryCode = null
|
||||
): array;
|
||||
|
||||
public function countBySearchCriteria(
|
||||
string $default = null,
|
||||
string $firstname = null,
|
||||
string $lastname = null,
|
||||
?\DateTime $birthdate = null,
|
||||
?\DateTime $birthdateBefore = null,
|
||||
?\DateTime $birthdateAfter = null,
|
||||
string $gender = null,
|
||||
string $countryCode = null
|
||||
);
|
||||
|
||||
public function findBySimilaritySearch(
|
||||
string $pattern,
|
||||
int $firstResult,
|
||||
int $maxResult,
|
||||
bool $simplify = false
|
||||
);
|
||||
|
||||
public function countBySimilaritySearch(string $pattern);
|
||||
}
|
@@ -23,9 +23,11 @@ use Chill\PersonBundle\Entity\Person;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
use UnexpectedValueException;
|
||||
|
||||
|
||||
final class PersonRepository
|
||||
final class PersonRepository implements ObjectRepository
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
@@ -44,6 +46,26 @@ final class PersonRepository
|
||||
return $this->repository->findBy(['id' => $ids]);
|
||||
}
|
||||
|
||||
public function findAll()
|
||||
{
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
|
||||
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null)
|
||||
{
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria)
|
||||
{
|
||||
return $this->repository->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function getClassName()
|
||||
{
|
||||
return Person::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $centers
|
||||
* @param $firstResult
|
||||
|
Reference in New Issue
Block a user