chill-bundles/Search/SimilarityPersonSearch.php

266 lines
7.3 KiB
PHP

<?php
namespace Chill\PersonBundle\Search;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Search\AbstractSearch;
use Chill\MainBundle\Search\SearchInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Role\Role;
/**
* Class SimilarityPersonSearch
*
* @package Chill\PersonBundle\Search
*/
class SimilarityPersonSearch extends AbstractSearch
{
use ContainerAwareTrait;
/**
*
* @var EntityManagerInterface
*/
private $em;
/**
*
* @var \Chill\MainBundle\Entity\User
*/
private $user;
/**
*
* @var AuthorizationHelper
*/
private $helper;
/**
*
* @var PaginatorFactory
*/
protected $paginatorFactory;
const NAME = "person_similarity";
/**
*
* @var PersonSearch
*/
private $personSearch;
/**
* SimilarityPersonSearch constructor.
*
* @param EntityManagerInterface $em
* @param TokenStorageInterface $tokenStorage
* @param AuthorizationHelper $helper
* @param PaginatorFactory $paginatorFactory
* @param PersonSearch $personSearch
*/
public function __construct(
EntityManagerInterface $em,
TokenStorageInterface $tokenStorage,
AuthorizationHelper $helper,
PaginatorFactory $paginatorFactory,
PersonSearch $personSearch)
{
$this->em = $em;
$this->user = $tokenStorage->getToken()->getUser();
$this->helper = $helper;
$this->paginatorFactory = $paginatorFactory;
$this->personSearch = $personSearch;
// 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');
}
}
/*
* (non-PHPdoc)
* @see \Chill\MainBundle\Search\SearchInterface::getOrder()
*/
public function getOrder()
{
return 200;
}
/*
* (non-PHPdoc)
* @see \Chill\MainBundle\Search\SearchInterface::isActiveByDefault()
*/
public function isActiveByDefault()
{
return true;
}
public function supports($domain, $format)
{
return 'person' === $domain;
}
/**
* @param array $terms
* @param int $start
* @param int $limit
* @param array $options
* @param string $format
* @return array
*/
public function renderResult(array $terms, $start = 0, $limit = 50, array $options = array(), $format = 'html')
{
$total = $this->count($terms);
$paginator = $this->paginatorFactory->create($total);
if ($format === 'html')
{
if ($total !== 0)
{
return $this->container->get('templating')->render('ChillPersonBundle:Person:list.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']),
'total' => $total,
'start' => $start,
'search_name' => self::NAME,
'preview' => $options[SearchInterface::SEARCH_PREVIEW_OPTION],
'paginator' => $paginator,
'title' => "Similar persons"
));
}
else {
return null;
}
} elseif ($format === 'json')
{
return [
'results' => $this->search($terms, $start, $limit, \array_merge($options, [ 'simplify' => true ])),
'pagination' => [
'more' => $paginator->hasNextPage()
]
];
}
}
/**
*
* @param string $pattern
* @param int $start
* @param int $limit
* @param array $options
* @return Person[]
*/
protected function search(array $terms, $start, $limit, array $options = array())
{
$qb = $this->createQuery($terms, 'search');
if ($options['simplify'] ?? false) {
$qb->select(
'sp.id',
$qb->expr()->concat(
'sp.firstName',
$qb->expr()->literal(' '),
'sp.lastName'
).'AS text'
);
} else {
$qb->select('sp');
}
$qb
->setMaxResults($limit)
->setFirstResult($start);
//order by firstname, lastname
$qb
->orderBy('sp.firstName')
->addOrderBy('sp.lastName');
if ($options['simplify'] ?? false) {
return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
} else {
return $qb->getQuery()->getResult();
}
}
protected function count(array $terms)
{
$qb = $this->createQuery($terms);
$qb->select('COUNT(sp.id)');
return $qb->getQuery()->getSingleScalarResult();
}
private $_cacheQuery = array();
/**
*
* @param array $terms
* @return \Doctrine\ORM\QueryBuilder
*/
protected 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 ->select('sp')
->from('ChillPersonBundle:Person', 'sp');
if ($terms['_default'] !== '') {
$grams = explode(' ', $terms['_default']);
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->personSearch
->createQuery($terms)
->addSelect('p.id')
->getDQL()
)
);
}
//restraint center for security
$reachableCenters = $this->helper->getReachableCenters($this->user,
new Role('CHILL_PERSON_SEE'));
$qb->andWhere($qb->expr()
->in('sp.center', ':centers'))
->setParameter('centers', $reachableCenters)
;
$this->_cacheQuery[$cacheKey] = $qb;
return clone $qb;
}
}