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:
2021-09-10 17:44:01 +02:00
parent f87f03b5c0
commit 6bc83edfe9
10 changed files with 497 additions and 416 deletions

View File

@@ -20,71 +20,39 @@
namespace Chill\PersonBundle\Search;
use Chill\MainBundle\Search\AbstractSearch;
use Doctrine\ORM\EntityManagerInterface;
use Chill\PersonBundle\Repository\PersonACLAwareRepositoryInterface;
use Chill\PersonBundle\Entity\Person;
use Chill\MainBundle\Search\SearchInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Chill\MainBundle\Search\ParsingException;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Symfony\Component\Security\Core\Role\Role;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Chill\MainBundle\Form\Type\ChillDateType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Chill\MainBundle\Search\HasAdvancedSearchFormInterface;
use Doctrine\ORM\Query;
use Symfony\Component\Templating\EngineInterface;
class PersonSearch extends AbstractSearch implements ContainerAwareInterface,
HasAdvancedSearchFormInterface
class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterface
{
use ContainerAwareTrait;
/**
*
* @var EntityManagerInterface
*/
private $em;
/**
*
* @var \Chill\MainBundle\Entity\User
*/
private $user;
/**
*
* @var AuthorizationHelper
*/
private $helper;
/**
*
* @var PaginatorFactory
*/
protected $paginatorFactory;
protected EngineInterface $templating;
protected PaginatorFactory $paginatorFactory;
protected PersonACLAwareRepositoryInterface $personACLAwareRepository;
const NAME = "person_regular";
private const POSSIBLE_KEYS = [
'_default', 'firstname', 'lastname', 'birthdate', 'birthdate-before',
'birthdate-after', 'gender', 'nationality'
];
public function __construct(
EntityManagerInterface $em,
TokenStorageInterface $tokenStorage,
AuthorizationHelper $helper,
PaginatorFactory $paginatorFactory)
{
$this->em = $em;
$this->user = $tokenStorage->getToken()->getUser();
$this->helper = $helper;
EngineInterface $templating,
PaginatorFactory $paginatorFactory,
PersonACLAwareRepositoryInterface $personACLAwareRepository
) {
$this->templating = $templating;
$this->paginatorFactory = $paginatorFactory;
// throw an error if user is not a valid user
if (!$this->user instanceof \Chill\MainBundle\Entity\User) {
throw new \LogicException('The user provided must be an instance'
. ' of Chill\MainBundle\Entity\User');
}
$this->personACLAwareRepository = $personACLAwareRepository;
}
/*
@@ -120,12 +88,14 @@ class PersonSearch extends AbstractSearch implements ContainerAwareInterface,
$paginator = $this->paginatorFactory->create($total);
if ($format === 'html') {
return $this->container->get('templating')->render('@ChillPerson/Person/list_with_period.html.twig',
return $this->templating->render('@ChillPerson/Person/list_with_period.html.twig',
array(
'persons' => $this->search($terms, $start, $limit, $options),
'pattern' => $this->recomposePattern($terms, array('nationality',
'firstname', 'lastname', 'birthdate', 'gender',
'birthdate-before','birthdate-after'), $terms['_domain']),
'pattern' => $this->recomposePattern(
$terms,
\array_filter(self::POSSIBLE_KEYS, fn($item) => $item !== '_default'),
$terms['_domain']
),
'total' => $total,
'start' => $start,
'search_name' => self::NAME,
@@ -152,158 +122,81 @@ class PersonSearch extends AbstractSearch implements ContainerAwareInterface,
*/
protected function search(array $terms, $start, $limit, array $options = array())
{
$qb = $this->createQuery($terms, 'search');
[
'_default' => $default,
'firstname' => $firstname,
'lastname' => $lastname,
'birthdate' => $birthdate,
'birthdate-before' => $birthdateBefore,
'birthdate-after' => $birthdateAfter,
'gender' => $gender,
'nationality' => $countryCode,
if ($options['simplify'] ?? false) {
$qb->select(
'p.id',
$qb->expr()->concat(
'p.firstName',
$qb->expr()->literal(' '),
'p.lastName'
).'AS text'
);
} else {
$qb->select('p');
] = $terms + \array_fill_keys(self::POSSIBLE_KEYS, null);
foreach (['birthdateBefore', 'birthdateAfter', 'birthdate'] as $v) {
if (NULL !== ${$v}) {
try {
${$v} = new \DateTime(${$v});
} catch (\Exception $e) {
throw new ParsingException('The date is '
. 'not parsable', 0, $e);
}
}
}
$qb
->setMaxResults($limit)
->setFirstResult($start);
//order by firstname, lastname
$qb
->orderBy('p.firstName')
->addOrderBy('p.lastName');
if ($options['simplify'] ?? false) {
return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
} else {
return $qb->getQuery()->getResult();
}
return $this->personACLAwareRepository
->findBySearchCriteria(
$start,
$limit,
$options['simplify'] ?? false,
$default,
$firstname,
$lastname,
$birthdate,
$birthdateBefore,
$birthdateAfter,
$gender,
$countryCode,
);
}
protected function count(array $terms)
protected function count(array $terms): int
{
$qb = $this->createQuery($terms);
[
'_default' => $default,
'firstname' => $firstname,
'lastname' => $lastname,
'birthdate' => $birthdate,
'birthdate-before' => $birthdateBefore,
'birthdate-after' => $birthdateAfter,
'gender' => $gender,
'nationality' => $countryCode,
$qb->select('COUNT(p.id)');
] = $terms + \array_fill_keys(self::POSSIBLE_KEYS, null);
return $qb->getQuery()->getSingleScalarResult();
}
private $_cacheQuery = array();
/**
*
* @param array $terms
* @return \Doctrine\ORM\QueryBuilder
*/
public function createQuery(array $terms)
{
//get from cache
$cacheKey = md5(serialize($terms));
if (array_key_exists($cacheKey, $this->_cacheQuery)) {
return clone $this->_cacheQuery[$cacheKey];
}
$qb = $this->em->createQueryBuilder();
$qb->from('ChillPersonBundle:Person', 'p');
if (array_key_exists('firstname', $terms)) {
$qb->andWhere($qb->expr()->like('UNACCENT(LOWER(p.firstName))', ':firstname'))
->setParameter('firstname', '%'.$terms['firstname'].'%');
}
if (array_key_exists('lastname', $terms)) {
$qb->andWhere($qb->expr()->like('UNACCENT(LOWER(p.lastName))', ':lastname'))
->setParameter('lastname', '%'.$terms['lastname'].'%');
}
foreach (['birthdate', 'birthdate-before', 'birthdate-after'] as $key)
if (array_key_exists($key, $terms)) {
try {
$date = new \DateTime($terms[$key]);
} catch (\Exception $ex) {
throw new ParsingException('The date is '
. 'not parsable', 0, $ex);
}
switch($key) {
case 'birthdate':
$qb->andWhere($qb->expr()->eq('p.birthdate', ':birthdate'))
->setParameter('birthdate', $date);
break;
case 'birthdate-before':
$qb->andWhere($qb->expr()->lt('p.birthdate', ':birthdatebefore'))
->setParameter('birthdatebefore', $date);
break;
case 'birthdate-after':
$qb->andWhere($qb->expr()->gt('p.birthdate', ':birthdateafter'))
->setParameter('birthdateafter', $date);
break;
default:
throw new \LogicException("this case $key should not exists");
}
}
if (array_key_exists('gender', $terms)) {
if (!in_array($terms['gender'], array(Person::MALE_GENDER, Person::FEMALE_GENDER))) {
throw new ParsingException('The gender '
.$terms['gender'].' is not accepted. Should be "'.Person::MALE_GENDER
.'" or "'.Person::FEMALE_GENDER.'"');
}
$qb->andWhere($qb->expr()->eq('p.gender', ':gender'))
->setParameter('gender', $terms['gender']);
}
if (array_key_exists('nationality', $terms)) {
try {
$country = $this->em->createQuery('SELECT c FROM '
. 'ChillMainBundle:Country c WHERE '
. 'LOWER(c.countryCode) LIKE :code')
->setParameter('code', $terms['nationality'])
->getSingleResult();
} catch (\Doctrine\ORM\NoResultException $ex) {
throw new ParsingException('The country code "'.$terms['nationality'].'" '
. ', used in nationality, is unknow', 0, $ex);
}
$qb->andWhere($qb->expr()->eq('p.nationality', ':nationality'))
->setParameter('nationality', $country);
}
if ($terms['_default'] !== '') {
$grams = explode(' ', $terms['_default']);
foreach($grams as $key => $gram) {
$qb->andWhere($qb->expr()
->like('p.fullnameCanonical', 'UNACCENT(LOWER(:default_'.$key.'))'))
->setParameter('default_'.$key, '%'.$gram.'%');
foreach (['birthdateBefore', 'birthdateAfter', 'birthdate'] as $v) {
if (NULL !== ${$v}) {
try {
${$v} = new \DateTime(${$v});
} catch (\Exception $e) {
throw new ParsingException('The date is '
. 'not parsable', 0, $e);
}
}
}
//restraint center for security
$reachableCenters = $this->helper->getReachableCenters($this->user,
new Role('CHILL_PERSON_SEE'));
$qb->andWhere(
$qb->expr()->orX(
$qb->expr()
->in('p.center', ':centers'),
$qb->expr()
->isNull('p.center')
)
);
$qb->setParameter('centers', $reachableCenters);
$this->_cacheQuery[$cacheKey] = $qb;
return clone $qb;
return $this->personACLAwareRepository
->countBySearchCriteria(
$default,
$firstname,
$lastname,
$birthdate,
$birthdateBefore,
$birthdateAfter,
$gender,
$countryCode,
);
}
public function buildForm(FormBuilderInterface $builder)
@@ -396,4 +289,10 @@ class PersonSearch extends AbstractSearch implements ContainerAwareInterface,
return 'Search within persons';
}
public static function getAlias(): string
{
return self::NAME;
}
}