From 3599a94f9467c86ae17be9b19e7b792f6d1d3cc0 Mon Sep 17 00:00:00 2001 From: Mat Date: Tue, 23 Oct 2018 16:18:09 +0200 Subject: [PATCH 1/9] Add fullnameCanonical column for trigram matching (fast searching) --- Entity/Person.php | 7 +- Resources/config/doctrine/Person.orm.yml | 3 + .../migrations/Version20181023101621.php | 79 +++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 Resources/migrations/Version20181023101621.php diff --git a/Entity/Person.php b/Entity/Person.php index 7d19ccc3c..11bad306a 100644 --- a/Entity/Person.php +++ b/Entity/Person.php @@ -113,7 +113,12 @@ class Person implements HasCenterInterface { * @var \Doctrine\Common\Collections\Collection */ private $addresses; - + + /** + * @var string + */ + private $fullnameCanonical; + public function __construct(\DateTime $opening = null) { $this->accompanyingPeriods = new ArrayCollection(); $this->spokenLanguages = new ArrayCollection(); diff --git a/Resources/config/doctrine/Person.orm.yml b/Resources/config/doctrine/Person.orm.yml index df911068b..543757f9a 100644 --- a/Resources/config/doctrine/Person.orm.yml +++ b/Resources/config/doctrine/Person.orm.yml @@ -51,6 +51,9 @@ Chill\PersonBundle\Entity\Person: type: text nullable: true length: 40 + fullnameCanonical: + type: string + length: 255 manyToOne: countryOfBirth: targetEntity: Chill\MainBundle\Entity\Country diff --git a/Resources/migrations/Version20181023101621.php b/Resources/migrations/Version20181023101621.php new file mode 100644 index 000000000..24632fc12 --- /dev/null +++ b/Resources/migrations/Version20181023101621.php @@ -0,0 +1,79 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql("ALTER TABLE chill_person_person ADD fullnameCanonical VARCHAR(255) DEFAULT '' "); + $this->addSql("UPDATE chill_person_person SET fullnameCanonical=LOWER(UNACCENT(CONCAT(firstname, ' ', lastname)))"); + $this->addSql("CREATE INDEX fullnameCanonical_trgm_idx ON chill_person_person USING GIN (fullnameCanonical gin_trgm_ops)"); + + $this->addSql(<<<'SQL' + CREATE OR REPLACE FUNCTION canonicalize_fullname_on_update() RETURNS TRIGGER AS + $BODY$ + BEGIN + IF NEW.firstname <> OLD.firstname OR NEW.lastname <> OLD.lastname + THEN + UPDATE chill_person_person + SET fullnameCanonical=LOWER(UNACCENT(CONCAT(NEW.firstname, ' ', NEW.lastname))) + WHERE id=NEW.id; + END IF; + RETURN NEW; + END; + $BODY$ LANGUAGE PLPGSQL; +SQL + ); + $this->addSql(<<addSql(<<<'SQL' + CREATE OR REPLACE FUNCTION canonicalize_fullname_on_insert() RETURNS TRIGGER AS + $BODY$ + BEGIN + UPDATE chill_person_person + SET fullnameCanonical=LOWER(UNACCENT(CONCAT(NEW.firstname, ' ', NEW.lastname))) + WHERE id=NEW.id; + RETURN NEW; + END; + $BODY$ LANGUAGE PLPGSQL; +SQL + ); + $this->addSql(<<abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('DROP INDEX fullnameCanonical_trgm_idx'); + $this->addSql('ALTER TABLE chill_person_person DROP fullnameCanonical'); + $this->addSql('DROP TRIGGER canonicalize_fullname_on_update ON chill_person_person'); + $this->addSql('DROP FUNCTION canonicalize_fullname_on_update()'); + $this->addSql('DROP TRIGGER canonicalize_fullname_on_insert ON chill_person_person'); + $this->addSql('DROP FUNCTION canonicalize_fullname_on_insert()'); + } +} From 290a9049915cffef4960dcb8360118c9c51a23ef Mon Sep 17 00:00:00 2001 From: Mat Date: Tue, 30 Oct 2018 13:22:23 +0100 Subject: [PATCH 2/9] add similarity test in search query when adding person --- Search/SimilarPersonMatcher.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Search/SimilarPersonMatcher.php b/Search/SimilarPersonMatcher.php index 2667f061f..ab13e607a 100644 --- a/Search/SimilarPersonMatcher.php +++ b/Search/SimilarPersonMatcher.php @@ -75,14 +75,18 @@ class SimilarPersonMatcher . ' OR UNACCENT(LOWER(p.lastName)) LIKE UNACCENT(LOWER(:lastName)) ' . ' OR UNACCENT(LOWER(p.firstName)) LIKE UNACCENT(LOWER(:lastName)) ' . ' OR UNACCENT(LOWER(p.lastName)) LIKE UNACCENT(LOWER(:firstName)) ' + . ' OR SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName))) >= 0.15 ' . ' ) ' - . ' AND p.center IN (:centers)'; + . ' AND p.center IN (:centers)' + . ' ORDER BY SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName))) DESC ' + ; $query = $this->em ->createQuery($dql) ->setParameter('firstName', $person->getFirstName()) ->setParameter('lastName', $person->getLastName()) + ->setParameter('fullName', $person->getFirstName() . ' ' . $person->getLastName()) ->setParameter('centers', $centers) ; From 9d8d330eb397d97a5b776eb56b3b7094d03fcfa7 Mon Sep 17 00:00:00 2001 From: Mat Date: Tue, 6 Nov 2018 15:10:57 +0100 Subject: [PATCH 3/9] adapt personSearch query with fullnameCanonical --- Search/PersonSearch.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Search/PersonSearch.php b/Search/PersonSearch.php index 0c9d0eab4..e94c28079 100644 --- a/Search/PersonSearch.php +++ b/Search/PersonSearch.php @@ -283,7 +283,7 @@ class PersonSearch extends AbstractSearch implements ContainerAwareInterface, foreach($grams as $key => $gram) { $qb->andWhere($qb->expr() - ->like('UNACCENT(LOWER(CONCAT(p.firstName, \' \', p.lastName)))', ':default_'.$key)) + ->like('p.fullnameCanonical', 'UNACCENT(LOWER(:default_'.$key.'))')) ->setParameter('default_'.$key, '%'.$gram.'%'); } } From 7be2d408f93ba35cc21b232539bdaa439929b927 Mon Sep 17 00:00:00 2001 From: Mat Date: Tue, 6 Nov 2018 15:12:13 +0100 Subject: [PATCH 4/9] add similarity results for persons search --- Resources/config/services/search.yml | 11 ++ Resources/translations/messages.en.yml | 3 +- Resources/translations/messages.fr.yml | 1 + Resources/views/Person/list.html.twig | 2 +- Search/SimilarityPersonSearch.php | 237 +++++++++++++++++++++++++ 5 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 Search/SimilarityPersonSearch.php diff --git a/Resources/config/services/search.yml b/Resources/config/services/search.yml index 2abffbea5..b0da6c7f2 100644 --- a/Resources/config/services/search.yml +++ b/Resources/config/services/search.yml @@ -11,6 +11,17 @@ services: tags: - { name: chill.search, alias: 'person_regular' } + Chill\PersonBundle\Search\SimilarityPersonSearch: + arguments: + - "@doctrine.orm.entity_manager" + - "@security.token_storage" + - "@chill.main.security.authorization.helper" + - "@chill_main.paginator_factory" + calls: + - ['setContainer', ["@service_container"]] + tags: + - { name: chill.search, alias: 'person_similarity' } + Chill\PersonBundle\Search\SimilarPersonMatcher: arguments: $em: '@Doctrine\ORM\EntityManagerInterface' diff --git a/Resources/translations/messages.en.yml b/Resources/translations/messages.en.yml index a99af9e74..4c0c13d83 100644 --- a/Resources/translations/messages.en.yml +++ b/Resources/translations/messages.en.yml @@ -68,4 +68,5 @@ Reset: 'Remise à zéro' 'Person details': 'Détails de la personne' Create an accompanying period: Create an accompanying period -'Create': Create \ No newline at end of file +'Create': Create +Similar persons: Similar persons diff --git a/Resources/translations/messages.fr.yml b/Resources/translations/messages.fr.yml index 65b7cd632..ba6d770cf 100644 --- a/Resources/translations/messages.fr.yml +++ b/Resources/translations/messages.fr.yml @@ -201,3 +201,4 @@ Aggregate by age: Aggréger par âge Calculate age in relation to this date: Calculer l'âge par rapport à cette date Group people by country of birth: Aggréger les personnes par pays de naissance +Similar persons: Personnes similaires diff --git a/Resources/views/Person/list.html.twig b/Resources/views/Person/list.html.twig index 31b80f477..11252404c 100644 --- a/Resources/views/Person/list.html.twig +++ b/Resources/views/Person/list.html.twig @@ -14,7 +14,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . #} -

{{ 'Person search results'|trans }}

+

{{ title|default('Person search results')|trans }}

{{ '%total% persons matching the search pattern:'|transchoice( total, { '%total%' : total}) }} diff --git a/Search/SimilarityPersonSearch.php b/Search/SimilarityPersonSearch.php new file mode 100644 index 000000000..69d9c67ee --- /dev/null +++ b/Search/SimilarityPersonSearch.php @@ -0,0 +1,237 @@ +em = $em; + $this->user = $tokenStorage->getToken()->getUser(); + $this->helper = $helper; + $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'); + } + } + + /* + * (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') { + 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" + )); + } 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( + '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 ($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(p.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->from('ChillPersonBundle:Person', 'p'); + + if ($terms['_default'] !== '') { + $grams = explode(' ', $terms['_default']); + + foreach($grams as $key => $gram) { + $qb->andWhere( + 'SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:default_'.$key.'))) >= 0.15') + ->setParameter('default_'.$key, '%'.$gram.'%'); + } + } + + //restraint center for security + $reachableCenters = $this->helper->getReachableCenters($this->user, + new Role('CHILL_PERSON_SEE')); + $qb->andWhere($qb->expr() + ->in('p.center', ':centers')) + ->setParameter('centers', $reachableCenters) + ; + + $this->_cacheQuery[$cacheKey] = $qb; + + return clone $qb; + } + +} \ No newline at end of file From 99ca79d86ec65e8a3d5f0329d653beb6c318ae33 Mon Sep 17 00:00:00 2001 From: Mat Date: Tue, 6 Nov 2018 15:19:02 +0100 Subject: [PATCH 5/9] do not display similar search results if null --- Search/SimilarityPersonSearch.php | 39 +++++++++++++++++++------------ 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/Search/SimilarityPersonSearch.php b/Search/SimilarityPersonSearch.php index 69d9c67ee..9aac92a88 100644 --- a/Search/SimilarityPersonSearch.php +++ b/Search/SimilarityPersonSearch.php @@ -113,21 +113,30 @@ class SimilarityPersonSearch extends AbstractSearch $total = $this->count($terms); $paginator = $this->paginatorFactory->create($total); - if ($format === 'html') { - 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" - )); - } elseif ($format === 'json') { + 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' => [ From ecf3f541ef96171796962a3c8dbfb87a8593e951 Mon Sep 17 00:00:00 2001 From: Mat Date: Thu, 8 Nov 2018 14:26:08 +0100 Subject: [PATCH 6/9] wip.. inject personSearch results in similaritySearch to avoid duplicates results error message : --------------- [Semantical Error] line 0, col 13 near 'p.id) FROM ChillPersonBundle:Person': Error: 'p' is used outside the scope of its declaration. --- Resources/config/services/search.yml | 1 + Search/PersonSearch.php | 2 +- Search/SimilarityPersonSearch.php | 33 +++++++++++++++++++++++----- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Resources/config/services/search.yml b/Resources/config/services/search.yml index b0da6c7f2..8b4db1373 100644 --- a/Resources/config/services/search.yml +++ b/Resources/config/services/search.yml @@ -17,6 +17,7 @@ services: - "@security.token_storage" - "@chill.main.security.authorization.helper" - "@chill_main.paginator_factory" + - '@chill.person.search_person' calls: - ['setContainer', ["@service_container"]] tags: diff --git a/Search/PersonSearch.php b/Search/PersonSearch.php index e94c28079..6d562ec95 100644 --- a/Search/PersonSearch.php +++ b/Search/PersonSearch.php @@ -201,7 +201,7 @@ class PersonSearch extends AbstractSearch implements ContainerAwareInterface, * @param array $terms * @return \Doctrine\ORM\QueryBuilder */ - protected function createQuery(array $terms) + public function createQuery(array $terms) { //get from cache $cacheKey = md5(serialize($terms)); diff --git a/Search/SimilarityPersonSearch.php b/Search/SimilarityPersonSearch.php index 9aac92a88..13c044592 100644 --- a/Search/SimilarityPersonSearch.php +++ b/Search/SimilarityPersonSearch.php @@ -50,6 +50,12 @@ class SimilarityPersonSearch extends AbstractSearch const NAME = "person_similarity"; + /** + * + * @var PersonSearch + */ + private $personSearch; + /** * SimilarityPersonSearch constructor. @@ -58,17 +64,20 @@ class SimilarityPersonSearch extends AbstractSearch * @param TokenStorage $tokenStorage * @param AuthorizationHelper $helper * @param PaginatorFactory $paginatorFactory + * @param PersonSearch $personSearch */ public function __construct( EntityManagerInterface $em, TokenStorage $tokenStorage, AuthorizationHelper $helper, - PaginatorFactory $paginatorFactory) + 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) { @@ -218,28 +227,42 @@ class SimilarityPersonSearch extends AbstractSearch $qb = $this->em->createQueryBuilder(); - $qb->from('ChillPersonBundle:Person', 'p'); + $qb->from('ChillPersonBundle:Person', 'simi'); if ($terms['_default'] !== '') { $grams = explode(' ', $terms['_default']); foreach($grams as $key => $gram) { - $qb->andWhere( - 'SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:default_'.$key.'))) >= 0.15') + $qb->andWhere('SIMILARITY(simi.fullnameCanonical, UNACCENT(LOWER(:default_'.$key.')) ) >= 0.15') ->setParameter('default_'.$key, '%'.$gram.'%'); } + + /// + //dump($this->personSearch->createQuery($terms)->addSelect('p.id')->getDQL()); + + $qb->andWhere($qb->expr() + ->notIn( + 'simi.id', + $this->personSearch->createQuery($terms) + ->select('p.id') + ->getDQL() + ) + ); + + /// } //restraint center for security $reachableCenters = $this->helper->getReachableCenters($this->user, new Role('CHILL_PERSON_SEE')); $qb->andWhere($qb->expr() - ->in('p.center', ':centers')) + ->in('simi.center', ':centers')) ->setParameter('centers', $reachableCenters) ; $this->_cacheQuery[$cacheKey] = $qb; + dump($qb->getDQL()); return clone $qb; } From 387d3b53c15fea9ee305c41743e1efdb43ca7af7 Mon Sep 17 00:00:00 2001 From: Mat Date: Thu, 8 Nov 2018 16:17:11 +0100 Subject: [PATCH 7/9] wip.. dump() give a correct result, but have error !! --- Search/SimilarityPersonSearch.php | 34 ++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/Search/SimilarityPersonSearch.php b/Search/SimilarityPersonSearch.php index 13c044592..d4c66225f 100644 --- a/Search/SimilarityPersonSearch.php +++ b/Search/SimilarityPersonSearch.php @@ -227,28 +227,35 @@ class SimilarityPersonSearch extends AbstractSearch $qb = $this->em->createQueryBuilder(); - $qb->from('ChillPersonBundle:Person', 'simi'); + $qb + ->select('sp') + ->from('ChillPersonBundle:Person', 'sp'); if ($terms['_default'] !== '') { $grams = explode(' ', $terms['_default']); foreach($grams as $key => $gram) { - $qb->andWhere('SIMILARITY(simi.fullnameCanonical, UNACCENT(LOWER(:default_'.$key.')) ) >= 0.15') + $qb->andWhere('SIMILARITY(sp.fullnameCanonical, UNACCENT(LOWER(:default_'.$key.')) ) >= 0.15') ->setParameter('default_'.$key, '%'.$gram.'%'); } /// - //dump($this->personSearch->createQuery($terms)->addSelect('p.id')->getDQL()); - + /// Testé avec http://localhost:8001/fr/search?q=matthieu + /// personSearch: 1 result + /// similaritySearch: 6 results - 1 = 5 results + /// + $subquery = $this->personSearch->createQuery($terms) + ->addSelect('p.id'); + $qb->andWhere($qb->expr() ->notIn( - 'simi.id', - $this->personSearch->createQuery($terms) - ->select('p.id') - ->getDQL() + 'sp.id', + $subquery->getDQL() ) ); - + + dump($subquery->getDQL()); + dump($subquery->getQuery()->getResult()); /// } @@ -256,14 +263,21 @@ class SimilarityPersonSearch extends AbstractSearch $reachableCenters = $this->helper->getReachableCenters($this->user, new Role('CHILL_PERSON_SEE')); $qb->andWhere($qb->expr() - ->in('simi.center', ':centers')) + ->in('sp.center', ':centers')) ->setParameter('centers', $reachableCenters) ; $this->_cacheQuery[$cacheKey] = $qb; dump($qb->getDQL()); + dump($qb->getQuery()->getResult()); //////////// <---- le résultat attendu !! + return clone $qb; + + /// + /// [Semantical Error] line 0, col 13 near 'p.id) FROM ChillPersonBundle:Person': + /// Error: 'p' is used outside the scope of its declaration. + /// } } \ No newline at end of file From d118f60dc32d5a5e96ceb6ee3b2840279221981b Mon Sep 17 00:00:00 2001 From: Mat Date: Tue, 13 Nov 2018 11:04:03 +0100 Subject: [PATCH 8/9] similaritySearch works, without duplicate personSearch results --- Search/SimilarityPersonSearch.php | 43 ++++++++++--------------------- 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/Search/SimilarityPersonSearch.php b/Search/SimilarityPersonSearch.php index d4c66225f..bedc715f0 100644 --- a/Search/SimilarityPersonSearch.php +++ b/Search/SimilarityPersonSearch.php @@ -168,17 +168,18 @@ class SimilarityPersonSearch extends AbstractSearch { $qb = $this->createQuery($terms, 'search'); + if ($options['simplify'] ?? false) { $qb->select( - 'p.id', + 'sp.id', $qb->expr()->concat( - 'p.firstName', + 'sp.firstName', $qb->expr()->literal(' '), - 'p.lastName' + 'sp.lastName' ).'AS text' ); } else { - $qb->select('p'); + $qb->select('sp'); } $qb @@ -188,8 +189,8 @@ class SimilarityPersonSearch extends AbstractSearch //order by firstname, lastname $qb - ->orderBy('p.firstName') - ->addOrderBy('p.lastName'); + ->orderBy('sp.firstName') + ->addOrderBy('sp.lastName'); if ($options['simplify'] ?? false) { return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY); @@ -204,7 +205,7 @@ class SimilarityPersonSearch extends AbstractSearch $qb = $this->createQuery($terms); - $qb->select('COUNT(p.id)'); + $qb->select('COUNT(sp.id)'); return $qb->getQuery()->getSingleScalarResult(); } @@ -227,8 +228,7 @@ class SimilarityPersonSearch extends AbstractSearch $qb = $this->em->createQueryBuilder(); - $qb - ->select('sp') + $qb ->select('sp') ->from('ChillPersonBundle:Person', 'sp'); if ($terms['_default'] !== '') { @@ -239,24 +239,15 @@ class SimilarityPersonSearch extends AbstractSearch ->setParameter('default_'.$key, '%'.$gram.'%'); } - /// - /// Testé avec http://localhost:8001/fr/search?q=matthieu - /// personSearch: 1 result - /// similaritySearch: 6 results - 1 = 5 results - /// - $subquery = $this->personSearch->createQuery($terms) - ->addSelect('p.id'); - $qb->andWhere($qb->expr() ->notIn( 'sp.id', - $subquery->getDQL() + $this->personSearch + ->createQuery($terms) + ->addSelect('p.id') + ->getDQL() ) ); - - dump($subquery->getDQL()); - dump($subquery->getQuery()->getResult()); - /// } //restraint center for security @@ -269,15 +260,7 @@ class SimilarityPersonSearch extends AbstractSearch $this->_cacheQuery[$cacheKey] = $qb; - dump($qb->getDQL()); - dump($qb->getQuery()->getResult()); //////////// <---- le résultat attendu !! - return clone $qb; - - /// - /// [Semantical Error] line 0, col 13 near 'p.id) FROM ChillPersonBundle:Person': - /// Error: 'p' is used outside the scope of its declaration. - /// } } \ No newline at end of file From 6cd966d876abf4a4f8ebee60175bfaa4ff5a84e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 13 Nov 2018 12:55:24 +0100 Subject: [PATCH 9/9] adding changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1aecfe92..d29efd05d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,3 +9,13 @@ Version 1.5.1 - remove inexistant `person.css` file - fix bug in accompanying person validation +Branch `Similarity` +================== + +- Add an column with fullname canonical (lowercase and unaccent) to persons entity ; +- Add a trigram index on fullname canonical ; +- Add a "similar person matcher", which allow to detect person with similar names when adding a person ; +- Add a research of persons by fuzzy name, returning result with a similarity of 0.15 ; + +Thanks to @matla :-) +