137 lines
4.4 KiB
PHP

<?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\MainBundle\Repository;
use Chill\MainBundle\Entity\Country;
use Chill\MainBundle\Entity\PostalCode;
use Chill\MainBundle\Search\SearchApiQuery;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
use RuntimeException;
final class PostalCodeRepository implements PostalCodeRepositoryInterface
{
private EntityManagerInterface $entityManager;
private EntityRepository $repository;
public function __construct(EntityManagerInterface $entityManager)
{
$this->repository = $entityManager->getRepository($this->getClassName());
$this->entityManager = $entityManager;
}
public function countByPattern(string $pattern, ?Country $country): int
{
$query = $this->buildQueryByPattern($pattern, $country);
$sql = $query->buildQuery(true);
$rsm = new ResultSetMapping();
$rsm->addScalarResult('c', 'c');
$nq = $this->entityManager->createNativeQuery($sql, $rsm)
->setParameters($query->buildParameters(true));
return (int) $nq->getSingleResult()['c'];
}
public function find($id, $lockMode = null, $lockVersion = null): ?PostalCode
{
return $this->repository->find($id, $lockMode, $lockVersion);
}
public function findAll(): array
{
return $this->repository->findAll();
}
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
{
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
}
public function findByPattern(string $pattern, ?Country $country, ?int $start = 0, ?int $limit = 50): array
{
$query = $this->buildQueryByPattern($pattern, $country);
$rsm = new ResultSetMappingBuilder($this->entityManager);
$rsm->addRootEntityFromClassMetadata(PostalCode::class, 'cmpc');
$query->addSelectClause($rsm->generateSelectClause());
$sql = strtr(
$query->buildQuery() . 'ORDER BY pertinence DESC, canonical ASC OFFSET ? LIMIT ? ',
// little hack for adding sql method to point
['cmpc.center AS center' => 'ST_AsGeojson(cmpc.center) AS center']
);
$parameters = [...$query->buildParameters(), $start, $limit];
return $this->entityManager->createNativeQuery($sql, $rsm)
->setParameters($parameters)
->getResult();
}
public function findOneBy(array $criteria, ?array $orderBy = null): ?PostalCode
{
return $this->repository->findOneBy($criteria, $orderBy);
}
public function getClassName(): string
{
return PostalCode::class;
}
private function buildQueryByPattern(string $pattern, ?Country $country): SearchApiQuery
{
$pattern = trim($pattern);
if ('' === $pattern) {
throw new RuntimeException('the search pattern must not be empty');
}
$query = new SearchApiQuery();
$query
->setFromClause('chill_main_postal_code cmpc')
->andWhereClause('cmpc.origin = 0');
if (null !== $country) {
$query->andWhereClause('cmpc.country_id = ?', [$country->getId()]);
}
$pertinenceClause = ['STRICT_WORD_SIMILARITY(canonical, UNACCENT(?))'];
$pertinenceArgs = [$pattern];
$andWhere = ['canonical %>> UNACCENT(?)'];
$andWhereArgs = [$pattern];
foreach (explode(' ', $pattern) as $part) {
$part = trim($part);
if ('' === $part) {
continue;
}
$andWhere[] = "canonical LIKE '%' || UNACCENT(LOWER(?)) || '%'";
$andWhereArgs[] = $part;
$pertinenceClause[] =
"(EXISTS (SELECT 1 FROM unnest(string_to_array(canonical, ' ')) AS t WHERE starts_with(t, UNACCENT(LOWER(?)))))::int";
$pertinenceClause[] =
'(canonical LIKE UNACCENT(LOWER(?)))::int';
array_push($pertinenceArgs, $part, $part);
}
$query
->setSelectPertinence(implode(' + ', $pertinenceClause), $pertinenceArgs)
->andWhereClause(implode(' AND ', $andWhere), $andWhereArgs);
return $query;
}
}