diff --git a/config/services.yaml b/config/services.yaml index 885287c..8ffd173 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -38,4 +38,9 @@ services: class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer public: false tags: - - { name: serializer.normalizer, priority: 1 } \ No newline at end of file + - { name: serializer.normalizer, priority: 1 } + + # Torrent searcher + App\Search\TorrentSearcher: + arguments: + $metadataFactory: '@doctrine.orm.magneticod_entity_manager.metadata_factory' \ No newline at end of file diff --git a/src/Api/V1/Controller/TorrentController.php b/src/Api/V1/Controller/TorrentController.php index d278bc9..14414c0 100644 --- a/src/Api/V1/Controller/TorrentController.php +++ b/src/Api/V1/Controller/TorrentController.php @@ -4,7 +4,7 @@ namespace App\Api\V1\Controller; use App\Api\V1\DTO\ListPage; use App\Magnetico\Entity\Torrent; -use App\Magnetico\Repository\TorrentRepository; +use App\Search\TorrentSearcher; use Pagerfanta\Adapter\DoctrineORMAdapter; use Pagerfanta\Pagerfanta; use Symfony\Component\HttpFoundation\{JsonResponse, Request}; @@ -13,12 +13,14 @@ class TorrentController extends AbstractApiController { private const PER_PAGE = 20; - public function search(Request $request, TorrentRepository $repo): JsonResponse + public function search(Request $request, TorrentSearcher $searcher): JsonResponse { $query = $request->query->get('query', ''); $page = (int) $request->query->get('page', '1'); + $orderBy = $request->query->get('order-by'); + $order = $request->query->get('order', 'asc'); - $pagerAdapter = new DoctrineORMAdapter($repo->createFindLikeQueryBuilder($query)); + $pagerAdapter = new DoctrineORMAdapter($searcher->createSearchQueryBuilder($query, $orderBy, $order)); $pager = new Pagerfanta($pagerAdapter); $pager ->setCurrentPage($page) diff --git a/src/Controller/TorrentController.php b/src/Controller/TorrentController.php index a6974bd..687a009 100644 --- a/src/Controller/TorrentController.php +++ b/src/Controller/TorrentController.php @@ -3,7 +3,7 @@ namespace App\Controller; use App\Magnetico\Entity\Torrent; -use App\Magnetico\Repository\TorrentRepository; +use App\Search\TorrentSearcher; use Pagerfanta\Adapter\DoctrineORMAdapter; use Pagerfanta\Pagerfanta; use Symfony\Bundle\FrameworkBundle\Controller\Controller; @@ -13,12 +13,14 @@ class TorrentController extends Controller { private const PER_PAGE = 20; - public function searchTorrent(Request $request, TorrentRepository $repo): Response + public function searchTorrent(Request $request, TorrentSearcher $searcher): Response { $query = $request->query->get('query', ''); $page = (int) $request->query->get('page', '1'); + $orderBy = $request->query->get('order-by'); + $order = $request->query->get('order', 'asc'); - $pagerAdapter = new DoctrineORMAdapter($repo->createFindLikeQueryBuilder($query)); + $pagerAdapter = new DoctrineORMAdapter($searcher->createSearchQueryBuilder($query, $orderBy, $order)); $pager = new Pagerfanta($pagerAdapter); $pager ->setCurrentPage($page) diff --git a/src/Magnetico/Repository/TorrentRepository.php b/src/Magnetico/Repository/TorrentRepository.php index 3edc964..ee5dab0 100644 --- a/src/Magnetico/Repository/TorrentRepository.php +++ b/src/Magnetico/Repository/TorrentRepository.php @@ -2,15 +2,15 @@ namespace App\Magnetico\Repository; +use App\Magnetico\Entity\Torrent; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; -use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\RegistryInterface; class TorrentRepository extends ServiceEntityRepository { public function __construct(RegistryInterface $registry) { - parent::__construct($registry, \App\Magnetico\Entity\Torrent::class); + parent::__construct($registry, Torrent::class); } public function getTorrentsTotalCount(): int @@ -25,25 +25,4 @@ class TorrentRepository extends ServiceEntityRepository return 0; } } - - public function createFindLikeQueryBuilder(string $query): QueryBuilder - { - $qb = $this->createQueryBuilder('t'); - - $where = $qb->expr()->andX(); - - $query = trim($query); - $query = preg_replace('/\s+/', ' ', $query); - - $parts = explode(' ', $query); - - foreach ($parts as $idx => $part) { - $where->add($qb->expr()->like('LOWER(t.name)', ':part_'.$idx)); - $qb->setParameter('part_'.$idx, '%'.strtolower($part).'%'); - } - - $qb->where($where); - - return $qb; - } } \ No newline at end of file diff --git a/src/Search/TorrentSearcher.php b/src/Search/TorrentSearcher.php new file mode 100644 index 0000000..cf1199b --- /dev/null +++ b/src/Search/TorrentSearcher.php @@ -0,0 +1,80 @@ +torrentRepo = $torrentRepo; + $this->metadataFactory = $metadataFactory; + } + + public function createSearchQueryBuilder(string $query, string $orderBy = null, string $order = 'asc'): QueryBuilder + { + $qb = $this->createFindLikeSplitPartsQueryBuilder($query); + + if ($orderBy) { + $this->addOrder($qb, $orderBy, $order); + } + + return $qb; + } + + private function createFindLikeSplitPartsQueryBuilder(string $query): QueryBuilder + { + $qb = $this->torrentRepo->createQueryBuilder('t'); + + $where = $qb->expr()->andX(); + + foreach ($this->splitQueryToParts($query) as $idx => $part) { + $where->add($qb->expr()->like('LOWER(t.name)', ':part_'.$idx)); + $qb->setParameter('part_'.$idx, '%'.strtolower($part).'%'); + } + + $qb->where($where); + + return $qb; + } + + private function addOrder(QueryBuilder $qb, string $orderBy, string $order): void + { + if (!\in_array(strtolower($order), ['asc', 'desc'])) { + throw new \InvalidArgumentException('Invalid sort order'); + } + + if ($this->canOrderBy($orderBy)) { + $qb->orderBy('t.'.$orderBy, $order); + } + } + + private function canOrderBy(string $orderBy): bool + { + return ( + !\in_array($orderBy, self::ORDER_DISABLED_FIELDS, true) + && $this->metadataFactory->getMetadataFor(Torrent::class)->hasField($orderBy) + ); + } + + /** @return string[] */ + private function splitQueryToParts(string $query): array + { + $query = trim($query); + $query = preg_replace('/\s+/', ' ', $query); + + return explode(' ', $query); + } +} \ No newline at end of file