Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f57c78d01 | |||
| e88da82267 | |||
| 319334834b | |||
| 9bad1cb2b9 | |||
| df927a8197 | |||
| 5c98989631 | |||
| 373087a5d5 | |||
| 205b29e07c | |||
| e1d5f094de | |||
| 8145b87242 | |||
| d027933ad9 | |||
| cbad86a01c | |||
| 15bf7cb324 | |||
| 1b9787aa49 | |||
| 9d4297659d | |||
| 92842c552b | |||
| 80e584e29f | |||
| c03d2ae9ad | |||
| c27ae18aa0 | |||
| 89c88520d9 | |||
| c19a755ed1 | |||
| 12925cc7d7 | |||
| 393a704f1c | |||
| 398e2903ea | |||
| 95db65f56d | |||
| 1ec9b05413 | |||
| 6a4ce3bef3 | |||
| bf89b962ec | |||
| 7de30e78ac | |||
| 6d65df0b9c | |||
| 5d56d98325 | |||
| 2f6597a957 | |||
| 93bb85f69f | |||
| 218b174d4f | |||
| 2258fa0c96 | |||
| 34b0e7f146 | |||
| ab25540462 | |||
| da950685c0 | |||
| 954e6aa712 | |||
| 3f410b8f55 | |||
| 78f356f030 | |||
| c35da20deb |
6
Makefile
6
Makefile
@@ -1,8 +1,10 @@
|
|||||||
PHP = docker compose run php
|
PHP = docker compose run php
|
||||||
|
|
||||||
|
|
||||||
composer_install:
|
composer_install:
|
||||||
@$(PHP) composer install
|
@$(PHP) composer install
|
||||||
|
|
||||||
|
composer_update:
|
||||||
|
@$(PHP) composer update
|
||||||
|
|
||||||
static_analysis:
|
static_analysis:
|
||||||
@$(PHP) vendor/bin/psalm
|
@$(PHP) vendor/bin/psalm
|
||||||
|
|||||||
5
config/packages/doctrine.yaml
Normal file
5
config/packages/doctrine.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
doctrine:
|
||||||
|
orm:
|
||||||
|
dql:
|
||||||
|
string_functions:
|
||||||
|
match: DoctrineExtensions\Query\Mysql\MatchAgainst
|
||||||
@@ -1,4 +1,25 @@
|
|||||||
services:
|
services:
|
||||||
Pcm\SearchBundle\EventSubscriber\SearchableSubscriber:
|
_defaults:
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
|
||||||
|
pcm_search.command.index:
|
||||||
|
class: Pcm\SearchBundle\Command\SearchIndexCommand
|
||||||
|
tags:
|
||||||
|
- { name: 'console.command', command: 'pcm:search:reindex' }
|
||||||
|
|
||||||
|
pcm_search.searchable_subscriber:
|
||||||
|
class: Pcm\SearchBundle\EventSubscriber\SearchableSubscriber
|
||||||
|
public: true
|
||||||
tags:
|
tags:
|
||||||
- { name: doctrine.event_subscriber }
|
- { name: doctrine.event_subscriber }
|
||||||
|
|
||||||
|
pcm_search.search_service:
|
||||||
|
alias: Pcm\SearchBundle\Service\SearchService
|
||||||
|
public: true
|
||||||
|
|
||||||
|
Pcm\SearchBundle\Service\SearchService: ~
|
||||||
|
|
||||||
|
Pcm\SearchBundle\Repository\SearchIndexRepository:
|
||||||
|
autowire: true
|
||||||
|
tags: ['doctrine.repository_service']
|
||||||
|
|||||||
23
src/DependencyInjection/PcmSearchExtension.php
Normal file
23
src/DependencyInjection/PcmSearchExtension.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pcm\SearchBundle\DependencyInjection;
|
||||||
|
|
||||||
|
use Symfony\Component\Config\FileLocator;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||||
|
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
||||||
|
|
||||||
|
class PcmSearchExtension extends Extension
|
||||||
|
{
|
||||||
|
public function load(array $configs, ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
$loader = new YamlFileLoader(
|
||||||
|
$container,
|
||||||
|
new FileLocator(__DIR__.'/../../config')
|
||||||
|
);
|
||||||
|
$loader->load('services.yaml');
|
||||||
|
$loader->load('packages/doctrine.yaml');
|
||||||
|
}
|
||||||
|
}
|
||||||
124
src/Repository/SearchIndexRepository.php
Normal file
124
src/Repository/SearchIndexRepository.php
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pcm\SearchBundle\Repository;
|
||||||
|
|
||||||
|
use Pcm\SearchBundle\Entity\SearchIndex;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
|
class SearchIndexRepository extends ServiceEntityRepository
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param ManagerRegistry $registry
|
||||||
|
*/
|
||||||
|
public function __construct(ManagerRegistry $registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry, SearchIndex::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a SearchIndex entity
|
||||||
|
*
|
||||||
|
* @param SearchIndex $entity
|
||||||
|
* @param boolean $flush
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function add(SearchIndex $entity, bool $flush = false): void
|
||||||
|
{
|
||||||
|
$this->getEntityManager()->persist($entity);
|
||||||
|
|
||||||
|
if ($flush) {
|
||||||
|
$this->getEntityManager()->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a SearchIndex entity
|
||||||
|
*
|
||||||
|
* @param SearchIndex $entity
|
||||||
|
* @param boolean $flush
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function remove(SearchIndex $entity, bool $flush = false): void
|
||||||
|
{
|
||||||
|
$this->getEntityManager()->remove($entity);
|
||||||
|
|
||||||
|
if ($flush) {
|
||||||
|
$this->getEntityManager()->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Query object ready to be paginated or used to present results.
|
||||||
|
*
|
||||||
|
* @param string $query
|
||||||
|
* @param integer $minScore
|
||||||
|
* @return Query
|
||||||
|
*/
|
||||||
|
public function findAllPagination(string $query, int $minScore = 0): Query
|
||||||
|
{
|
||||||
|
$qb = $this->createQueryBuilder('r')
|
||||||
|
->addSelect('MATCH(r.data) AGAINST(:searchText boolean) AS score')
|
||||||
|
->where(
|
||||||
|
sprintf(
|
||||||
|
'MATCH(r.data) AGAINST(:searchText boolean) > %f',
|
||||||
|
$minScore
|
||||||
|
)
|
||||||
|
)->orderBy('score', 'DESC')
|
||||||
|
->setParameter(
|
||||||
|
'searchText',
|
||||||
|
$this->convertSearchTerm($query)
|
||||||
|
);
|
||||||
|
return $qb->getQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a string $query and explodes into individual words. Each word
|
||||||
|
* is then prefixed with + and ends with *, making the full text search
|
||||||
|
* operate as wildcard on all words
|
||||||
|
*
|
||||||
|
* @param string $query
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function convertSearchTerm(string $query): string
|
||||||
|
{
|
||||||
|
$extractedWords = [];
|
||||||
|
$sanitisedString = preg_replace('/[^\w^\d]/', ' ', $query);
|
||||||
|
$words = mb_split('\s', preg_replace(['/([^\w+])/','/(\s+)/'], ' ', $sanitisedString));
|
||||||
|
|
||||||
|
foreach ($words as $word) {
|
||||||
|
if (strlen($word)< 1) {
|
||||||
|
//
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$word = strtoupper($word);
|
||||||
|
$extractedWords[$word] = $word;
|
||||||
|
}
|
||||||
|
|
||||||
|
array_walk(
|
||||||
|
$extractedWords,
|
||||||
|
function(&$word) {
|
||||||
|
// require every word but allow matching just the start
|
||||||
|
$word = '+' . $word . '*';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return implode(' ', $extractedWords);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the index table of all results
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function clearIndex(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->createQueryBuilder('s')
|
||||||
|
->delete()
|
||||||
|
->getQuery()
|
||||||
|
->execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user