Updated UrlShortener so that it does not match a short code which is out of the validity dat erange

This commit is contained in:
Alejandro Celaya 2017-10-21 11:58:20 +02:00
parent 68b4cfbae0
commit a3bbd06fe3
6 changed files with 116 additions and 42 deletions

View file

@ -15,14 +15,20 @@ interface PaginableRepositoryInterface
* @param string|array|null $orderBy * @param string|array|null $orderBy
* @return array * @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 * Counts the number of elements in a list using provided filtering data
* *
* @param null $searchTerm * @param string|null $searchTerm
* @param array $tags * @param array $tags
* @return int * @return int
*/ */
public function countList($searchTerm = null, array $tags = []); public function countList(string $searchTerm = null, array $tags = []): int;
} }

View file

@ -54,22 +54,32 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable
* }) * })
*/ */
protected $tags; 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. * ShortUrl constructor.
*/ */
public function __construct() public function __construct()
{ {
$this->setDateCreated(new \DateTime()); $this->dateCreated = new \DateTime();
$this->setVisits(new ArrayCollection()); $this->visits = new ArrayCollection();
$this->setShortCode(''); $this->shortCode = '';
$this->tags = new ArrayCollection(); $this->tags = new ArrayCollection();
} }
/** /**
* @return string * @return string
*/ */
public function getOriginalUrl() public function getOriginalUrl(): string
{ {
return $this->originalUrl; return $this->originalUrl;
} }
@ -78,7 +88,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable
* @param string $originalUrl * @param string $originalUrl
* @return $this * @return $this
*/ */
public function setOriginalUrl($originalUrl) public function setOriginalUrl(string $originalUrl)
{ {
$this->originalUrl = (string) $originalUrl; $this->originalUrl = (string) $originalUrl;
return $this; return $this;
@ -87,7 +97,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable
/** /**
* @return string * @return string
*/ */
public function getShortCode() public function getShortCode(): string
{ {
return $this->shortCode; return $this->shortCode;
} }
@ -96,7 +106,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable
* @param string $shortCode * @param string $shortCode
* @return $this * @return $this
*/ */
public function setShortCode($shortCode) public function setShortCode(string $shortCode)
{ {
$this->shortCode = $shortCode; $this->shortCode = $shortCode;
return $this; return $this;
@ -105,7 +115,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable
/** /**
* @return \DateTime * @return \DateTime
*/ */
public function getDateCreated() public function getDateCreated(): \DateTime
{ {
return $this->dateCreated; return $this->dateCreated;
} }
@ -114,34 +124,16 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable
* @param \DateTime $dateCreated * @param \DateTime $dateCreated
* @return $this * @return $this
*/ */
public function setDateCreated($dateCreated) public function setDateCreated(\DateTime $dateCreated)
{ {
$this->dateCreated = $dateCreated; $this->dateCreated = $dateCreated;
return $this; 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[] * @return Collection|Tag[]
*/ */
public function getTags() public function getTags(): Collection
{ {
return $this->tags; return $this->tags;
} }
@ -150,7 +142,7 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable
* @param Collection|Tag[] $tags * @param Collection|Tag[] $tags
* @return $this * @return $this
*/ */
public function setTags($tags) public function setTags(Collection $tags)
{ {
$this->tags = $tags; $this->tags = $tags;
return $this; return $this;
@ -166,6 +158,42 @@ class ShortUrl extends AbstractEntity implements \JsonSerializable
return $this; 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 * Specify data which should be serialized to JSON
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php * @link http://php.net/manual/en/jsonserializable.jsonserialize.php

View file

@ -17,8 +17,13 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
* @param string|array|null $orderBy * @param string|array|null $orderBy
* @return \Shlinkio\Shlink\Core\Entity\ShortUrl[] * @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 = $this->createListQueryBuilder($searchTerm, $tags);
$qb->select('s'); $qb->select('s');
@ -74,7 +79,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
* @param array $tags * @param array $tags
* @return int * @return int
*/ */
public function countList($searchTerm = null, array $tags = []) public function countList(string $searchTerm = null, array $tags = []): int
{ {
$qb = $this->createListQueryBuilder($searchTerm, $tags); $qb = $this->createListQueryBuilder($searchTerm, $tags);
$qb->select('COUNT(s)'); $qb->select('COUNT(s)');
@ -87,7 +92,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
* @param array $tags * @param array $tags
* @return QueryBuilder * @return QueryBuilder
*/ */
protected function createListQueryBuilder($searchTerm = null, array $tags = []) protected function createListQueryBuilder(string $searchTerm = null, array $tags = []): QueryBuilder
{ {
$qb = $this->getEntityManager()->createQueryBuilder(); $qb = $this->getEntityManager()->createQueryBuilder();
$qb->from(ShortUrl::class, 's'); $qb->from(ShortUrl::class, 's');
@ -117,4 +122,29 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
return $qb; 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();
}
} }

View file

@ -5,7 +5,13 @@ namespace Shlinkio\Shlink\Core\Repository;
use Doctrine\Common\Persistence\ObjectRepository; use Doctrine\Common\Persistence\ObjectRepository;
use Shlinkio\Shlink\Common\Repository\PaginableRepositoryInterface; use Shlinkio\Shlink\Common\Repository\PaginableRepositoryInterface;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
interface ShortUrlRepositoryInterface extends ObjectRepository, PaginableRepositoryInterface interface ShortUrlRepositoryInterface extends ObjectRepository, PaginableRepositoryInterface
{ {
/**
* @param string $shortCode
* @return ShortUrl|null
*/
public function findOneByShortCode(string $shortCode);
} }

View file

@ -14,6 +14,7 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException; use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException; use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
use Shlinkio\Shlink\Core\Util\TagManagerTrait; use Shlinkio\Shlink\Core\Util\TagManagerTrait;
class UrlShortener implements UrlShortenerInterface class UrlShortener implements UrlShortenerInterface
@ -160,11 +161,13 @@ class UrlShortener implements UrlShortenerInterface
throw InvalidShortCodeException::fromCharset($shortCode, $this->chars); throw InvalidShortCodeException::fromCharset($shortCode, $this->chars);
} }
$criteria = ['shortCode' => $shortCode]; /** @var ShortUrlRepository $shortUrlRepo */
/** @var ShortUrl|null $shortUrl */ $shortUrlRepo = $this->em->getRepository(ShortUrl::class);
$shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy($criteria); $shortUrl = $shortUrlRepo->findOneByShortCode($shortCode);
if ($shortUrl === null) { if ($shortUrl === null) {
throw EntityDoesNotExistException::createFromEntityAndConditions(ShortUrl::class, $criteria); throw EntityDoesNotExistException::createFromEntityAndConditions(ShortUrl::class, [
'shortCode' => $shortCode,
]);
} }
// Cache the shortcode // Cache the shortcode

View file

@ -16,6 +16,7 @@ use PHPUnit\Framework\TestCase;
use Prophecy\Argument; use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy; use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
use Shlinkio\Shlink\Core\Service\UrlShortener; use Shlinkio\Shlink\Core\Service\UrlShortener;
use Zend\Diactoros\Uri; use Zend\Diactoros\Uri;
@ -127,8 +128,8 @@ class UrlShortenerTest extends TestCase
$shortUrl->setShortCode($shortCode) $shortUrl->setShortCode($shortCode)
->setOriginalUrl('expected_url'); ->setOriginalUrl('expected_url');
$repo = $this->prophesize(ObjectRepository::class); $repo = $this->prophesize(ShortUrlRepositoryInterface::class);
$repo->findOneBy(['shortCode' => $shortCode])->willReturn($shortUrl); $repo->findOneByShortCode($shortCode)->willReturn($shortUrl);
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal()); $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
$this->assertFalse($this->cache->contains($shortCode . '_longUrl')); $this->assertFalse($this->cache->contains($shortCode . '_longUrl'));