Extracted logic to stringify ShortUrls to its own service

This commit is contained in:
Alejandro Celaya
2021-02-01 22:55:52 +01:00
parent 01aebd90d5
commit 9cddedcdba
28 changed files with 215 additions and 135 deletions

View File

@@ -43,6 +43,8 @@ return [
Action\QrCodeAction::class => ConfigAbstractFactory::class,
ShortUrl\Resolver\PersistenceShortUrlRelationResolver::class => ConfigAbstractFactory::class,
ShortUrl\Helper\ShortUrlStringifier::class => ConfigAbstractFactory::class,
ShortUrl\Transformer\ShortUrlDataTransformer::class => ConfigAbstractFactory::class,
Mercure\MercureUpdatesGenerator::class => ConfigAbstractFactory::class,
@@ -114,13 +116,15 @@ return [
],
Action\QrCodeAction::class => [
Service\ShortUrl\ShortUrlResolver::class,
'config.url_shortener.domain',
ShortUrl\Helper\ShortUrlStringifier::class,
'Logger_Shlink',
],
ShortUrl\Resolver\PersistenceShortUrlRelationResolver::class => ['em'],
ShortUrl\Helper\ShortUrlStringifier::class => ['config.url_shortener.domain'],
ShortUrl\Transformer\ShortUrlDataTransformer::class => [ShortUrl\Helper\ShortUrlStringifier::class],
Mercure\MercureUpdatesGenerator::class => ['config.url_shortener.domain'],
Mercure\MercureUpdatesGenerator::class => [ShortUrl\Transformer\ShortUrlDataTransformer::class],
Importer\ImportedLinksProcessor::class => [
'em',

View File

@@ -53,7 +53,7 @@ return [
'em',
'Logger_Shlink',
'config.url_shortener.visits_webhooks',
'config.url_shortener.domain',
ShortUrl\Transformer\ShortUrlDataTransformer::class,
Options\AppOptions::class,
],
EventDispatcher\NotifyVisitToMercure::class => [

View File

@@ -16,6 +16,7 @@ use Shlinkio\Shlink\Common\Response\QrCodeResponse;
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
class QrCodeAction implements MiddlewareInterface
{
@@ -24,17 +25,17 @@ class QrCodeAction implements MiddlewareInterface
private const MAX_SIZE = 1000;
private ShortUrlResolverInterface $urlResolver;
private array $domainConfig;
private ShortUrlStringifierInterface $stringifier;
private LoggerInterface $logger;
public function __construct(
ShortUrlResolverInterface $urlResolver,
array $domainConfig,
ShortUrlStringifierInterface $stringifier,
?LoggerInterface $logger = null
) {
$this->urlResolver = $urlResolver;
$this->domainConfig = $domainConfig;
$this->logger = $logger ?? new NullLogger();
$this->stringifier = $stringifier;
}
public function process(Request $request, RequestHandlerInterface $handler): Response
@@ -52,7 +53,7 @@ class QrCodeAction implements MiddlewareInterface
// Size attribute is deprecated
$size = $this->normalizeSize((int) $request->getAttribute('size', $query['size'] ?? self::DEFAULT_SIZE));
$qrCode = new QrCode($shortUrl->toString($this->domainConfig));
$qrCode = new QrCode($this->stringifier->stringify($shortUrl));
$qrCode->setSize($size);
$qrCode->setMargin(0);

View File

@@ -7,7 +7,6 @@ namespace Shlinkio\Shlink\Core\Entity;
use Cake\Chronos\Chronos;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Laminas\Diactoros\Uri;
use Shlinkio\Shlink\Common\Entity\AbstractEntity;
use Shlinkio\Shlink\Core\Exception\ShortCodeCannotBeRegeneratedException;
use Shlinkio\Shlink\Core\Model\ShortUrlEdit;
@@ -128,6 +127,36 @@ class ShortUrl extends AbstractEntity
return $this->tags;
}
public function getValidSince(): ?Chronos
{
return $this->validSince;
}
public function getValidUntil(): ?Chronos
{
return $this->validUntil;
}
public function getVisitsCount(): int
{
return count($this->visits);
}
/**
* @param Collection|Visit[] $visits
* @internal
*/
public function setVisits(Collection $visits): self
{
$this->visits = $visits;
return $this;
}
public function getMaxVisits(): ?int
{
return $this->maxVisits;
}
public function update(
ShortUrlEdit $shortUrlEdit,
?ShortUrlRelationResolverInterface $relationResolver = null
@@ -168,36 +197,6 @@ class ShortUrl extends AbstractEntity
$this->shortCode = generateRandomShortCode($this->shortCodeLength);
}
public function getValidSince(): ?Chronos
{
return $this->validSince;
}
public function getValidUntil(): ?Chronos
{
return $this->validUntil;
}
public function getVisitsCount(): int
{
return count($this->visits);
}
/**
* @param Collection|Visit[] $visits
* @internal
*/
public function setVisits(Collection $visits): self
{
$this->visits = $visits;
return $this;
}
public function getMaxVisits(): ?int
{
return $this->maxVisits;
}
public function isEnabled(): bool
{
$maxVisitsReached = $this->maxVisits !== null && $this->getVisitsCount() >= $this->maxVisits;
@@ -218,21 +217,4 @@ class ShortUrl extends AbstractEntity
return true;
}
public function toString(array $domainConfig): string
{
return (new Uri())->withPath($this->shortCode)
->withScheme($domainConfig['schema'] ?? 'http')
->withHost($this->resolveDomain($domainConfig['hostname'] ?? ''))
->__toString();
}
private function resolveDomain(string $fallback = ''): string
{
if ($this->domain === null) {
return $fallback;
}
return $this->domain->getAuthority();
}
}

View File

@@ -12,10 +12,10 @@ use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\RequestOptions;
use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\EventDispatcher\Event\VisitLocated;
use Shlinkio\Shlink\Core\Options\AppOptions;
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
use Throwable;
use function Functional\map;
@@ -29,7 +29,7 @@ class NotifyVisitToWebHooks
private LoggerInterface $logger;
/** @var string[] */
private array $webhooks;
private ShortUrlDataTransformer $transformer;
private DataTransformerInterface $transformer;
private AppOptions $appOptions;
public function __construct(
@@ -37,14 +37,14 @@ class NotifyVisitToWebHooks
EntityManagerInterface $em,
LoggerInterface $logger,
array $webhooks,
array $domainConfig,
DataTransformerInterface $transformer,
AppOptions $appOptions
) {
$this->httpClient = $httpClient;
$this->em = $em;
$this->logger = $logger;
$this->webhooks = $webhooks;
$this->transformer = new ShortUrlDataTransformer($domainConfig);
$this->transformer = $transformer;
$this->appOptions = $appOptions;
}

View File

@@ -4,8 +4,8 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Mercure;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
use Symfony\Component\Mercure\Update;
use function json_encode;
@@ -17,11 +17,11 @@ final class MercureUpdatesGenerator implements MercureUpdatesGeneratorInterface
{
private const NEW_VISIT_TOPIC = 'https://shlink.io/new-visit';
private ShortUrlDataTransformer $transformer;
private DataTransformerInterface $transformer;
public function __construct(array $domainConfig)
public function __construct(DataTransformerInterface $transformer)
{
$this->transformer = new ShortUrlDataTransformer($domainConfig);
$this->transformer = $transformer;
}
public function newVisitUpdate(Visit $visit): Update

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ShortUrl\Helper;
use Laminas\Diactoros\Uri;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
class ShortUrlStringifier implements ShortUrlStringifierInterface
{
private array $domainConfig;
public function __construct(array $domainConfig)
{
$this->domainConfig = $domainConfig;
}
public function stringify(ShortUrl $shortUrl): string
{
return (new Uri())->withPath($shortUrl->getShortCode())
->withScheme($this->domainConfig['schema'] ?? 'http')
->withHost($this->resolveDomain($shortUrl))
->__toString();
}
private function resolveDomain(ShortUrl $shortUrl): string
{
$domain = $shortUrl->getDomain();
if ($domain === null) {
return $this->domainConfig['hostname'] ?? '';
}
return $domain->getAuthority();
}
}

View File

@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ShortUrl\Helper;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
interface ShortUrlStringifierInterface
{
public function stringify(ShortUrl $shortUrl): string;
}

View File

@@ -2,21 +2,22 @@
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Transformer;
namespace Shlinkio\Shlink\Core\ShortUrl\Transformer;
use Shlinkio\Shlink\Common\Rest\DataTransformerInterface;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
use function Functional\invoke;
use function Functional\invoke_if;
class ShortUrlDataTransformer implements DataTransformerInterface
{
private array $domainConfig;
private ShortUrlStringifierInterface $stringifier;
public function __construct(array $domainConfig)
public function __construct(ShortUrlStringifierInterface $stringifier)
{
$this->domainConfig = $domainConfig;
$this->stringifier = $stringifier;
}
/**
@@ -26,7 +27,7 @@ class ShortUrlDataTransformer implements DataTransformerInterface
{
return [
'shortCode' => $shortUrl->getShortCode(),
'shortUrl' => $shortUrl->toString($this->domainConfig),
'shortUrl' => $this->stringifier->stringify($shortUrl),
'longUrl' => $shortUrl->getLongUrl(),
'dateCreated' => $shortUrl->getDateCreated()->toAtomString(),
'visitsCount' => $shortUrl->getVisitsCount(),

View File

@@ -20,6 +20,7 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortUrlResolverInterface;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
use function getimagesizefromstring;
@@ -37,7 +38,10 @@ class QrCodeActionTest extends TestCase
$this->urlResolver = $this->prophesize(ShortUrlResolverInterface::class);
$this->action = new QrCodeAction($this->urlResolver->reveal(), ['domain' => 'doma.in']);
$this->action = new QrCodeAction(
$this->urlResolver->reveal(),
new ShortUrlStringifier(['domain' => 'doma.in']),
);
}
/** @test */

View File

@@ -23,6 +23,8 @@ use Shlinkio\Shlink\Core\EventDispatcher\Event\VisitLocated;
use Shlinkio\Shlink\Core\EventDispatcher\NotifyVisitToWebHooks;
use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Options\AppOptions;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer;
use function count;
use function Functional\contains;
@@ -127,7 +129,7 @@ class NotifyVisitToWebHooksTest extends TestCase
$this->em->reveal(),
$this->logger->reveal(),
$webhooks,
[],
new ShortUrlDataTransformer(new ShortUrlStringifier([])),
new AppOptions(['name' => 'Shlink', 'version' => '1.2.3']),
);
}

View File

@@ -10,6 +10,8 @@ use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Mercure\MercureUpdatesGenerator;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer;
use function Shlinkio\Shlink\Common\json_decode;
@@ -19,7 +21,7 @@ class MercureUpdatesGeneratorTest extends TestCase
public function setUp(): void
{
$this->generator = new MercureUpdatesGenerator([]);
$this->generator = new MercureUpdatesGenerator(new ShortUrlDataTransformer(new ShortUrlStringifier([])));
}
/**

View File

@@ -2,13 +2,14 @@
declare(strict_types=1);
namespace ShlinkioTest\Shlink\Core\Transformer;
namespace ShlinkioTest\Shlink\Core\ShortUrl\Transformer;
use Cake\Chronos\Chronos;
use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier;
use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer;
use function random_int;
@@ -18,7 +19,7 @@ class ShortUrlDataTransformerTest extends TestCase
public function setUp(): void
{
$this->transformer = new ShortUrlDataTransformer([]);
$this->transformer = new ShortUrlDataTransformer(new ShortUrlStringifier([]));
}
/**