mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-21 07:03:49 +00:00
Issue316 addresses search by postal code
This commit is contained in:
@@ -12,17 +12,29 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\AddressReference;
|
||||
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 Doctrine\Persistence\ObjectRepository;
|
||||
use RuntimeException;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function strtr;
|
||||
use function trim;
|
||||
|
||||
final class AddressReferenceRepository implements ObjectRepository
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->repository = $entityManager->getRepository(AddressReference::class);
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
public function countAll(): int
|
||||
@@ -33,6 +45,18 @@ final class AddressReferenceRepository implements ObjectRepository
|
||||
return $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countByPostalCodePattern(PostalCode $postalCode, string $pattern): int
|
||||
{
|
||||
$query = $this->buildQueryByPostalCodePattern($postalCode, $pattern);
|
||||
$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): ?AddressReference
|
||||
{
|
||||
return $this->repository->find($id, $lockMode, $lockVersion);
|
||||
@@ -57,6 +81,33 @@ final class AddressReferenceRepository implements ObjectRepository
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AddressReference[]|array
|
||||
*/
|
||||
public function findByPostalCodePattern(PostalCode $postalCode, string $pattern, bool $simplify = false, int $start = 0, int $limit = 50): array
|
||||
{
|
||||
$query = $this->buildQueryByPostalCodePattern($postalCode, $pattern);
|
||||
|
||||
if (!$simplify) {
|
||||
$rsm = new ResultSetMappingBuilder($this->entityManager);
|
||||
$rsm->addRootEntityFromClassMetadata(AddressReference::class, 'cma');
|
||||
$query->addSelectClause($rsm->generateSelectClause());
|
||||
} else {
|
||||
throw new RuntimeException('not implemented');
|
||||
}
|
||||
|
||||
$sql = strtr(
|
||||
$query->buildQuery() . 'ORDER BY pertinence DESC, lpad(streetnumber, 10, \'0\') ASC OFFSET ? LIMIT ? ',
|
||||
// little hack for adding sql method to point
|
||||
['cma.point AS point' => 'ST_AsGeojson(cma.point) AS point']
|
||||
);
|
||||
$parameters = [...$query->buildParameters(), $start, $limit];
|
||||
|
||||
return $this->entityManager->createNativeQuery($sql, $rsm)
|
||||
->setParameters($parameters)
|
||||
->getResult();
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria, ?array $orderBy = null): ?AddressReference
|
||||
{
|
||||
return $this->repository->findOneBy($criteria, $orderBy);
|
||||
@@ -66,4 +117,44 @@ final class AddressReferenceRepository implements ObjectRepository
|
||||
{
|
||||
return AddressReference::class;
|
||||
}
|
||||
|
||||
private function buildQueryByPostalCodePattern(PostalCode $postalCode, string $pattern): SearchApiQuery
|
||||
{
|
||||
$pattern = trim($pattern);
|
||||
|
||||
if ('' === $pattern) {
|
||||
throw new RuntimeException('the search pattern must not be empty');
|
||||
}
|
||||
$query = new SearchApiQuery();
|
||||
|
||||
$query
|
||||
->setFromClause('chill_main_address_reference cma')
|
||||
->andWhereClause('postcode_id = ?', [$postalCode->getId()]);
|
||||
|
||||
$pertinenceClause = ['STRICT_WORD_SIMILARITY(addresscanonical, UNACCENT(?))'];
|
||||
$pertinenceArgs = [$pattern];
|
||||
$orWhere = ['addresscanonical %>> UNACCENT(?)'];
|
||||
$orWhereArgs = [$pattern];
|
||||
|
||||
foreach (explode(' ', $pattern) as $part) {
|
||||
$part = trim($part);
|
||||
|
||||
if ('' === $part) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$orWhere[] = "addresscanonical LIKE '%' || UNACCENT(LOWER(?)) || '%'";
|
||||
$orWhereArgs[] = $part;
|
||||
$pertinenceClause[] =
|
||||
"(EXISTS (SELECT 1 FROM unnest(string_to_array(addresscanonical, ' ')) AS t WHERE starts_with(t, UNACCENT(LOWER(?)))))::int";
|
||||
$pertinenceClause[] =
|
||||
'(addresscanonical LIKE UNACCENT(LOWER(?)))::int';
|
||||
array_push($pertinenceArgs, $part, $part);
|
||||
}
|
||||
$query
|
||||
->setSelectPertinence(implode(' + ', $pertinenceClause), $pertinenceArgs)
|
||||
->andWhereClause(implode(' OR ', $orWhere), $orWhereArgs);
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
|
@@ -11,18 +11,39 @@ declare(strict_types=1);
|
||||
|
||||
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 Doctrine\Persistence\ObjectRepository;
|
||||
use RuntimeException;
|
||||
|
||||
final class PostalCodeRepository implements ObjectRepository
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->repository = $entityManager->getRepository(PostalCode::class);
|
||||
$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
|
||||
@@ -49,6 +70,26 @@ final class PostalCodeRepository implements ObjectRepository
|
||||
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);
|
||||
@@ -58,4 +99,48 @@ final class PostalCodeRepository implements ObjectRepository
|
||||
{
|
||||
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];
|
||||
$orWhere = ['canonical %>> UNACCENT(?)'];
|
||||
$orWhereArgs = [$pattern];
|
||||
|
||||
foreach (explode(' ', $pattern) as $part) {
|
||||
$part = trim($part);
|
||||
|
||||
if ('' === $part) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$orWhere[] = "canonical LIKE '%' || UNACCENT(LOWER(?)) || '%'";
|
||||
$orWhereArgs[] = $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(' OR ', $orWhere), $orWhereArgs);
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user