mirror of
https://github.com/shlinkio/shlink.git
synced 2024-11-24 05:38:06 +03:00
Refactored UrlShortener public method to receibe DTOs instead of primitive params
This commit is contained in:
parent
5756609531
commit
d61f5faf59
9 changed files with 74 additions and 78 deletions
|
@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
|
|||
use Cake\Chronos\Chronos;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Shlinkio\Shlink\Core\Util\ShortUrlBuilderTrait;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
@ -116,10 +117,12 @@ class GenerateShortUrlCommand extends Command
|
|||
$shortCode = $this->urlShortener->urlToShortCode(
|
||||
new Uri($longUrl),
|
||||
$tags,
|
||||
$this->getOptionalDate($input, 'validSince'),
|
||||
$this->getOptionalDate($input, 'validUntil'),
|
||||
$customSlug,
|
||||
$maxVisits !== null ? (int) $maxVisits : null
|
||||
ShortUrlMeta::createFromParams(
|
||||
$this->getOptionalDate($input, 'validSince'),
|
||||
$this->getOptionalDate($input, 'validUntil'),
|
||||
$customSlug,
|
||||
$maxVisits !== null ? (int) $maxVisits : null
|
||||
)
|
||||
)->getShortCode();
|
||||
$shortUrl = $this->buildShortUrl($this->domainConfig, $shortCode);
|
||||
|
||||
|
|
|
@ -17,11 +17,11 @@ final class CreateShortUrlData
|
|||
public function __construct(
|
||||
UriInterface $longUrl,
|
||||
array $tags = [],
|
||||
ShortUrlMeta $meta = null
|
||||
?ShortUrlMeta $meta = null
|
||||
) {
|
||||
$this->longUrl = $longUrl;
|
||||
$this->tags = $tags;
|
||||
$this->meta = $meta ?? ShortUrlMeta::createFromParams();
|
||||
$this->meta = $meta ?? ShortUrlMeta::createEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -138,4 +138,12 @@ final class ShortUrlMeta
|
|||
{
|
||||
return $this->maxVisits !== null;
|
||||
}
|
||||
|
||||
public function withCustomSlug(string $customSlug): self
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->customSlug = $customSlug;
|
||||
|
||||
return $clone;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shlinkio\Shlink\Core\Service;
|
||||
|
||||
use Cake\Chronos\Chronos;
|
||||
use Cocur\Slugify\SlugifyInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
|
@ -53,41 +52,35 @@ class UrlShortener implements UrlShortenerInterface
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string[] $tags
|
||||
* @throws NonUniqueSlugException
|
||||
* @throws InvalidUrlException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function urlToShortCode(
|
||||
UriInterface $url,
|
||||
array $tags = [],
|
||||
?Chronos $validSince = null,
|
||||
?Chronos $validUntil = null,
|
||||
?string $customSlug = null,
|
||||
?int $maxVisits = null
|
||||
): ShortUrl {
|
||||
public function urlToShortCode(UriInterface $url, array $tags, ShortUrlMeta $meta): ShortUrl
|
||||
{
|
||||
// If the URL validation is enabled, check that the URL actually exists
|
||||
if ($this->options->isUrlValidationEnabled()) {
|
||||
$this->checkUrlExists($url);
|
||||
}
|
||||
$customSlug = $this->processCustomSlug($customSlug);
|
||||
$meta = $this->processCustomSlug($meta);
|
||||
|
||||
// Transactionally insert the short url, then generate the short code and finally update the short code
|
||||
try {
|
||||
$this->em->beginTransaction();
|
||||
|
||||
// First, create the short URL with an empty short code
|
||||
$shortUrl = new ShortUrl(
|
||||
(string) $url,
|
||||
ShortUrlMeta::createFromParams($validSince, $validUntil, null, $maxVisits)
|
||||
);
|
||||
$shortUrl = new ShortUrl((string) $url, $meta);
|
||||
$this->em->persist($shortUrl);
|
||||
$this->em->flush();
|
||||
|
||||
// Generate the short code and persist it
|
||||
// TODO Somehow provide the logic to calculate the shortCode to avoid the need of a setter
|
||||
$shortCode = $customSlug ?? $this->convertAutoincrementIdToShortCode((float) $shortUrl->getId());
|
||||
$shortUrl->setShortCode($shortCode)
|
||||
->setTags($this->tagNamesToEntities($this->em, $tags));
|
||||
// Generate the short code and persist it if no custom slug was provided
|
||||
if (! $meta->hasCustomSlug()) {
|
||||
// TODO Somehow provide the logic to calculate the shortCode to avoid the need of a setter
|
||||
$shortCode = $this->convertAutoincrementIdToShortCode((float) $shortUrl->getId());
|
||||
$shortUrl->setShortCode($shortCode);
|
||||
}
|
||||
$shortUrl->setTags($this->tagNamesToEntities($this->em, $tags));
|
||||
$this->em->flush();
|
||||
|
||||
$this->em->commit();
|
||||
|
@ -130,25 +123,27 @@ class UrlShortener implements UrlShortenerInterface
|
|||
return $chars[(int) $id] . $code;
|
||||
}
|
||||
|
||||
private function processCustomSlug(?string $customSlug): ?string
|
||||
private function processCustomSlug(ShortUrlMeta $meta): ?ShortUrlMeta
|
||||
{
|
||||
if ($customSlug === null) {
|
||||
return null;
|
||||
if (! $meta->hasCustomSlug()) {
|
||||
return $meta;
|
||||
}
|
||||
|
||||
// If a custom slug was provided, make sure it's unique
|
||||
$customSlug = $this->slugger->slugify($customSlug);
|
||||
$shortUrl = $this->em->getRepository(ShortUrl::class)->findOneBy(['shortCode' => $customSlug]);
|
||||
if ($shortUrl !== null) {
|
||||
// FIXME If the slug was generated while filtering the value originally, we would not need an immutable setter
|
||||
// in ShortUrlMeta
|
||||
$customSlug = $this->slugger->slugify($meta->getCustomSlug());
|
||||
|
||||
/** @var ShortUrlRepository $repo */
|
||||
$repo = $this->em->getRepository(ShortUrl::class);
|
||||
$shortUrlsCount = $repo->count(['shortCode' => $customSlug]);
|
||||
if ($shortUrlsCount > 0) {
|
||||
throw NonUniqueSlugException::fromSlug($customSlug);
|
||||
}
|
||||
|
||||
return $customSlug;
|
||||
return $meta->withCustomSlug($customSlug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to find the mapped URL for provided short code. Returns null if not found
|
||||
*
|
||||
* @throws InvalidShortCodeException
|
||||
* @throws EntityDoesNotExistException
|
||||
*/
|
||||
|
|
|
@ -3,7 +3,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shlinkio\Shlink\Core\Service;
|
||||
|
||||
use Cake\Chronos\Chronos;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Exception\EntityDoesNotExistException;
|
||||
|
@ -11,26 +10,19 @@ use Shlinkio\Shlink\Core\Exception\InvalidShortCodeException;
|
|||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
||||
use Shlinkio\Shlink\Core\Exception\RuntimeException;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
|
||||
interface UrlShortenerInterface
|
||||
{
|
||||
/**
|
||||
* @param string[] $tags
|
||||
* @throws NonUniqueSlugException
|
||||
* @throws InvalidUrlException
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function urlToShortCode(
|
||||
UriInterface $url,
|
||||
array $tags = [],
|
||||
?Chronos $validSince = null,
|
||||
?Chronos $validUntil = null,
|
||||
?string $customSlug = null,
|
||||
?int $maxVisits = null
|
||||
): ShortUrl;
|
||||
public function urlToShortCode(UriInterface $url, array $tags, ShortUrlMeta $meta): ShortUrl;
|
||||
|
||||
/**
|
||||
* Tries to find the mapped URL for provided short code. Returns null if not found
|
||||
*
|
||||
* @throws InvalidShortCodeException
|
||||
* @throws EntityDoesNotExistException
|
||||
*/
|
||||
|
|
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||
namespace ShlinkioTest\Shlink\Core\Service;
|
||||
|
||||
use Cocur\Slugify\SlugifyInterface;
|
||||
use Doctrine\Common\Persistence\ObjectRepository;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\ORMException;
|
||||
|
@ -17,7 +16,9 @@ use Prophecy\Prophecy\MethodProphecy;
|
|||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
use Shlinkio\Shlink\Core\Options\UrlShortenerOptions;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Zend\Diactoros\Uri;
|
||||
|
@ -49,8 +50,8 @@ class UrlShortenerTest extends TestCase
|
|||
$shortUrl = $arguments[0];
|
||||
$shortUrl->setId('10');
|
||||
});
|
||||
$repo = $this->prophesize(ObjectRepository::class);
|
||||
$repo->findOneBy(Argument::any())->willReturn(null);
|
||||
$repo = $this->prophesize(ShortUrlRepository::class);
|
||||
$repo->count(Argument::any())->willReturn(0);
|
||||
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
|
||||
|
||||
$this->slugger = $this->prophesize(SlugifyInterface::class);
|
||||
|
@ -74,7 +75,11 @@ class UrlShortenerTest extends TestCase
|
|||
public function urlIsProperlyShortened()
|
||||
{
|
||||
// 10 -> 12C1c
|
||||
$shortUrl = $this->urlShortener->urlToShortCode(new Uri('http://foobar.com/12345/hello?foo=bar'));
|
||||
$shortUrl = $this->urlShortener->urlToShortCode(
|
||||
new Uri('http://foobar.com/12345/hello?foo=bar'),
|
||||
[],
|
||||
ShortUrlMeta::createEmpty()
|
||||
);
|
||||
$this->assertEquals('12C1c', $shortUrl->getShortCode());
|
||||
}
|
||||
|
||||
|
@ -91,7 +96,11 @@ class UrlShortenerTest extends TestCase
|
|||
$this->em->close()->shouldBeCalledOnce();
|
||||
|
||||
$this->em->flush()->willThrow(new ORMException());
|
||||
$this->urlShortener->urlToShortCode(new Uri('http://foobar.com/12345/hello?foo=bar'));
|
||||
$this->urlShortener->urlToShortCode(
|
||||
new Uri('http://foobar.com/12345/hello?foo=bar'),
|
||||
[],
|
||||
ShortUrlMeta::createEmpty()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,7 +114,11 @@ class UrlShortenerTest extends TestCase
|
|||
$this->httpClient->request(Argument::cetera())->willThrow(
|
||||
new ClientException('', $this->prophesize(Request::class)->reveal())
|
||||
);
|
||||
$this->urlShortener->urlToShortCode(new Uri('http://foobar.com/12345/hello?foo=bar'));
|
||||
$this->urlShortener->urlToShortCode(
|
||||
new Uri('http://foobar.com/12345/hello?foo=bar'),
|
||||
[],
|
||||
ShortUrlMeta::createEmpty()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -119,9 +132,7 @@ class UrlShortenerTest extends TestCase
|
|||
$this->urlShortener->urlToShortCode(
|
||||
new Uri('http://foobar.com/12345/hello?foo=bar'),
|
||||
[],
|
||||
null,
|
||||
null,
|
||||
'custom-slug'
|
||||
ShortUrlMeta::createFromRawData(['customSlug' => 'custom-slug'])
|
||||
);
|
||||
|
||||
$slugify->shouldHaveBeenCalledOnce();
|
||||
|
@ -135,24 +146,21 @@ class UrlShortenerTest extends TestCase
|
|||
/** @var MethodProphecy $slugify */
|
||||
$slugify = $this->slugger->slugify('custom-slug')->willReturnArgument();
|
||||
|
||||
$repo = $this->prophesize(ShortUrlRepositoryInterface::class);
|
||||
/** @var MethodProphecy $findBySlug */
|
||||
$findBySlug = $repo->findOneBy(['shortCode' => 'custom-slug'])->willReturn(new ShortUrl(''));
|
||||
$repo = $this->prophesize(ShortUrlRepository::class);
|
||||
$countBySlug = $repo->count(['shortCode' => 'custom-slug'])->willReturn(1);
|
||||
$repo->findOneBy(Argument::cetera())->willReturn(null);
|
||||
/** @var MethodProphecy $getRepo */
|
||||
$getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
|
||||
|
||||
$slugify->shouldBeCalledOnce();
|
||||
$findBySlug->shouldBeCalledOnce();
|
||||
$countBySlug->shouldBeCalledOnce();
|
||||
$getRepo->shouldBeCalled();
|
||||
$this->expectException(NonUniqueSlugException::class);
|
||||
|
||||
$this->urlShortener->urlToShortCode(
|
||||
new Uri('http://foobar.com/12345/hello?foo=bar'),
|
||||
[],
|
||||
null,
|
||||
null,
|
||||
'custom-slug'
|
||||
ShortUrlMeta::createFromRawData(['customSlug' => 'custom-slug'])
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -53,17 +53,9 @@ abstract class AbstractCreateShortUrlAction extends AbstractRestAction
|
|||
|
||||
$longUrl = $shortUrlData->getLongUrl();
|
||||
$shortUrlMeta = $shortUrlData->getMeta();
|
||||
$customSlug = $shortUrlMeta->getCustomSlug();
|
||||
|
||||
try {
|
||||
$shortUrl = $this->urlShortener->urlToShortCode(
|
||||
$longUrl,
|
||||
$shortUrlData->getTags(),
|
||||
$shortUrlMeta->getValidSince(),
|
||||
$shortUrlMeta->getValidUntil(),
|
||||
$customSlug,
|
||||
$shortUrlMeta->getMaxVisits()
|
||||
);
|
||||
$shortUrl = $this->urlShortener->urlToShortCode($longUrl, $shortUrlData->getTags(), $shortUrlMeta);
|
||||
$transformer = new ShortUrlDataTransformer($this->domainConfig);
|
||||
|
||||
return new JsonResponse($transformer->transform($shortUrl));
|
||||
|
@ -74,6 +66,7 @@ abstract class AbstractCreateShortUrlAction extends AbstractRestAction
|
|||
'message' => sprintf('Provided URL %s is invalid. Try with a different one.', $longUrl),
|
||||
], self::STATUS_BAD_REQUEST);
|
||||
} catch (NonUniqueSlugException $e) {
|
||||
$customSlug = $shortUrlMeta->getCustomSlug();
|
||||
$this->logger->warning('Provided non-unique slug. {e}', ['e' => $e]);
|
||||
return new JsonResponse([
|
||||
'error' => RestUtils::getRestErrorCodeFromException($e),
|
||||
|
|
|
@ -10,6 +10,7 @@ use Prophecy\Prophecy\ObjectProphecy;
|
|||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Exception\InvalidUrlException;
|
||||
use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortener;
|
||||
use Shlinkio\Shlink\Rest\Action\ShortUrl\CreateShortUrlAction;
|
||||
use Shlinkio\Shlink\Rest\Util\RestUtils;
|
||||
|
@ -86,9 +87,7 @@ class CreateShortUrlActionTest extends TestCase
|
|||
$this->urlShortener->urlToShortCode(
|
||||
Argument::type(Uri::class),
|
||||
Argument::type('array'),
|
||||
null,
|
||||
null,
|
||||
'foo',
|
||||
ShortUrlMeta::createFromRawData(['customSlug' => 'foo']),
|
||||
Argument::cetera()
|
||||
)->willThrow(NonUniqueSlugException::class)->shouldBeCalledOnce();
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ use Prophecy\Argument;
|
|||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
use Shlinkio\Shlink\Core\Service\UrlShortenerInterface;
|
||||
use Shlinkio\Shlink\Rest\Action\ShortUrl\SingleStepCreateShortUrlAction;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface;
|
||||
|
@ -91,10 +92,7 @@ class SingleStepCreateShortUrlActionTest extends TestCase
|
|||
return $argument;
|
||||
}),
|
||||
[],
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
ShortUrlMeta::createEmpty()
|
||||
)->willReturn(new ShortUrl(''));
|
||||
|
||||
$resp = $this->action->handle($request);
|
||||
|
|
Loading…
Reference in a new issue