diff --git a/module/Common/src/Repository/PaginableRepositoryInterface.php b/module/Common/src/Repository/PaginableRepositoryInterface.php index c02e2045..31808ee1 100644 --- a/module/Common/src/Repository/PaginableRepositoryInterface.php +++ b/module/Common/src/Repository/PaginableRepositoryInterface.php @@ -15,14 +15,20 @@ interface PaginableRepositoryInterface * @param string|array|null $orderBy * @return array */ - public function findList($limit = null, $offset = null, $searchTerm = null, array $tags = [], $orderBy = null); + public function findList( + int $limit = null, + int $offset = null, + string $searchTerm = null, + array $tags = [], + $orderBy = null + ): array; /** * Counts the number of elements in a list using provided filtering data * - * @param null $searchTerm + * @param string|null $searchTerm * @param array $tags * @return int */ - public function countList($searchTerm = null, array $tags = []); + public function countList(string $searchTerm = null, array $tags = []): int; } diff --git a/module/Core/src/Entity/ShortUrl.php b/module/Core/src/Entity/ShortUrl.php index 06cb02ce..38fc5911 100644 --- a/module/Core/src/Entity/ShortUrl.php +++ b/module/Core/src/Entity/ShortUrl.php @@ -54,22 +54,32 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable * }) */ protected $tags; + /** + * @var \DateTime + * @ORM\Column(name="valid_since", type="datetime", nullable=true) + */ + protected $validSince; + /** + * @var \DateTime + * @ORM\Column(name="valid_until", type="datetime", nullable=true) + */ + protected $validUntil; /** * ShortUrl constructor. */ public function __construct() { - $this->setDateCreated(new \DateTime()); - $this->setVisits(new ArrayCollection()); - $this->setShortCode(''); + $this->dateCreated = new \DateTime(); + $this->visits = new ArrayCollection(); + $this->shortCode = ''; $this->tags = new ArrayCollection(); } /** * @return string */ - public function getOriginalUrl() + public function getOriginalUrl(): string { return $this->originalUrl; } @@ -78,7 +88,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable * @param string $originalUrl * @return $this */ - public function setOriginalUrl($originalUrl) + public function setOriginalUrl(string $originalUrl) { $this->originalUrl = (string) $originalUrl; return $this; @@ -87,7 +97,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable /** * @return string */ - public function getShortCode() + public function getShortCode(): string { return $this->shortCode; } @@ -96,7 +106,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable * @param string $shortCode * @return $this */ - public function setShortCode($shortCode) + public function setShortCode(string $shortCode) { $this->shortCode = $shortCode; return $this; @@ -105,7 +115,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable /** * @return \DateTime */ - public function getDateCreated() + public function getDateCreated(): \DateTime { return $this->dateCreated; } @@ -114,34 +124,16 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable * @param \DateTime $dateCreated * @return $this */ - public function setDateCreated($dateCreated) + public function setDateCreated(\DateTime $dateCreated) { $this->dateCreated = $dateCreated; return $this; } - /** - * @return Visit[]|Collection - */ - public function getVisits() - { - return $this->visits; - } - - /** - * @param Visit[]|Collection $visits - * @return $this - */ - public function setVisits($visits) - { - $this->visits = $visits; - return $this; - } - /** * @return Collection|Tag[] */ - public function getTags() + public function getTags(): Collection { return $this->tags; } @@ -150,7 +142,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable * @param Collection|Tag[] $tags * @return $this */ - public function setTags($tags) + public function setTags(Collection $tags) { $this->tags = $tags; return $this; @@ -166,6 +158,42 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable return $this; } + /** + * @return \DateTime|null + */ + public function getValidSince() + { + return $this->validSince; + } + + /** + * @param \DateTime|null $validSince + * @return $this|self + */ + public function setValidSince($validSince): self + { + $this->validSince = $validSince; + return $this; + } + + /** + * @return \DateTime|null + */ + public function getValidUntil() + { + return $this->validUntil; + } + + /** + * @param \DateTime|null $validUntil + * @return $this|self + */ + public function setValidUntil($validUntil): self + { + $this->validUntil = $validUntil; + return $this; + } + /** * Specify data which should be serialized to JSON * @link http://php.net/manual/en/jsonserializable.jsonserialize.php diff --git a/module/Core/src/Repository/ShortUrlRepository.php b/module/Core/src/Repository/ShortUrlRepository.php index a888c07c..84d07511 100644 --- a/module/Core/src/Repository/ShortUrlRepository.php +++ b/module/Core/src/Repository/ShortUrlRepository.php @@ -17,8 +17,13 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI * @param string|array|null $orderBy * @return \Shlinkio\Shlink\Core\Entity\ShortUrl[] */ - public function findList($limit = null, $offset = null, $searchTerm = null, array $tags = [], $orderBy = null) - { + public function findList( + int $limit = null, + int $offset = null, + string $searchTerm = null, + array $tags = [], + $orderBy = null + ): array { $qb = $this->createListQueryBuilder($searchTerm, $tags); $qb->select('s'); @@ -74,7 +79,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI * @param array $tags * @return int */ - public function countList($searchTerm = null, array $tags = []) + public function countList(string $searchTerm = null, array $tags = []): int { $qb = $this->createListQueryBuilder($searchTerm, $tags); $qb->select('COUNT(s)'); @@ -87,7 +92,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI * @param array $tags * @return QueryBuilder */ - protected function createListQueryBuilder($searchTerm = null, array $tags = []) + protected function createListQueryBuilder(string $searchTerm = null, array $tags = []): QueryBuilder { $qb = $this->getEntityManager()->createQueryBuilder(); $qb->from(ShortUrl::class, 's'); @@ -117,4 +122,29 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI return $qb; } + + /** + * @param string $shortCode + * @return ShortUrl|null + */ + public function findOneByShortCode(string $shortCode) + { + $now = new \DateTimeImmutable(); + + $qb = $this->createQueryBuilder('s'); + $qb->where($qb->expr()->eq('s.shortCode', ':shortCode')) + ->setParameter('shortCode', $shortCode) + ->andWhere($qb->expr()->orX( + $qb->expr()->lte('s.validSince', ':now'), + $qb->expr()->isNull('s.validSince') + )) + ->andWhere($qb->expr()->orX( + $qb->expr()->gte('s.validUntil', ':now'), + $qb->expr()->isNull('s.validUntil') + )) + ->setParameter('now', $now) + ->setMaxResults(1); + + return $qb->getQuery()->getOneOrNullResult(); + } } diff --git a/module/Core/src/Repository/ShortUrlRepositoryInterface.php b/module/Core/src/Repository/ShortUrlRepositoryInterface.php index 3ddae2d6..bff0d723 100644 --- a/module/Core/src/Repository/ShortUrlRepositoryInterface.php +++ b/module/Core/src/Repository/ShortUrlRepositoryInterface.php @@ -5,7 +5,13 @@ namespace Shlinkio\Shlink\Core\Repository; use Doctrine\Common\Persistence\ObjectRepository; use Shlinkio\Shlink\Common\Repository\PaginableRepositoryInterface; +use Shlinkio\Shlink\Core\Entity\ShortUrl; interface ShortUrlRepositoryInterface extends ObjectRepository, PaginableRepositoryInterface { + /** + * @param string $shortCode + * @return ShortUrl|null + */ + public function findOneByShortCode(string $shortCode); } diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index 148279e8..4aecade9 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -14,6 +14,7 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; +use Shlinkio\Shlink\Core\Repository\ShortUrlRepository; use Shlinkio\Shlink\Core\Util\TagManagerTrait; class UrlShortener implements UrlShortenerInterface @@ -160,11 +161,13 @@ class UrlShortener implements UrlShortenerInterface throw InvalidShortCodeException::fromCharset($shortCode, $this->chars); } - $criteria = ['shortCode' => $shortCode]; - /** @var ShortUrl|null $shortUrl */ - $shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy($criteria); + /** @var ShortUrlRepository $shortUrlRepo */ + $shortUrlRepo = $this->em->getRepository(ShortUrl::class); + $shortUrl = $shortUrlRepo->findOneByShortCode($shortCode); if ($shortUrl === null) { - throw EntityDoesNotExistException::createFromEntityAndConditions(ShortUrl::class, $criteria); + throw EntityDoesNotExistException::createFromEntityAndConditions(ShortUrl::class, [ + 'shortCode' => $shortCode, + ]); } // Cache the shortcode diff --git a/module/Core/test/Service/UrlShortenerTest.php b/module/Core/test/Service/UrlShortenerTest.php index 6b7ed22c..89aeb44e 100644 --- a/module/Core/test/Service/UrlShortenerTest.php +++ b/module/Core/test/Service/UrlShortenerTest.php @@ -16,6 +16,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; use Shlinkio\Shlink\Core\Entity\ShortUrl; +use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Service\UrlShortener; use Zend\Diactoros\Uri; @@ -127,8 +128,8 @@ class UrlShortenerTest extends TestCase $shortUrl->setShortCode($shortCode) ->setOriginalUrl('expected_url'); - $repo = $this->prophesize(ObjectRepository::class); - $repo->findOneBy(['shortCode' => $shortCode])->willReturn($shortUrl); + $repo = $this->prophesize(ShortUrlRepositoryInterface::class); + $repo->findOneByShortCode($shortCode)->willReturn($shortUrl); $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); $this->assertFalse($this->cache->contains($shortCode . '_longUrl'));