add search engine
This commit is contained in:
parent
c3c49d89cd
commit
120dcdaa44
|
@ -7,6 +7,7 @@
|
||||||
"php": ">=7.2.5",
|
"php": ">=7.2.5",
|
||||||
"ext-ctype": "*",
|
"ext-ctype": "*",
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
|
"beberlei/doctrineextensions": "^1.3",
|
||||||
"bjeavons/zxcvbn-php": "^1.2",
|
"bjeavons/zxcvbn-php": "^1.2",
|
||||||
"cocur/slugify": "^4.0",
|
"cocur/slugify": "^4.0",
|
||||||
"composer/package-versions-deprecated": "1.11.99.1",
|
"composer/package-versions-deprecated": "1.11.99.1",
|
||||||
|
|
|
@ -9,6 +9,9 @@ doctrine:
|
||||||
auto_generate_proxy_classes: true
|
auto_generate_proxy_classes: true
|
||||||
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
|
||||||
auto_mapping: true
|
auto_mapping: true
|
||||||
|
dql:
|
||||||
|
string_functions:
|
||||||
|
FIELD: DoctrineExtensions\Query\Mysql\Field
|
||||||
mappings:
|
mappings:
|
||||||
App\Core\Entity:
|
App\Core\Entity:
|
||||||
is_bundle: false
|
is_bundle: false
|
||||||
|
|
|
@ -28,28 +28,8 @@ abstract class RepositoryQuery
|
||||||
|
|
||||||
public function __call(string $name, $params): self
|
public function __call(string $name, $params): self
|
||||||
{
|
{
|
||||||
$fn = function (&$data) {
|
|
||||||
if (is_string($data)) {
|
|
||||||
$words = explode(' ', $data);
|
|
||||||
|
|
||||||
foreach ($words as $k => $v) {
|
|
||||||
if (isset($v[0]) && '.' === $v[0]) {
|
|
||||||
$words[$k] = $this->id.$v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = implode(' ', $words);
|
|
||||||
} elseif (is_array($data)) {
|
|
||||||
foreach ($data as $k => $v) {
|
|
||||||
$fn($data[$k]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach ($params as $key => $value) {
|
foreach ($params as $key => $value) {
|
||||||
$fn($params[$key]);
|
$this->populateDqlId($params[$key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
call_user_func_array([$this->query, $name], $params);
|
call_user_func_array([$this->query, $name], $params);
|
||||||
|
@ -93,4 +73,25 @@ abstract class RepositoryQuery
|
||||||
{
|
{
|
||||||
return $this->repository;
|
return $this->repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function populateDqlId(&$data)
|
||||||
|
{
|
||||||
|
if (is_string($data)) {
|
||||||
|
$words = explode(' ', $data);
|
||||||
|
|
||||||
|
foreach ($words as $k => $v) {
|
||||||
|
if (isset($v[0]) && '.' === $v[0]) {
|
||||||
|
$words[$k] = $this->id.$v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = implode(' ', $words);
|
||||||
|
} elseif (is_array($data)) {
|
||||||
|
foreach ($data as $k => $v) {
|
||||||
|
$this->populateDqlId($data[$k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ use App\Repository\Blog\PostRepositoryQuery;
|
||||||
use App\UrlGenerator\PostGenerator;
|
use App\UrlGenerator\PostGenerator;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use App\Core\Site\SiteStore;
|
use App\Core\Site\SiteStore;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
|
||||||
class PostController extends PageController
|
class PostController extends PageController
|
||||||
{
|
{
|
||||||
|
@ -58,6 +59,23 @@ class PostController extends PageController
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function search(Request $request, int $page = 1): Response
|
||||||
|
{
|
||||||
|
$query = $request->query->get('query');
|
||||||
|
|
||||||
|
if ($query) {
|
||||||
|
$entities = $this->createQuery()
|
||||||
|
->search($query)
|
||||||
|
->paginate($page, 5)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->defaultRender('blog/post/search.html.twig', [
|
||||||
|
'pager' => $entities ?? null,
|
||||||
|
'query' => $query,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @UrlGenerator(service=PostGenerator::class, method="category")
|
* @UrlGenerator(service=PostGenerator::class, method="category")
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -13,6 +13,10 @@ use Symfony\Component\HttpFoundation\File\File;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Entity(repositoryClass=PostRepository::class)
|
* @ORM\Entity(repositoryClass=PostRepository::class)
|
||||||
|
* @ORM\Table(indexes={
|
||||||
|
* @ORM\Index(name="post_title", columns={"title"}, flags={"fulltext"}),
|
||||||
|
* @ORM\Index(name="post_content", columns={"content"}, flags={"fulltext"})
|
||||||
|
* })
|
||||||
* @ORM\HasLifecycleCallbacks
|
* @ORM\HasLifecycleCallbacks
|
||||||
*/
|
*/
|
||||||
class Post implements EntityInterface
|
class Post implements EntityInterface
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Repository\Blog;
|
||||||
use App\Entity\Blog\Post;
|
use App\Entity\Blog\Post;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
use App\Core\Manager\EntityManager;
|
||||||
|
|
||||||
class PostRepository extends ServiceEntityRepository
|
class PostRepository extends ServiceEntityRepository
|
||||||
{
|
{
|
||||||
|
@ -12,4 +13,9 @@ class PostRepository extends ServiceEntityRepository
|
||||||
{
|
{
|
||||||
parent::__construct($registry, Post::class);
|
parent::__construct($registry, Post::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getEm()
|
||||||
|
{
|
||||||
|
return $this->getEntityManager();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ namespace App\Repository\Blog;
|
||||||
|
|
||||||
use App\Core\Repository\RepositoryQuery;
|
use App\Core\Repository\RepositoryQuery;
|
||||||
use App\Entity\Blog\Category;
|
use App\Entity\Blog\Category;
|
||||||
use App\Entity\User;
|
|
||||||
use Knp\Component\Pager\PaginatorInterface;
|
use Knp\Component\Pager\PaginatorInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,13 +36,14 @@ class PostRepositoryQuery extends RepositoryQuery
|
||||||
return $this
|
return $this
|
||||||
->andWhere('.status = 1')
|
->andWhere('.status = 1')
|
||||||
->andWhere('.publishedAt <= :now')
|
->andWhere('.publishedAt <= :now')
|
||||||
->setParameter(':now', (new \DateTime('now'))->format('Y-m-d H:i:s'));
|
->setParameter(':now', (new \DateTime('now'))->format('Y-m-d H:i:s'))
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function useFilters(array $filters)
|
public function useFilters(array $filters)
|
||||||
{
|
{
|
||||||
foreach ($filters as $name => $value) {
|
foreach ($filters as $name => $value) {
|
||||||
if ($value === null) {
|
if (null === $value) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ class PostRepositoryQuery extends RepositoryQuery
|
||||||
$this->andWhere('.'.$name.' LIKE :'.$name);
|
$this->andWhere('.'.$name.' LIKE :'.$name);
|
||||||
$this->setParameter(':'.$name, '%'.$value.'%');
|
$this->setParameter(':'.$name, '%'.$value.'%');
|
||||||
} else {
|
} else {
|
||||||
if ($name === 'category') {
|
if ('category' === $name) {
|
||||||
$this->inCategory($value);
|
$this->inCategory($value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,4 +62,60 @@ class PostRepositoryQuery extends RepositoryQuery
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function search(string $keywords)
|
||||||
|
{
|
||||||
|
$conn = $this->repository->getEm()->getConnection();
|
||||||
|
|
||||||
|
$statement = $conn->prepare(
|
||||||
|
'SELECT
|
||||||
|
post.id,
|
||||||
|
post.title,
|
||||||
|
MATCH(post.title) AGAINST(:search) AS MATCH_TITLE,
|
||||||
|
MATCH(post.content) AGAINST(:search) AS MATCH_CONTENT
|
||||||
|
FROM post
|
||||||
|
WHERE
|
||||||
|
post.status = 1 AND
|
||||||
|
post.published_at < :date
|
||||||
|
ORDER BY
|
||||||
|
MATCH_TITLE DESC,
|
||||||
|
MATCH_CONTENT DESC
|
||||||
|
');
|
||||||
|
|
||||||
|
$statement->execute([
|
||||||
|
':search' => $keywords,
|
||||||
|
':date' => (new \DateTime())->format('Y-m-d H:i:s'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$results = $statement->fetchAll();
|
||||||
|
$ids = [];
|
||||||
|
|
||||||
|
foreach ($results as $k => $v) {
|
||||||
|
$rate = ($v['MATCH_TITLE'] * 2) + $v['MATCH_CONTENT'];
|
||||||
|
|
||||||
|
if ($rate >= 7) {
|
||||||
|
$ids[] = $v['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 == count($ids)) {
|
||||||
|
foreach ($results as $k => $v) {
|
||||||
|
$rate = ($v['MATCH_TITLE'] * 2) + $v['MATCH_CONTENT'];
|
||||||
|
|
||||||
|
if ($rate >= 6) {
|
||||||
|
$ids[] = $v['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$ids) {
|
||||||
|
$ids = [-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this
|
||||||
|
->orderBy('FIELD(p.id, :ids)')
|
||||||
|
->andWhere('.id IN(:ids)')
|
||||||
|
->setParameter(':ids', $ids)
|
||||||
|
;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
"beberlei/assert": {
|
"beberlei/assert": {
|
||||||
"version": "v3.3.0"
|
"version": "v3.3.0"
|
||||||
},
|
},
|
||||||
|
"beberlei/doctrineextensions": {
|
||||||
|
"version": "v1.3.0"
|
||||||
|
},
|
||||||
"behat/transliterator": {
|
"behat/transliterator": {
|
||||||
"version": "v1.3.0"
|
"version": "v1.3.0"
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue