Added logic to resolve tags during short URL creation through ShortUrlRelationResolver

This commit is contained in:
Alejandro Celaya 2021-01-31 10:53:18 +01:00
parent 1081211439
commit 82091c7951
15 changed files with 92 additions and 20 deletions

View file

@ -64,7 +64,7 @@ class ShortUrl extends AbstractEntity
$instance->longUrl = $meta->getLongUrl();
$instance->dateCreated = Chronos::now();
$instance->visits = new ArrayCollection();
$instance->tags = new ArrayCollection();
$instance->tags = $relationResolver->resolveTags($meta->getTags());
$instance->validSince = $meta->getValidSince();
$instance->validUntil = $meta->getValidUntil();
$instance->maxVisits = $meta->getMaxVisits();
@ -85,6 +85,7 @@ class ShortUrl extends AbstractEntity
$meta = [
ShortUrlInputFilter::LONG_URL => $url->longUrl(),
ShortUrlInputFilter::DOMAIN => $url->domain(),
ShortUrlInputFilter::TAGS => $url->tags(),
ShortUrlInputFilter::VALIDATE_URL => false,
];
if ($importShortCode) {
@ -129,6 +130,7 @@ class ShortUrl extends AbstractEntity
/**
* @param Collection|Tag[] $tags
* @deprecated
*/
public function setTags(Collection $tags): self
{

View file

@ -10,7 +10,6 @@ use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortCodeHelperInterface;
use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface;
use Shlinkio\Shlink\Core\Util\DoctrineBatchHelperInterface;
use Shlinkio\Shlink\Core\Util\TagManagerTrait;
use Shlinkio\Shlink\Importer\ImportedLinksProcessorInterface;
use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl;
use Symfony\Component\Console\Style\StyleInterface;
@ -19,8 +18,6 @@ use function sprintf;
class ImportedLinksProcessor implements ImportedLinksProcessorInterface
{
use TagManagerTrait;
private EntityManagerInterface $em;
private ShortUrlRelationResolverInterface $relationResolver;
private ShortCodeHelperInterface $shortCodeHelper;
@ -59,8 +56,6 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface
}
$shortUrl = ShortUrl::fromImport($url, $importShortCodes, $this->relationResolver);
$shortUrl->setTags($this->tagNamesToEntities($this->em, $url->tags()));
if (! $this->handleShortCodeUniqueness($url, $shortUrl, $io, $importShortCodes)) {
continue;
}

View file

@ -23,6 +23,8 @@ final class ShortUrlEdit
private ?Chronos $validUntil = null;
private bool $maxVisitsPropWasProvided = false;
private ?int $maxVisits = null;
private bool $tagsPropWasProvided = false;
private array $tags = [];
private ?bool $validateUrl = null;
private function __construct()
@ -53,12 +55,14 @@ final class ShortUrlEdit
$this->validSincePropWasProvided = array_key_exists(ShortUrlInputFilter::VALID_SINCE, $data);
$this->validUntilPropWasProvided = array_key_exists(ShortUrlInputFilter::VALID_UNTIL, $data);
$this->maxVisitsPropWasProvided = array_key_exists(ShortUrlInputFilter::MAX_VISITS, $data);
$this->tagsPropWasProvided = array_key_exists(ShortUrlInputFilter::TAGS, $data);
$this->longUrl = $inputFilter->getValue(ShortUrlInputFilter::LONG_URL);
$this->validSince = parseDateField($inputFilter->getValue(ShortUrlInputFilter::VALID_SINCE));
$this->validUntil = parseDateField($inputFilter->getValue(ShortUrlInputFilter::VALID_UNTIL));
$this->maxVisits = getOptionalIntFromInputFilter($inputFilter, ShortUrlInputFilter::MAX_VISITS);
$this->validateUrl = getOptionalBoolFromInputFilter($inputFilter, ShortUrlInputFilter::VALIDATE_URL);
$this->tags = $inputFilter->getValue(ShortUrlInputFilter::TAGS);
}
public function longUrl(): ?string
@ -101,6 +105,19 @@ final class ShortUrlEdit
return $this->maxVisitsPropWasProvided;
}
/**
* @return string[]
*/
public function tags(): array
{
return $this->tags;
}
public function hasTags(): bool
{
return $this->tagsPropWasProvided;
}
public function doValidateUrl(): ?bool
{
return $this->validateUrl;

View file

@ -53,6 +53,7 @@ class ShortUrlService implements ShortUrlServiceInterface
/**
* @param string[] $tags
* @deprecated Use updateShortUrl instead
* @throws ShortUrlNotFoundException
*/
public function setTagsByShortCode(ShortUrlIdentifier $identifier, array $tags, ?ApiKey $apiKey = null): ShortUrl
@ -69,7 +70,7 @@ class ShortUrlService implements ShortUrlServiceInterface
* @throws ShortUrlNotFoundException
* @throws InvalidUrlException
*/
public function updateMetadataByShortCode(
public function updateShortUrl(
ShortUrlIdentifier $identifier,
ShortUrlEdit $shortUrlEdit,
?ApiKey $apiKey = null

View file

@ -22,6 +22,7 @@ interface ShortUrlServiceInterface
/**
* @param string[] $tags
* @deprecated Use updateShortUrl instead
* @throws ShortUrlNotFoundException
*/
public function setTagsByShortCode(ShortUrlIdentifier $identifier, array $tags, ?ApiKey $apiKey = null): ShortUrl;
@ -30,7 +31,7 @@ interface ShortUrlServiceInterface
* @throws ShortUrlNotFoundException
* @throws InvalidUrlException
*/
public function updateMetadataByShortCode(
public function updateShortUrl(
ShortUrlIdentifier $identifier,
ShortUrlEdit $shortUrlEdit,
?ApiKey $apiKey = null

View file

@ -12,14 +12,11 @@ use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
use Shlinkio\Shlink\Core\Service\ShortUrl\ShortCodeHelperInterface;
use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface;
use Shlinkio\Shlink\Core\Util\TagManagerTrait;
use Shlinkio\Shlink\Core\Util\UrlValidatorInterface;
use Throwable;
class UrlShortener implements UrlShortenerInterface
{
use TagManagerTrait;
private EntityManagerInterface $em;
private UrlValidatorInterface $urlValidator;
private ShortUrlRelationResolverInterface $relationResolver;
@ -54,7 +51,6 @@ class UrlShortener implements UrlShortenerInterface
return $this->em->transactional(function () use ($meta) {
$shortUrl = ShortUrl::fromMeta($meta, $this->relationResolver);
$shortUrl->setTags($this->tagNamesToEntities($this->em, $meta->getTags()));
$this->verifyShortCodeUniqueness($meta, $shortUrl);
$this->em->persist($shortUrl);

View file

@ -4,8 +4,13 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ShortUrl\Resolver;
use Doctrine\Common\Collections;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\EntityManagerInterface;
use Shlinkio\Shlink\Core\Entity\Domain;
use Shlinkio\Shlink\Core\Entity\Tag;
use function Functional\map;
class PersistenceShortUrlRelationResolver implements ShortUrlRelationResolverInterface
{
@ -26,4 +31,18 @@ class PersistenceShortUrlRelationResolver implements ShortUrlRelationResolverInt
$existingDomain = $this->em->getRepository(Domain::class)->findOneBy(['authority' => $domain]);
return $existingDomain ?? new Domain($domain);
}
/**
* @param string[] $tags
* @return Collection|Tag[]
*/
public function resolveTags(array $tags): Collections\Collection
{
return new Collections\ArrayCollection(map($tags, function (string $tagName): Tag {
$tag = $this->em->getRepository(Tag::class)->findOneBy(['name' => $tagName]) ?? new Tag($tagName);
$this->em->persist($tag);
return $tag;
}));
}
}

View file

@ -4,9 +4,17 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ShortUrl\Resolver;
use Doctrine\Common\Collections\Collection;
use Shlinkio\Shlink\Core\Entity\Domain;
use Shlinkio\Shlink\Core\Entity\Tag;
interface ShortUrlRelationResolverInterface
{
public function resolveDomain(?string $domain): ?Domain;
/**
* @param string[] $tags
* @return Collection|Tag[]
*/
public function resolveTags(array $tags): Collection;
}

View file

@ -4,7 +4,12 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ShortUrl\Resolver;
use Doctrine\Common\Collections;
use Doctrine\Common\Collections\Collection;
use Shlinkio\Shlink\Core\Entity\Domain;
use Shlinkio\Shlink\Core\Entity\Tag;
use function Functional\map;
class SimpleShortUrlRelationResolver implements ShortUrlRelationResolverInterface
{
@ -12,4 +17,13 @@ class SimpleShortUrlRelationResolver implements ShortUrlRelationResolverInterfac
{
return $domain !== null ? new Domain($domain) : null;
}
/**
* @param string[] $tags
* @return Collection|Tag[]
*/
public function resolveTags(array $tags): Collections\Collection
{
return new Collections\ArrayCollection(map($tags, fn (string $tag) => new Tag($tag)));
}
}

View file

@ -13,10 +13,12 @@ use function str_replace;
use function strtolower;
use function trim;
/** @deprecated */
trait TagManagerTrait
{
/**
* @param string[] $tags
* @deprecated
* @return Collections\Collection|Tag[]
*/
private function tagNamesToEntities(EntityManagerInterface $em, array $tags): Collections\Collection

View file

@ -4,6 +4,8 @@ declare(strict_types=1);
namespace ShlinkioTest\Shlink\Core\Domain\Repository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Shlinkio\Shlink\Core\Domain\Repository\DomainRepository;
use Shlinkio\Shlink\Core\Entity\Domain;
use Shlinkio\Shlink\Core\Entity\ShortUrl;
@ -102,6 +104,11 @@ class DomainRepositoryTest extends DatabaseTestCase
{
return $this->domain;
}
public function resolveTags(array $tags): Collection
{
return new ArrayCollection();
}
},
);
}

View file

@ -100,7 +100,7 @@ class ShortUrlServiceTest extends TestCase
* @test
* @dataProvider provideShortUrlEdits
*/
public function updateMetadataByShortCodeUpdatesProvidedData(
public function updateShortUrlUpdatesProvidedData(
int $expectedValidateCalls,
ShortUrlEdit $shortUrlEdit,
?ApiKey $apiKey
@ -114,7 +114,7 @@ class ShortUrlServiceTest extends TestCase
)->willReturn($shortUrl);
$flush = $this->em->flush()->willReturn(null);
$result = $this->service->updateMetadataByShortCode(new ShortUrlIdentifier('abc123'), $shortUrlEdit, $apiKey);
$result = $this->service->updateShortUrl(new ShortUrlIdentifier('abc123'), $shortUrlEdit, $apiKey);
self::assertSame($shortUrl, $result);
self::assertEquals($shortUrlEdit->validSince(), $shortUrl->getValidSince());

View file

@ -31,7 +31,7 @@ class EditShortUrlAction extends AbstractRestAction
$identifier = ShortUrlIdentifier::fromApiRequest($request);
$apiKey = AuthenticationMiddleware::apiKeyFromRequest($request);
$this->shortUrlService->updateMetadataByShortCode($identifier, $shortUrlEdit, $apiKey);
$this->shortUrlService->updateShortUrl($identifier, $shortUrlEdit, $apiKey);
return new EmptyResponse();
}
}

View file

@ -60,13 +60,23 @@ class CreateShortUrlTest extends ApiTestCase
}
}
/** @test */
public function createsNewShortUrlWithTags(): void
/**
* @test
* @dataProvider provideTags
*/
public function createsNewShortUrlWithTags(array $providedTags, array $expectedTags): void
{
[$statusCode, ['tags' => $tags]] = $this->createShortUrl(['tags' => ['foo', 'bar', 'baz']]);
[$statusCode, ['tags' => $tags]] = $this->createShortUrl(['tags' => $providedTags]);
self::assertEquals(self::STATUS_OK, $statusCode);
self::assertEquals(['foo', 'bar', 'baz'], $tags);
self::assertEquals($expectedTags, $tags);
}
public function provideTags(): iterable
{
yield 'simple tags' => [$simpleTags = ['foo', 'bar', 'baz'], $simpleTags];
yield 'tags with spaces' => [['fo o', ' bar', 'b az'], ['fo-o', 'bar', 'b-az']];
yield 'tags with special chars' => [['UUU', 'Aäa'], ['uuu', 'aäa']];
}
/**

View file

@ -48,7 +48,7 @@ class EditShortUrlActionTest extends TestCase
->withParsedBody([
'maxVisits' => 5,
]);
$updateMeta = $this->shortUrlService->updateMetadataByShortCode(Argument::cetera())->willReturn(
$updateMeta = $this->shortUrlService->updateShortUrl(Argument::cetera())->willReturn(
ShortUrl::createEmpty(),
);