*/ class PostRepositoryQuery extends RepositoryQuery { public function __construct(PostRepository $repository, PaginatorInterface $paginator) { parent::__construct($repository, 'p', $paginator); } public function inCategory(Category $category, bool $strict = true): self { $c = 'c'.mt_rand(); if ($strict) { $this ->innerJoin('p.categories', $c) ->andWhere($c.'.id = :category') ->setParameter(':category', $category->getId()) ; } else { $ids = [$category->getId()]; foreach ($category->getCategories() as $childCategory) { $ids[] = $childCategory->getId(); } $this ->innerJoin('p.categories', $c) ->andWhere($c.'.id IN (:categories)') ->setParameter(':categories', $ids) ; } return $this; } public function published(): self { return $this ->andWhere('.status = 1') ->andWhere('.publishedAt <= :now') ->setParameter(':now', (new \DateTime('now'))->format('Y-m-d H:i:s')) ; } public function search(?string $keywords, ?string $tag) { $keywords = explode(' ', $keywords); $filterWords = fn ($keyword) => '' !== trim($keyword) && preg_match('/[a-zA-Z]+/', $keyword); $keywords = array_filter($keywords, $filterWords); if ($keywords) { $conn = $this->repository->getEm()->getConnection(); $query = $conn->prepare( 'SELECT post.id, post.title, post.content, post.published_at FROM post WHERE post.status = 1 AND post.published_at < :date ' ); $statement = $query->execute([ ':date' => (new \DateTime())->format('Y-m-d H:i:s'), ]); $results = $statement->fetchAll(); $ids = []; $matches = []; foreach ($results as $k => $v) { $initWords = explode(' ', $v['title']); $words = []; foreach ($initWords as $initWord) { $words = array_merge($words, preg_split('/[:_\'-]+/', $initWord)); } $words = array_filter($words, $filterWords); foreach ($keywords as $keyword) { if (str_contains(mb_strtolower($v['content']), mb_strtolower($keyword))) { $similarity = 99; if (isset($matches[$v['id']])) { $matches[$v['id']]['similarity'] += $similarity; ++$matches[$v['id']]['count']; } else { $matches[$v['id']] = [ 'id' => $v['id'], 'title' => $v['title'], 'published_at' => $v['published_at'], 'similarity' => $similarity, 'count' => 1, ]; } } foreach ($words as $word) { if (str_contains(mb_strtolower($word), mb_strtolower($keyword))) { $similarity = 150; if (isset($matches[$v['id']])) { $matches[$v['id']]['similarity'] += $similarity; ++$matches[$v['id']]['count']; } else { $matches[$v['id']] = [ 'id' => $v['id'], 'title' => $v['title'], 'published_at' => $v['published_at'], 'similarity' => $similarity, 'count' => 1, ]; } } else { $lev = levenshtein($word, $keyword); $similarity = 100 - ($lev * 100 / mb_strlen($word)); if ($similarity > 70) { if (isset($matches[$v['id']])) { $matches[$v['id']]['similarity'] += $similarity; } else { $matches[$v['id']] = [ 'id' => $v['id'], 'title' => $v['title'], 'published_at' => $v['published_at'], 'similarity' => $similarity, 'count' => 1, ]; } } } } } } $matches = array_filter($matches, function($match) use ($keywords) { return (100 * $match['count'] / count($keywords)) > 80; }); usort($matches, function ($a, $b) { if ($a['similarity'] > $b['similarity']) { return -1; } if ($b['similarity'] > $a['similarity']) { return 1; } return ($a['published_at'] != $b['published_at']) * -1; }); $ids = array_column($matches, 'id'); if (!$ids) { $ids = [-1]; } $this ->orderBy('FIELD(p.id, :ids)') ->andWhere('.id IN(:ids)') ->setParameter(':ids', $ids) ; } if ($tag) { $this ->andWhere('.tags LIKE :tag') ->setParameter(':tag', '%"'.$tag.'"%') ; } return $this; } protected function filterHandler(string $name, $value) { if ('category' === $name) { $this->inCategory($value); } } }