diff --git a/module/Common/src/Paginator/Adapter/PaginableRepositoryAdapter.php b/module/Common/src/Paginator/Adapter/PaginableRepositoryAdapter.php index ade0c0c9..017f5e89 100644 --- a/module/Common/src/Paginator/Adapter/PaginableRepositoryAdapter.php +++ b/module/Common/src/Paginator/Adapter/PaginableRepositoryAdapter.php @@ -13,18 +13,18 @@ class PaginableRepositoryAdapter implements AdapterInterface */ private $paginableRepository; /** - * @var null + * @var null|string */ private $searchTerm; /** - * @var null + * @var null|array|string */ private $orderBy; - public function __construct(PaginableRepositoryInterface $paginableRepository, $searchTerm = null, $orderBy = null) + public function __construct(PaginableRepositoryInterface $paginableRepository, $searchQuery = null, $orderBy = null) { $this->paginableRepository = $paginableRepository; - $this->searchTerm = $searchTerm; + $this->searchTerm = trim(strip_tags($searchQuery)); $this->orderBy = $orderBy; } diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index e2d81cba..9becb216 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -2,6 +2,7 @@ namespace Shlinkio\Shlink\Core\Repository; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\QueryBuilder; use Shlinkio\Shlink\Core\Entity\ShortUrl; class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryInterface @@ -15,7 +16,8 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI */ public function findList($limit = null, $offset = null, $searchTerm = null, $orderBy = null) { - $qb = $this->createQueryBuilder('s'); + $qb = $this->createListQueryBuilder($searchTerm); + $qb->select('s'); if (isset($limit)) { $qb->setMaxResults($limit); @@ -23,9 +25,6 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI if (isset($offset)) { $qb->setFirstResult($offset); } - if (isset($searchTerm)) { - // TODO - } if (isset($orderBy)) { if (is_string($orderBy)) { $qb->orderBy($orderBy); @@ -43,19 +42,39 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI /** * Counts the number of elements in a list using provided filtering data * - * @param null $searchTerm + * @param null|string $searchTerm * @return int */ public function countList($searchTerm = null) { - $qb = $this->getEntityManager()->createQueryBuilder(); - $qb->select('COUNT(s)') - ->from(ShortUrl::class, 's'); - - if (isset($searchTerm)) { - // TODO - } + $qb = $this->createListQueryBuilder($searchTerm); + $qb->select('COUNT(s)'); return (int) $qb->getQuery()->getSingleScalarResult(); } + + /** + * @param null|string $searchTerm + * @return QueryBuilder + */ + protected function createListQueryBuilder($searchTerm = null) + { + $qb = $this->getEntityManager()->createQueryBuilder(); + $qb->from(ShortUrl::class, 's'); + + // Apply search term to every searchable field if not empty + if (! empty($searchTerm)) { + $conditions = [ + $qb->expr()->like('s.originalUrl', ':searchPattern'), + $qb->expr()->like('s.shortCode', ':searchPattern'), + ]; + + // Unpack and apply search conditions + $qb->where($qb->expr()->orX(...$conditions)); + $searchTerm = '%' . $searchTerm . '%'; + $qb->setParameter('searchPattern', $searchTerm); + } + + return $qb; + } } diff --git a/module/Core/src/Service/ShortUrlService.php b/module/Core/src/Service/ShortUrlService.php index 60845b88..e3c2e451 100644 --- a/module/Core/src/Service/ShortUrlService.php +++ b/module/Core/src/Service/ShortUrlService.php @@ -32,13 +32,14 @@ class ShortUrlService implements ShortUrlServiceInterface /** * @param int $page - * @return Paginator|ShortUrl[] + * @param string $searchQuery + * @return ShortUrl[]|Paginator */ - public function listShortUrls($page = 1) + public function listShortUrls($page = 1, $searchQuery = null) { /** @var ShortUrlRepository $repo */ $repo = $this->em->getRepository(ShortUrl::class); - $paginator = new Paginator(new PaginableRepositoryAdapter($repo)); + $paginator = new Paginator(new PaginableRepositoryAdapter($repo, $searchQuery)); $paginator->setItemCountPerPage(PaginableRepositoryAdapter::ITEMS_PER_PAGE) ->setCurrentPageNumber($page); diff --git a/module/Core/src/Service/ShortUrlServiceInterface.php b/module/Core/src/Service/ShortUrlServiceInterface.php index 5ad304ee..7fbc1b4e 100644 --- a/module/Core/src/Service/ShortUrlServiceInterface.php +++ b/module/Core/src/Service/ShortUrlServiceInterface.php @@ -9,9 +9,10 @@ interface ShortUrlServiceInterface { /** * @param int $page + * @param string $searchQuery * @return ShortUrl[]|Paginator */ - public function listShortUrls($page = 1); + public function listShortUrls($page = 1, $searchQuery = null); /** * @param string $shortCode diff --git a/module/Rest/src/Action/ListShortcodesAction.php b/module/Rest/src/Action/ListShortcodesAction.php index 1cd376bc..d6f6782d 100644 --- a/module/Rest/src/Action/ListShortcodesAction.php +++ b/module/Rest/src/Action/ListShortcodesAction.php @@ -53,8 +53,8 @@ class ListShortcodesAction extends AbstractRestAction public function dispatch(Request $request, Response $response, callable $out = null) { try { - $query = $request->getQueryParams(); - $shortUrls = $this->shortUrlService->listShortUrls(isset($query['page']) ? $query['page'] : 1); + $params = $this->queryToListParams($request->getQueryParams()); + $shortUrls = $this->shortUrlService->listShortUrls(...$params); return new JsonResponse(['shortUrls' => $this->serializePaginator($shortUrls)]); } catch (\Exception $e) { $this->logger->error('Unexpected error while listing short URLs.' . PHP_EOL . $e); @@ -64,4 +64,16 @@ class ListShortcodesAction extends AbstractRestAction ], 500); } } + + /** + * @param array $query + * @return string + */ + public function queryToListParams(array $query) + { + return [ + isset($query['page']) ? $query['page'] : 1, + isset($query['searchTerm']) ? $query['searchTerm'] : null, + ]; + } } diff --git a/module/Rest/test/Action/ListShortcodesActionTest.php b/module/Rest/test/Action/ListShortcodesActionTest.php index b5ec0c9d..9ff2e54a 100644 --- a/module/Rest/test/Action/ListShortcodesActionTest.php +++ b/module/Rest/test/Action/ListShortcodesActionTest.php @@ -34,8 +34,8 @@ class ListShortcodesActionTest extends TestCase public function properListReturnsSuccessResponse() { $page = 3; - $this->service->listShortUrls($page)->willReturn(new Paginator(new ArrayAdapter())) - ->shouldBeCalledTimes(1); + $this->service->listShortUrls($page, null)->willReturn(new Paginator(new ArrayAdapter())) + ->shouldBeCalledTimes(1); $response = $this->action->__invoke( ServerRequestFactory::fromGlobals()->withQueryParams([ @@ -52,8 +52,8 @@ class ListShortcodesActionTest extends TestCase public function anExceptionsReturnsErrorResponse() { $page = 3; - $this->service->listShortUrls($page)->willThrow(\Exception::class) - ->shouldBeCalledTimes(1); + $this->service->listShortUrls($page, null)->willThrow(\Exception::class) + ->shouldBeCalledTimes(1); $response = $this->action->__invoke( ServerRequestFactory::fromGlobals()->withQueryParams([