mirror of
https://github.com/shlinkio/shlink.git
synced 2025-02-17 07:49:54 +03:00
Merge pull request #248 from acelaya/feature/fix-anemic-model
Feature/fix anemic model
This commit is contained in:
commit
6986d03c53
53 changed files with 241 additions and 403 deletions
|
@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||
* [#241](https://github.com/shlinkio/shlink/issues/241) Fixed columns in `visit_locations` table, to be snake_case instead of camelCase.
|
||||
* [#228](https://github.com/shlinkio/shlink/issues/228) Updated how exceptions are serialized into logs, by using monlog's `PsrLogMessageProcessor`.
|
||||
* [#225](https://github.com/shlinkio/shlink/issues/225) Performance and maintainability slightly improved by enforcing via code sniffer that all global namespace classes, functions and constants are explicitly imported.
|
||||
* [#196](https://github.com/shlinkio/shlink/issues/196) Reduced anemic model in entities, defining more expressive public APIs instead.
|
||||
|
||||
#### Deprecated
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function sprintf;
|
||||
|
||||
class ListKeysCommand extends Command
|
||||
|
@ -54,24 +55,20 @@ class ListKeysCommand extends Command
|
|||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$enabledOnly = $input->getOption('enabledOnly');
|
||||
$list = $this->apiKeyService->listKeys($enabledOnly);
|
||||
$rows = [];
|
||||
|
||||
/** @var ApiKey $row */
|
||||
foreach ($list as $row) {
|
||||
$key = $row->getKey();
|
||||
$expiration = $row->getExpirationDate();
|
||||
$messagePattern = $this->determineMessagePattern($row);
|
||||
$rows = array_map(function (ApiKey $apiKey) use ($enabledOnly) {
|
||||
$key = (string) $apiKey;
|
||||
$expiration = $apiKey->getExpirationDate();
|
||||
$messagePattern = $this->determineMessagePattern($apiKey);
|
||||
|
||||
// Set columns for this row
|
||||
$rowData = [sprintf($messagePattern, $key)];
|
||||
if (! $enabledOnly) {
|
||||
$rowData[] = sprintf($messagePattern, $this->getEnabledSymbol($row));
|
||||
$rowData[] = sprintf($messagePattern, $this->getEnabledSymbol($apiKey));
|
||||
}
|
||||
$rowData[] = $expiration !== null ? $expiration->toAtomString() : '-';
|
||||
|
||||
$rows[] = $rowData;
|
||||
}
|
||||
return $rowData;
|
||||
}, $this->apiKeyService->listKeys($enabledOnly));
|
||||
|
||||
$io->table(array_filter([
|
||||
$this->translator->translate('Key'),
|
||||
|
|
|
@ -62,7 +62,7 @@ class GeneratePreviewCommand extends Command
|
|||
$page += 1;
|
||||
|
||||
foreach ($shortUrls as $shortUrl) {
|
||||
$this->processUrl($shortUrl->getOriginalUrl(), $output);
|
||||
$this->processUrl($shortUrl->getLongUrl(), $output);
|
||||
}
|
||||
} while ($page <= $shortUrls->count());
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
|
|||
|
||||
use Cake\Chronos\Chronos;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
|
@ -13,7 +14,8 @@ use Symfony\Component\Console\Input\InputOption;
|
|||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Zend\I18n\Translator\TranslatorInterface;
|
||||
use function array_values;
|
||||
use function array_map;
|
||||
use function Shlinkio\Shlink\Common\pick;
|
||||
|
||||
class GetVisitsCommand extends Command
|
||||
{
|
||||
|
@ -87,17 +89,11 @@ class GetVisitsCommand extends Command
|
|||
$endDate = $this->getDateOption($input, 'endDate');
|
||||
|
||||
$visits = $this->visitsTracker->info($shortCode, new DateRange($startDate, $endDate));
|
||||
$rows = [];
|
||||
foreach ($visits as $row) {
|
||||
$rowData = $row->jsonSerialize();
|
||||
|
||||
// Unset location info and remote addr
|
||||
unset($rowData['visitLocation'], $rowData['remoteAddr']);
|
||||
|
||||
$rowData['country'] = $row->getVisitLocation()->getCountryName();
|
||||
|
||||
$rows[] = array_values($rowData);
|
||||
}
|
||||
$rows = array_map(function (Visit $visit) {
|
||||
$rowData = $visit->jsonSerialize();
|
||||
$rowData['country'] = $visit->getVisitLocation()->getCountryName();
|
||||
return pick($rowData, ['referer', 'date', 'userAgent', 'country']);
|
||||
}, $visits);
|
||||
$io->table([
|
||||
$this->translator->translate('Referer'),
|
||||
$this->translator->translate('Date'),
|
||||
|
|
|
@ -53,7 +53,7 @@ class ListTagsCommand extends Command
|
|||
}
|
||||
|
||||
return array_map(function (Tag $tag) {
|
||||
return [$tag->getName()];
|
||||
return [(string) $tag];
|
||||
}, $tags);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,8 +80,7 @@ class ProcessVisitsCommand extends Command
|
|||
try {
|
||||
$result = $this->ipLocationResolver->resolveIpLocation($ipAddr);
|
||||
|
||||
$location = new VisitLocation();
|
||||
$location->exchangeArray($result);
|
||||
$location = new VisitLocation($result);
|
||||
$visit->setVisitLocation($location);
|
||||
$this->visitService->saveVisit($visit);
|
||||
|
||||
|
|
|
@ -56,9 +56,9 @@ class GeneratePreviewCommandTest extends TestCase
|
|||
public function previewsForEveryUrlAreGenerated()
|
||||
{
|
||||
$paginator = $this->createPaginator([
|
||||
(new ShortUrl())->setOriginalUrl('http://foo.com'),
|
||||
(new ShortUrl())->setOriginalUrl('https://bar.com'),
|
||||
(new ShortUrl())->setOriginalUrl('http://baz.com/something'),
|
||||
new ShortUrl('http://foo.com'),
|
||||
new ShortUrl('https://bar.com'),
|
||||
new ShortUrl('http://baz.com/something'),
|
||||
]);
|
||||
$this->shortUrlService->listShortUrls(1)->willReturn($paginator)->shouldBeCalledTimes(1);
|
||||
|
||||
|
@ -77,9 +77,9 @@ class GeneratePreviewCommandTest extends TestCase
|
|||
public function exceptionWillOutputError()
|
||||
{
|
||||
$items = [
|
||||
(new ShortUrl())->setOriginalUrl('http://foo.com'),
|
||||
(new ShortUrl())->setOriginalUrl('https://bar.com'),
|
||||
(new ShortUrl())->setOriginalUrl('http://baz.com/something'),
|
||||
new ShortUrl('http://foo.com'),
|
||||
new ShortUrl('https://bar.com'),
|
||||
new ShortUrl('http://baz.com/something'),
|
||||
];
|
||||
$paginator = $this->createPaginator($items);
|
||||
$this->shortUrlService->listShortUrls(1)->willReturn($paginator)->shouldBeCalledTimes(1);
|
||||
|
|
|
@ -45,8 +45,7 @@ class GenerateShortcodeCommandTest extends TestCase
|
|||
{
|
||||
$this->urlShortener->urlToShortCode(Argument::cetera())
|
||||
->willReturn(
|
||||
(new ShortUrl())->setShortCode('abc123')
|
||||
->setLongUrl('')
|
||||
(new ShortUrl(''))->setShortCode('abc123')
|
||||
)
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
|
|
|
@ -9,8 +9,10 @@ use Prophecy\Argument;
|
|||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\ShortUrl\GetVisitsCommand;
|
||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Entity\VisitLocation;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Service\VisitsTrackerInterface;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
@ -79,9 +81,9 @@ class GetVisitsCommandTest extends TestCase
|
|||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->visitsTracker->info($shortCode, Argument::any())->willReturn([
|
||||
(new Visit())->setReferer('foo')
|
||||
->setVisitLocation((new VisitLocation())->setCountryName('Spain'))
|
||||
->setUserAgent('bar'),
|
||||
(new Visit(new ShortUrl(''), new Visitor('bar', 'foo', '')))->setVisitLocation(
|
||||
new VisitLocation(['country_name' => 'Spain'])
|
||||
),
|
||||
])->shouldBeCalledTimes(1);
|
||||
|
||||
$this->commandTester->execute([
|
||||
|
|
|
@ -55,7 +55,7 @@ class ListShortcodesCommandTest extends TestCase
|
|||
// The paginator will return more than one page for the first 3 times
|
||||
$data = [];
|
||||
for ($i = 0; $i < 50; $i++) {
|
||||
$data[] = (new ShortUrl())->setLongUrl('url_' . $i);
|
||||
$data[] = new ShortUrl('url_' . $i);
|
||||
}
|
||||
|
||||
$this->shortUrlService->listShortUrls(Argument::cetera())->will(function () use (&$data) {
|
||||
|
@ -74,7 +74,7 @@ class ListShortcodesCommandTest extends TestCase
|
|||
// The paginator will return more than one page
|
||||
$data = [];
|
||||
for ($i = 0; $i < 30; $i++) {
|
||||
$data[] = (new ShortUrl())->setLongUrl('url_' . $i);
|
||||
$data[] = new ShortUrl('url_' . $i);
|
||||
}
|
||||
|
||||
$this->shortUrlService->listShortUrls(Argument::cetera())->willReturn(new Paginator(new ArrayAdapter($data)))
|
||||
|
|
|
@ -43,7 +43,7 @@ class ResolveUrlCommandTest extends TestCase
|
|||
{
|
||||
$shortCode = 'abc123';
|
||||
$expectedUrl = 'http://domain.com/foo/bar';
|
||||
$shortUrl = (new ShortUrl())->setLongUrl($expectedUrl);
|
||||
$shortUrl = new ShortUrl($expectedUrl);
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn($shortUrl)
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
|
|
|
@ -61,8 +61,8 @@ class ListTagsCommandTest extends TestCase
|
|||
{
|
||||
/** @var MethodProphecy $listTags */
|
||||
$listTags = $this->tagService->listTags()->willReturn([
|
||||
(new Tag())->setName('foo'),
|
||||
(new Tag())->setName('bar'),
|
||||
new Tag('foo'),
|
||||
new Tag('bar'),
|
||||
]);
|
||||
|
||||
$this->commandTester->execute([]);
|
||||
|
|
|
@ -68,7 +68,7 @@ class RenameTagCommandTest extends TestCase
|
|||
$oldName = 'foo';
|
||||
$newName = 'bar';
|
||||
/** @var MethodProphecy $renameTag */
|
||||
$renameTag = $this->tagService->renameTag($oldName, $newName)->willReturn(new Tag());
|
||||
$renameTag = $this->tagService->renameTag($oldName, $newName)->willReturn(new Tag($newName));
|
||||
|
||||
$this->commandTester->execute([
|
||||
'oldName' => $oldName,
|
||||
|
|
|
@ -8,7 +8,9 @@ use Prophecy\Argument;
|
|||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\CLI\Command\Visit\ProcessVisitsCommand;
|
||||
use Shlinkio\Shlink\Common\Service\IpApiLocationResolver;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Service\VisitService;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
@ -54,10 +56,12 @@ class ProcessVisitsCommandTest extends TestCase
|
|||
*/
|
||||
public function allReturnedVisitsIpsAreProcessed()
|
||||
{
|
||||
$shortUrl = new ShortUrl('');
|
||||
|
||||
$visits = [
|
||||
(new Visit())->setRemoteAddr('1.2.3.4'),
|
||||
(new Visit())->setRemoteAddr('4.3.2.1'),
|
||||
(new Visit())->setRemoteAddr('12.34.56.78'),
|
||||
new Visit($shortUrl, new Visitor('', '', '1.2.3.4')),
|
||||
new Visit($shortUrl, new Visitor('', '', '4.3.2.1')),
|
||||
new Visit($shortUrl, new Visitor('', '', '12.34.56.78')),
|
||||
];
|
||||
$this->visitService->getUnlocatedVisits()->willReturn($visits)
|
||||
->shouldBeCalledTimes(1);
|
||||
|
@ -80,14 +84,16 @@ class ProcessVisitsCommandTest extends TestCase
|
|||
*/
|
||||
public function localhostAndEmptyAddressIsIgnored()
|
||||
{
|
||||
$shortUrl = new ShortUrl('');
|
||||
|
||||
$visits = [
|
||||
(new Visit())->setRemoteAddr('1.2.3.4'),
|
||||
(new Visit())->setRemoteAddr('4.3.2.1'),
|
||||
(new Visit())->setRemoteAddr('12.34.56.78'),
|
||||
(new Visit())->setRemoteAddr('127.0.0.1'),
|
||||
(new Visit())->setRemoteAddr('127.0.0.1'),
|
||||
(new Visit())->setRemoteAddr(''),
|
||||
(new Visit())->setRemoteAddr(null),
|
||||
new Visit($shortUrl, new Visitor('', '', '1.2.3.4')),
|
||||
new Visit($shortUrl, new Visitor('', '', '4.3.2.1')),
|
||||
new Visit($shortUrl, new Visitor('', '', '12.34.56.78')),
|
||||
new Visit($shortUrl, new Visitor('', '', '127.0.0.1')),
|
||||
new Visit($shortUrl, new Visitor('', '', '127.0.0.1')),
|
||||
new Visit($shortUrl, new Visitor('', '', '')),
|
||||
new Visit($shortUrl, new Visitor('', '', null)),
|
||||
];
|
||||
$this->visitService->getUnlocatedVisits()->willReturn($visits)
|
||||
->shouldBeCalledTimes(1);
|
||||
|
@ -109,17 +115,19 @@ class ProcessVisitsCommandTest extends TestCase
|
|||
*/
|
||||
public function sleepsEveryTimeTheApiLimitIsReached()
|
||||
{
|
||||
$shortUrl = new ShortUrl('');
|
||||
|
||||
$visits = [
|
||||
(new Visit())->setRemoteAddr('1.2.3.4'),
|
||||
(new Visit())->setRemoteAddr('4.3.2.1'),
|
||||
(new Visit())->setRemoteAddr('12.34.56.78'),
|
||||
(new Visit())->setRemoteAddr('1.2.3.4'),
|
||||
(new Visit())->setRemoteAddr('4.3.2.1'),
|
||||
(new Visit())->setRemoteAddr('12.34.56.78'),
|
||||
(new Visit())->setRemoteAddr('1.2.3.4'),
|
||||
(new Visit())->setRemoteAddr('4.3.2.1'),
|
||||
(new Visit())->setRemoteAddr('12.34.56.78'),
|
||||
(new Visit())->setRemoteAddr('4.3.2.1'),
|
||||
new Visit($shortUrl, new Visitor('', '', '1.2.3.4')),
|
||||
new Visit($shortUrl, new Visitor('', '', '4.3.2.1')),
|
||||
new Visit($shortUrl, new Visitor('', '', '12.34.56.78')),
|
||||
new Visit($shortUrl, new Visitor('', '', '1.2.3.4')),
|
||||
new Visit($shortUrl, new Visitor('', '', '4.3.2.1')),
|
||||
new Visit($shortUrl, new Visitor('', '', '12.34.56.78')),
|
||||
new Visit($shortUrl, new Visitor('', '', '1.2.3.4')),
|
||||
new Visit($shortUrl, new Visitor('', '', '4.3.2.1')),
|
||||
new Visit($shortUrl, new Visitor('', '', '12.34.56.78')),
|
||||
new Visit($shortUrl, new Visitor('', '', '4.3.2.1')),
|
||||
];
|
||||
$apiLimit = 3;
|
||||
|
||||
|
|
|
@ -3,7 +3,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace Shlinkio\Shlink\Common;
|
||||
|
||||
use const ARRAY_FILTER_USE_KEY;
|
||||
use const JSON_ERROR_NONE;
|
||||
use function array_filter;
|
||||
use function getenv;
|
||||
use function in_array;
|
||||
use function json_decode as spl_json_decode;
|
||||
|
@ -52,6 +54,19 @@ function contains($needle, array $haystack): bool
|
|||
return in_array($needle, $haystack, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns only the keys in keysToPick from provided array
|
||||
*
|
||||
* @param array $array
|
||||
* @param array $keysToPick
|
||||
*/
|
||||
function pick(array $array, array $keysToPick): array
|
||||
{
|
||||
return array_filter($array, function (string $key) use ($keysToPick) {
|
||||
return contains($key, $keysToPick);
|
||||
}, ARRAY_FILTER_USE_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception\InvalidArgumentException
|
||||
*/
|
||||
|
|
|
@ -8,6 +8,7 @@ use Doctrine\Common\Collections\ArrayCollection;
|
|||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Shlinkio\Shlink\Common\Entity\AbstractEntity;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
|
||||
use function count;
|
||||
|
||||
|
@ -25,7 +26,7 @@ class ShortUrl extends AbstractEntity
|
|||
* @var string
|
||||
* @ORM\Column(name="original_url", type="string", nullable=false, length=1024)
|
||||
*/
|
||||
private $originalUrl;
|
||||
private $longUrl;
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(
|
||||
|
@ -73,39 +74,23 @@ class ShortUrl extends AbstractEntity
|
|||
*/
|
||||
private $maxVisits;
|
||||
|
||||
public function __construct()
|
||||
public function __construct(string $longUrl, ShortUrlMeta $meta = null)
|
||||
{
|
||||
$this->shortCode = '';
|
||||
$meta = $meta ?? ShortUrlMeta::createEmpty();
|
||||
|
||||
$this->longUrl = $longUrl;
|
||||
$this->dateCreated = Chronos::now();
|
||||
$this->visits = new ArrayCollection();
|
||||
$this->tags = new ArrayCollection();
|
||||
$this->validSince = $meta->getValidSince();
|
||||
$this->validUntil = $meta->getValidUntil();
|
||||
$this->maxVisits = $meta->getMaxVisits();
|
||||
$this->shortCode = $meta->getCustomSlug() ?? ''; // TODO logic to calculate short code should be passed somehow
|
||||
}
|
||||
|
||||
public function getLongUrl(): string
|
||||
{
|
||||
return $this->originalUrl;
|
||||
}
|
||||
|
||||
public function setLongUrl(string $longUrl): self
|
||||
{
|
||||
$this->originalUrl = $longUrl;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use getLongUrl() instead
|
||||
*/
|
||||
public function getOriginalUrl(): string
|
||||
{
|
||||
return $this->getLongUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use setLongUrl() instead
|
||||
*/
|
||||
public function setOriginalUrl(string $originalUrl): self
|
||||
{
|
||||
return $this->setLongUrl($originalUrl);
|
||||
return $this->longUrl;
|
||||
}
|
||||
|
||||
public function getShortCode(): string
|
||||
|
@ -113,6 +98,7 @@ class ShortUrl extends AbstractEntity
|
|||
return $this->shortCode;
|
||||
}
|
||||
|
||||
// TODO Short code is currently calculated based on the ID, so a setter is needed
|
||||
public function setShortCode(string $shortCode): self
|
||||
{
|
||||
$this->shortCode = $shortCode;
|
||||
|
@ -124,12 +110,6 @@ class ShortUrl extends AbstractEntity
|
|||
return $this->dateCreated;
|
||||
}
|
||||
|
||||
public function setDateCreated(Chronos $dateCreated): self
|
||||
{
|
||||
$this->dateCreated = $dateCreated;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection|Tag[]
|
||||
*/
|
||||
|
@ -147,10 +127,17 @@ class ShortUrl extends AbstractEntity
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function addTag(Tag $tag): self
|
||||
public function updateMeta(ShortUrlMeta $shortCodeMeta): void
|
||||
{
|
||||
$this->tags->add($tag);
|
||||
return $this;
|
||||
if ($shortCodeMeta->hasValidSince()) {
|
||||
$this->validSince = $shortCodeMeta->getValidSince();
|
||||
}
|
||||
if ($shortCodeMeta->hasValidUntil()) {
|
||||
$this->validUntil = $shortCodeMeta->getValidUntil();
|
||||
}
|
||||
if ($shortCodeMeta->hasMaxVisits()) {
|
||||
$this->maxVisits = $shortCodeMeta->getMaxVisits();
|
||||
}
|
||||
}
|
||||
|
||||
public function getValidSince(): ?Chronos
|
||||
|
@ -158,23 +145,11 @@ class ShortUrl extends AbstractEntity
|
|||
return $this->validSince;
|
||||
}
|
||||
|
||||
public function setValidSince(?Chronos $validSince): self
|
||||
{
|
||||
$this->validSince = $validSince;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getValidUntil(): ?Chronos
|
||||
{
|
||||
return $this->validUntil;
|
||||
}
|
||||
|
||||
public function setValidUntil(?Chronos $validUntil): self
|
||||
{
|
||||
$this->validUntil = $validUntil;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getVisitsCount(): int
|
||||
{
|
||||
return count($this->visits);
|
||||
|
@ -196,12 +171,6 @@ class ShortUrl extends AbstractEntity
|
|||
return $this->maxVisits;
|
||||
}
|
||||
|
||||
public function setMaxVisits(?int $maxVisits): self
|
||||
{
|
||||
$this->maxVisits = $maxVisits;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function maxVisitsReached(): bool
|
||||
{
|
||||
return $this->maxVisits !== null && $this->getVisitsCount() >= $this->maxVisits;
|
||||
|
|
|
@ -24,24 +24,23 @@ class Tag extends AbstractEntity implements JsonSerializable
|
|||
*/
|
||||
private $name;
|
||||
|
||||
public function __construct($name = null)
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name)
|
||||
public function rename(string $name): void
|
||||
{
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ use JsonSerializable;
|
|||
use Shlinkio\Shlink\Common\Entity\AbstractEntity;
|
||||
use Shlinkio\Shlink\Common\Exception\WrongIpException;
|
||||
use Shlinkio\Shlink\Common\Util\IpAddress;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Repository\VisitRepository;
|
||||
|
||||
/**
|
||||
|
@ -54,58 +55,13 @@ class Visit extends AbstractEntity implements JsonSerializable
|
|||
*/
|
||||
private $visitLocation;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->date = Chronos::now();
|
||||
}
|
||||
|
||||
public function getReferer(): string
|
||||
{
|
||||
return $this->referer;
|
||||
}
|
||||
|
||||
public function setReferer(string $referer): self
|
||||
{
|
||||
$this->referer = $referer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDate(): Chronos
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
public function setDate(Chronos $date): self
|
||||
{
|
||||
$this->date = $date;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getShortUrl(): ShortUrl
|
||||
{
|
||||
return $this->shortUrl;
|
||||
}
|
||||
|
||||
public function setShortUrl(ShortUrl $shortUrl): self
|
||||
public function __construct(ShortUrl $shortUrl, Visitor $visitor, ?Chronos $date = null)
|
||||
{
|
||||
$this->shortUrl = $shortUrl;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRemoteAddr(): ?string
|
||||
{
|
||||
return $this->remoteAddr;
|
||||
}
|
||||
|
||||
public function setRemoteAddr(?string $remoteAddr): self
|
||||
{
|
||||
$this->remoteAddr = $this->obfuscateAddress($remoteAddr);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasRemoteAddr(): bool
|
||||
{
|
||||
return ! empty($this->remoteAddr);
|
||||
$this->date = $date ?? Chronos::now();
|
||||
$this->userAgent = $visitor->getUserAgent();
|
||||
$this->referer = $visitor->getReferer();
|
||||
$this->remoteAddr = $this->obfuscateAddress($visitor->getRemoteAddress());
|
||||
}
|
||||
|
||||
private function obfuscateAddress(?string $address): ?string
|
||||
|
@ -122,15 +78,14 @@ class Visit extends AbstractEntity implements JsonSerializable
|
|||
}
|
||||
}
|
||||
|
||||
public function getUserAgent(): string
|
||||
public function getRemoteAddr(): ?string
|
||||
{
|
||||
return $this->userAgent;
|
||||
return $this->remoteAddr;
|
||||
}
|
||||
|
||||
public function setUserAgent(string $userAgent): self
|
||||
public function hasRemoteAddr(): bool
|
||||
{
|
||||
$this->userAgent = $userAgent;
|
||||
return $this;
|
||||
return ! empty($this->remoteAddr);
|
||||
}
|
||||
|
||||
public function getVisitLocation(): VisitLocation
|
||||
|
|
|
@ -6,7 +6,6 @@ namespace Shlinkio\Shlink\Core\Entity;
|
|||
use Doctrine\ORM\Mapping as ORM;
|
||||
use JsonSerializable;
|
||||
use Shlinkio\Shlink\Common\Entity\AbstractEntity;
|
||||
use Zend\Stdlib\ArraySerializableInterface;
|
||||
use function array_key_exists;
|
||||
|
||||
/**
|
||||
|
@ -17,7 +16,7 @@ use function array_key_exists;
|
|||
* @ORM\Entity()
|
||||
* @ORM\Table(name="visit_locations")
|
||||
*/
|
||||
class VisitLocation extends AbstractEntity implements ArraySerializableInterface, JsonSerializable
|
||||
class VisitLocation extends AbstractEntity implements JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
|
@ -55,15 +54,9 @@ class VisitLocation extends AbstractEntity implements ArraySerializableInterface
|
|||
*/
|
||||
private $timezone;
|
||||
|
||||
public function getCountryCode(): string
|
||||
public function __construct(array $locationInfo)
|
||||
{
|
||||
return $this->countryCode ?? '';
|
||||
}
|
||||
|
||||
public function setCountryCode(string $countryCode)
|
||||
{
|
||||
$this->countryCode = $countryCode;
|
||||
return $this;
|
||||
$this->exchangeArray($locationInfo);
|
||||
}
|
||||
|
||||
public function getCountryName(): string
|
||||
|
@ -71,99 +64,50 @@ class VisitLocation extends AbstractEntity implements ArraySerializableInterface
|
|||
return $this->countryName ?? '';
|
||||
}
|
||||
|
||||
public function setCountryName(string $countryName): self
|
||||
{
|
||||
$this->countryName = $countryName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRegionName(): string
|
||||
{
|
||||
return $this->regionName ?? '';
|
||||
}
|
||||
|
||||
public function setRegionName(string $regionName): self
|
||||
{
|
||||
$this->regionName = $regionName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCityName(): string
|
||||
{
|
||||
return $this->cityName ?? '';
|
||||
}
|
||||
|
||||
public function setCityName(string $cityName): self
|
||||
{
|
||||
$this->cityName = $cityName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLatitude(): string
|
||||
{
|
||||
return $this->latitude ?? '';
|
||||
}
|
||||
|
||||
public function setLatitude(string $latitude): self
|
||||
{
|
||||
$this->latitude = $latitude;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLongitude(): string
|
||||
{
|
||||
return $this->longitude ?? '';
|
||||
}
|
||||
|
||||
public function setLongitude(string $longitude): self
|
||||
public function getCityName(): string
|
||||
{
|
||||
$this->longitude = $longitude;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTimezone(): string
|
||||
{
|
||||
return $this->timezone ?? '';
|
||||
}
|
||||
|
||||
public function setTimezone(string $timezone): self
|
||||
{
|
||||
$this->timezone = $timezone;
|
||||
return $this;
|
||||
return $this->cityName ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchange internal values from provided array
|
||||
*/
|
||||
public function exchangeArray(array $array): void
|
||||
private function exchangeArray(array $array): void
|
||||
{
|
||||
if (array_key_exists('country_code', $array)) {
|
||||
$this->setCountryCode((string) $array['country_code']);
|
||||
$this->countryCode = (string) $array['country_code'];
|
||||
}
|
||||
if (array_key_exists('country_name', $array)) {
|
||||
$this->setCountryName((string) $array['country_name']);
|
||||
$this->countryName = (string) $array['country_name'];
|
||||
}
|
||||
if (array_key_exists('region_name', $array)) {
|
||||
$this->setRegionName((string) $array['region_name']);
|
||||
$this->regionName = (string) $array['region_name'];
|
||||
}
|
||||
if (array_key_exists('city', $array)) {
|
||||
$this->setCityName((string) $array['city']);
|
||||
$this->cityName = (string) $array['city'];
|
||||
}
|
||||
if (array_key_exists('latitude', $array)) {
|
||||
$this->setLatitude((string) $array['latitude']);
|
||||
$this->latitude = (string) $array['latitude'];
|
||||
}
|
||||
if (array_key_exists('longitude', $array)) {
|
||||
$this->setLongitude((string) $array['longitude']);
|
||||
$this->longitude = (string) $array['longitude'];
|
||||
}
|
||||
if (array_key_exists('time_zone', $array)) {
|
||||
$this->setTimezone((string) $array['time_zone']);
|
||||
$this->timezone = (string) $array['time_zone'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array representation of the object
|
||||
*/
|
||||
public function getArrayCopy(): array
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'countryCode' => $this->countryCode,
|
||||
|
@ -175,9 +119,4 @@ class VisitLocation extends AbstractEntity implements ArraySerializableInterface
|
|||
'timezone' => $this->timezone,
|
||||
];
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->getArrayCopy();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,9 +32,13 @@ final class ShortUrlMeta
|
|||
{
|
||||
}
|
||||
|
||||
public static function createEmpty(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return ShortUrlMeta
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public static function createFromRawData(array $data): self
|
||||
|
@ -49,7 +53,6 @@ final class ShortUrlMeta
|
|||
* @param string|Chronos|null $validUntil
|
||||
* @param string|null $customSlug
|
||||
* @param int|null $maxVisits
|
||||
* @return ShortUrlMeta
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public static function createFromParams(
|
||||
|
@ -124,10 +127,7 @@ final class ShortUrlMeta
|
|||
return $this->validUntil !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
*/
|
||||
public function getCustomSlug()
|
||||
public function getCustomSlug(): ?string
|
||||
{
|
||||
return $this->customSlug;
|
||||
}
|
||||
|
@ -137,10 +137,7 @@ final class ShortUrlMeta
|
|||
return $this->customSlug !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getMaxVisits()
|
||||
public function getMaxVisits(): ?int
|
||||
{
|
||||
return $this->maxVisits;
|
||||
}
|
||||
|
|
|
@ -55,8 +55,8 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
|
|||
{
|
||||
// Map public field names to column names
|
||||
$fieldNameMap = [
|
||||
'originalUrl' => 'originalUrl',
|
||||
'longUrl' => 'originalUrl',
|
||||
'originalUrl' => 'longUrl',
|
||||
'longUrl' => 'longUrl',
|
||||
'shortCode' => 'shortCode',
|
||||
'dateCreated' => 'dateCreated',
|
||||
];
|
||||
|
@ -112,7 +112,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
|
|||
}
|
||||
|
||||
$conditions = [
|
||||
$qb->expr()->like('s.originalUrl', ':searchPattern'),
|
||||
$qb->expr()->like('s.longUrl', ':searchPattern'),
|
||||
$qb->expr()->like('s.shortCode', ':searchPattern'),
|
||||
$qb->expr()->like('t.name', ':searchPattern'),
|
||||
];
|
||||
|
|
|
@ -60,18 +60,10 @@ class ShortUrlService implements ShortUrlServiceInterface
|
|||
/**
|
||||
* @throws InvalidShortCodeException
|
||||
*/
|
||||
public function updateMetadataByShortCode(string $shortCode, ShortUrlMeta $shortCodeMeta): ShortUrl
|
||||
public function updateMetadataByShortCode(string $shortCode, ShortUrlMeta $shortUrlMeta): ShortUrl
|
||||
{
|
||||
$shortUrl = $this->findByShortCode($this->em, $shortCode);
|
||||
if ($shortCodeMeta->hasValidSince()) {
|
||||
$shortUrl->setValidSince($shortCodeMeta->getValidSince());
|
||||
}
|
||||
if ($shortCodeMeta->hasValidUntil()) {
|
||||
$shortUrl->setValidUntil($shortCodeMeta->getValidUntil());
|
||||
}
|
||||
if ($shortCodeMeta->hasMaxVisits()) {
|
||||
$shortUrl->setMaxVisits($shortCodeMeta->getMaxVisits());
|
||||
}
|
||||
$shortUrl->updateMeta($shortUrlMeta);
|
||||
|
||||
/** @var ORM\EntityManager $em */
|
||||
$em = $this->em;
|
||||
|
|
|
@ -26,5 +26,5 @@ interface ShortUrlServiceInterface
|
|||
/**
|
||||
* @throws InvalidShortCodeException
|
||||
*/
|
||||
public function updateMetadataByShortCode(string $shortCode, ShortUrlMeta $shortCodeMeta): ShortUrl;
|
||||
public function updateMetadataByShortCode(string $shortCode, ShortUrlMeta $shortUrlMeta): ShortUrl;
|
||||
}
|
||||
|
|
|
@ -76,7 +76,7 @@ class TagService implements TagServiceInterface
|
|||
throw EntityDoesNotExistException::createFromEntityAndConditions(Tag::class, $criteria);
|
||||
}
|
||||
|
||||
$tag->setName($newName);
|
||||
$tag->rename($newName);
|
||||
|
||||
/** @var ORM\EntityManager $em */
|
||||
$em = $this->em;
|
||||
|
|
|
@ -16,6 +16,7 @@ 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;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
|
||||
use Shlinkio\Shlink\Core\Util\TagManagerTrait;
|
||||
use Throwable;
|
||||
|
@ -90,15 +91,15 @@ class UrlShortener implements UrlShortenerInterface
|
|||
$this->em->beginTransaction();
|
||||
|
||||
// First, create the short URL with an empty short code
|
||||
$shortUrl = new ShortUrl();
|
||||
$shortUrl->setOriginalUrl((string) $url)
|
||||
->setValidSince($validSince)
|
||||
->setValidUntil($validUntil)
|
||||
->setMaxVisits($maxVisits);
|
||||
$shortUrl = new ShortUrl(
|
||||
(string) $url,
|
||||
ShortUrlMeta::createFromParams($validSince, $validUntil, null, $maxVisits)
|
||||
);
|
||||
$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));
|
||||
|
|
|
@ -34,11 +34,7 @@ class VisitsTracker implements VisitsTrackerInterface
|
|||
'shortCode' => $shortCode,
|
||||
]);
|
||||
|
||||
$visit = new Visit();
|
||||
$visit->setShortUrl($shortUrl)
|
||||
->setUserAgent($visitor->getUserAgent())
|
||||
->setReferer($visitor->getReferer())
|
||||
->setRemoteAddr($visitor->getRemoteAddress());
|
||||
$visit = new Visit($shortUrl, $visitor);
|
||||
|
||||
/** @var ORM\EntityManager $em */
|
||||
$em = $this->em;
|
||||
|
|
|
@ -47,6 +47,6 @@ class ShortUrlDataTransformer implements DataTransformerInterface
|
|||
|
||||
private function serializeTag(Tag $tag): string
|
||||
{
|
||||
return $tag->getName();
|
||||
return (string) $tag;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ trait TagManagerTrait
|
|||
$entities = [];
|
||||
foreach ($tags as $tagName) {
|
||||
$tagName = $this->normalizeTagName($tagName);
|
||||
$tag = $em->getRepository(Tag::class)->findOneBy(['name' => $tagName]) ?: (new Tag())->setName($tagName);
|
||||
$tag = $em->getRepository(Tag::class)->findOneBy(['name' => $tagName]) ?: new Tag($tagName);
|
||||
$em->persist($tag);
|
||||
$entities[] = $tag;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ use Doctrine\Common\Collections\ArrayCollection;
|
|||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Tag;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
|
||||
use ShlinkioTest\Shlink\Common\DbUnit\DatabaseTestCase;
|
||||
use function count;
|
||||
|
@ -35,28 +37,23 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
|
|||
*/
|
||||
public function findOneByShortCodeReturnsProperData()
|
||||
{
|
||||
$foo = new ShortUrl();
|
||||
$foo->setOriginalUrl('foo')
|
||||
->setShortCode('foo');
|
||||
$foo = new ShortUrl('foo');
|
||||
$foo->setShortCode('foo');
|
||||
$this->getEntityManager()->persist($foo);
|
||||
|
||||
$bar = new ShortUrl();
|
||||
$bar->setOriginalUrl('bar')
|
||||
->setShortCode('bar_very_long_text')
|
||||
->setValidSince(Chronos::now()->addMonth());
|
||||
$bar = new ShortUrl('bar', ShortUrlMeta::createFromParams(Chronos::now()->addMonth()));
|
||||
$bar->setShortCode('bar_very_long_text');
|
||||
$this->getEntityManager()->persist($bar);
|
||||
|
||||
$baz = new ShortUrl('baz', ShortUrlMeta::createFromRawData(['maxVisits' => 3]));
|
||||
$visits = [];
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$visit = new Visit();
|
||||
$visit = new Visit($baz, Visitor::emptyInstance());
|
||||
$this->getEntityManager()->persist($visit);
|
||||
$visits[] = $visit;
|
||||
}
|
||||
$baz = new ShortUrl();
|
||||
$baz->setOriginalUrl('baz')
|
||||
->setShortCode('baz')
|
||||
->setVisits(new ArrayCollection($visits))
|
||||
->setMaxVisits(3);
|
||||
$baz->setShortCode('baz')
|
||||
->setVisits(new ArrayCollection($visits));
|
||||
$this->getEntityManager()->persist($baz);
|
||||
|
||||
$this->getEntityManager()->flush();
|
||||
|
@ -75,8 +72,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
|
|||
$count = 5;
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$this->getEntityManager()->persist(
|
||||
(new ShortUrl())->setOriginalUrl((string) $i)
|
||||
->setShortCode((string) $i)
|
||||
(new ShortUrl((string) $i))->setShortCode((string) $i)
|
||||
);
|
||||
}
|
||||
$this->getEntityManager()->flush();
|
||||
|
@ -92,20 +88,17 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
|
|||
$tag = new Tag('bar');
|
||||
$this->getEntityManager()->persist($tag);
|
||||
|
||||
$foo = new ShortUrl();
|
||||
$foo->setOriginalUrl('foo')
|
||||
->setShortCode('foo')
|
||||
$foo = new ShortUrl('foo');
|
||||
$foo->setShortCode('foo')
|
||||
->setTags(new ArrayCollection([$tag]));
|
||||
$this->getEntityManager()->persist($foo);
|
||||
|
||||
$bar = new ShortUrl();
|
||||
$bar->setOriginalUrl('bar')
|
||||
->setShortCode('bar_very_long_text');
|
||||
$bar = new ShortUrl('bar');
|
||||
$bar->setShortCode('bar_very_long_text');
|
||||
$this->getEntityManager()->persist($bar);
|
||||
|
||||
$foo2 = new ShortUrl();
|
||||
$foo2->setOriginalUrl('foo_2')
|
||||
->setShortCode('foo_2');
|
||||
$foo2 = new ShortUrl('foo_2');
|
||||
$foo2->setShortCode('foo_2');
|
||||
$this->getEntityManager()->persist($foo2);
|
||||
|
||||
$this->getEntityManager()->flush();
|
||||
|
@ -123,8 +116,7 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
|
|||
$urls = ['a', 'z', 'c', 'b'];
|
||||
foreach ($urls as $url) {
|
||||
$this->getEntityManager()->persist(
|
||||
(new ShortUrl())->setShortCode($url)
|
||||
->setLongUrl($url)
|
||||
(new ShortUrl($url))->setShortCode($url)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ use Shlinkio\Shlink\Common\Util\DateRange;
|
|||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Entity\VisitLocation;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Repository\VisitRepository;
|
||||
use ShlinkioTest\Shlink\Common\DbUnit\DatabaseTestCase;
|
||||
use function sprintf;
|
||||
|
@ -35,11 +36,14 @@ class VisitRepositoryTest extends DatabaseTestCase
|
|||
*/
|
||||
public function findUnlocatedVisitsReturnsProperVisits()
|
||||
{
|
||||
$shortUrl = new ShortUrl('');
|
||||
$this->getEntityManager()->persist($shortUrl);
|
||||
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
$visit = new Visit();
|
||||
$visit = new Visit($shortUrl, Visitor::emptyInstance());
|
||||
|
||||
if ($i % 2 === 0) {
|
||||
$location = new VisitLocation();
|
||||
$location = new VisitLocation([]);
|
||||
$this->getEntityManager()->persist($location);
|
||||
$visit->setVisitLocation($location);
|
||||
}
|
||||
|
@ -56,15 +60,11 @@ class VisitRepositoryTest extends DatabaseTestCase
|
|||
*/
|
||||
public function findVisitsByShortUrlReturnsProperData()
|
||||
{
|
||||
$shortUrl = new ShortUrl();
|
||||
$shortUrl->setOriginalUrl('');
|
||||
$shortUrl = new ShortUrl('');
|
||||
$this->getEntityManager()->persist($shortUrl);
|
||||
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
$visit = new Visit();
|
||||
$visit->setShortUrl($shortUrl)
|
||||
->setDate(Chronos::parse(sprintf('2016-01-0%s', $i + 1)));
|
||||
|
||||
$visit = new Visit($shortUrl, Visitor::emptyInstance(), Chronos::parse(sprintf('2016-01-0%s', $i + 1)));
|
||||
$this->getEntityManager()->persist($visit);
|
||||
}
|
||||
$this->getEntityManager()->flush();
|
||||
|
|
|
@ -50,7 +50,7 @@ class PixelActionTest extends TestCase
|
|||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn(
|
||||
(new ShortUrl())->setLongUrl('http://domain.com/foo/bar')
|
||||
new ShortUrl('http://domain.com/foo/bar')
|
||||
)->shouldBeCalledTimes(1);
|
||||
$this->visitTracker->track(Argument::cetera())->shouldBeCalledTimes(1);
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ class PreviewActionTest extends TestCase
|
|||
{
|
||||
$shortCode = 'abc123';
|
||||
$url = 'foobar.com';
|
||||
$shortUrl = (new ShortUrl())->setLongUrl($url);
|
||||
$shortUrl = new ShortUrl($url);
|
||||
$path = __FILE__;
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn($shortUrl)->shouldBeCalledTimes(1);
|
||||
$this->previewGenerator->generatePreview($url)->willReturn($path)->shouldBeCalledTimes(1);
|
||||
|
|
|
@ -84,7 +84,7 @@ class QrCodeActionTest extends TestCase
|
|||
public function aCorrectRequestReturnsTheQrCodeResponse()
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn((new ShortUrl())->setLongUrl(''))
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn(new ShortUrl(''))
|
||||
->shouldBeCalledTimes(1);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ class RedirectActionTest extends TestCase
|
|||
{
|
||||
$shortCode = 'abc123';
|
||||
$expectedUrl = 'http://domain.com/foo/bar';
|
||||
$shortUrl = (new ShortUrl())->setLongUrl($expectedUrl);
|
||||
$shortUrl = new ShortUrl($expectedUrl);
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn($shortUrl)
|
||||
->shouldBeCalledTimes(1);
|
||||
$this->visitTracker->track(Argument::cetera())->shouldBeCalledTimes(1);
|
||||
|
@ -93,7 +93,7 @@ class RedirectActionTest extends TestCase
|
|||
{
|
||||
$shortCode = 'abc123';
|
||||
$expectedUrl = 'http://domain.com/foo/bar';
|
||||
$shortUrl = (new ShortUrl())->setLongUrl($expectedUrl);
|
||||
$shortUrl = new ShortUrl($expectedUrl);
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn($shortUrl)
|
||||
->shouldBeCalledTimes(1);
|
||||
$this->visitTracker->track(Argument::cetera())->shouldNotBeCalled();
|
||||
|
|
|
@ -11,10 +11,9 @@ class TagTest extends TestCase
|
|||
/**
|
||||
* @test
|
||||
*/
|
||||
public function jsonSerializationOfTagsReturnsItsName()
|
||||
public function jsonSerializationOfTagsReturnsItsStringRepresentation()
|
||||
{
|
||||
$tag = new Tag();
|
||||
$tag->setName('This is my name');
|
||||
$this->assertEquals($tag->getName(), $tag->jsonSerialize());
|
||||
$tag = new Tag('This is my name');
|
||||
$this->assertEquals((string) $tag, $tag->jsonSerialize());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,7 @@ class VisitLocationTest extends TestCase
|
|||
'longitude' => -2000.4,
|
||||
];
|
||||
|
||||
$location = new VisitLocation();
|
||||
$location->exchangeArray($payload);
|
||||
$location = new VisitLocation($payload);
|
||||
|
||||
$this->assertSame('1000.7', $location->getLatitude());
|
||||
$this->assertSame('-2000.4', $location->getLongitude());
|
||||
|
|
|
@ -11,6 +11,7 @@ use Prophecy\Prophecy\ObjectProphecy;
|
|||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Exception\DeleteShortUrlException;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Options\DeleteShortUrlsOptions;
|
||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
|
||||
use Shlinkio\Shlink\Core\Service\ShortUrl\DeleteShortUrlService;
|
||||
|
@ -30,10 +31,10 @@ class DeleteShortUrlServiceTest extends TestCase
|
|||
|
||||
public function setUp()
|
||||
{
|
||||
$shortUrl = (new ShortUrl())->setShortCode('abc123')
|
||||
->setVisits(new ArrayCollection(array_map(function () {
|
||||
return new Visit();
|
||||
}, range(0, 10))));
|
||||
$shortUrl = (new ShortUrl(''))->setShortCode('abc123')
|
||||
->setVisits(new ArrayCollection(array_map(function () {
|
||||
return new Visit(new ShortUrl(''), Visitor::emptyInstance());
|
||||
}, range(0, 10))));
|
||||
|
||||
$this->em = $this->prophesize(EntityManagerInterface::class);
|
||||
|
||||
|
|
|
@ -42,10 +42,10 @@ class ShortUrlServiceTest extends TestCase
|
|||
public function listedUrlsAreReturnedFromEntityManager()
|
||||
{
|
||||
$list = [
|
||||
new ShortUrl(),
|
||||
new ShortUrl(),
|
||||
new ShortUrl(),
|
||||
new ShortUrl(),
|
||||
new ShortUrl(''),
|
||||
new ShortUrl(''),
|
||||
new ShortUrl(''),
|
||||
new ShortUrl(''),
|
||||
];
|
||||
|
||||
$repo = $this->prophesize(ShortUrlRepository::class);
|
||||
|
@ -86,7 +86,7 @@ class ShortUrlServiceTest extends TestCase
|
|||
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
|
||||
|
||||
$tagRepo = $this->prophesize(EntityRepository::class);
|
||||
$tagRepo->findOneBy(['name' => 'foo'])->willReturn(new Tag())->shouldbeCalledTimes(1);
|
||||
$tagRepo->findOneBy(['name' => 'foo'])->willReturn(new Tag('foo'))->shouldbeCalledTimes(1);
|
||||
$tagRepo->findOneBy(['name' => 'bar'])->willReturn(null)->shouldbeCalledTimes(1);
|
||||
$this->em->getRepository(Tag::class)->willReturn($tagRepo->reveal());
|
||||
|
||||
|
@ -98,7 +98,7 @@ class ShortUrlServiceTest extends TestCase
|
|||
*/
|
||||
public function updateMetadataByShortCodeUpdatesProvidedData()
|
||||
{
|
||||
$shortUrl = new ShortUrl();
|
||||
$shortUrl = new ShortUrl('');
|
||||
|
||||
$repo = $this->prophesize(ShortUrlRepository::class);
|
||||
$findShortUrl = $repo->findOneBy(['shortCode' => 'abc123'])->willReturn($shortUrl);
|
||||
|
|
|
@ -36,7 +36,7 @@ class TagServiceTest extends TestCase
|
|||
*/
|
||||
public function listTagsDelegatesOnRepository()
|
||||
{
|
||||
$expected = [new Tag(), new Tag()];
|
||||
$expected = [new Tag('foo'), new Tag('bar')];
|
||||
|
||||
$repo = $this->prophesize(EntityRepository::class);
|
||||
/** @var MethodProphecy $find */
|
||||
|
@ -75,7 +75,7 @@ class TagServiceTest extends TestCase
|
|||
{
|
||||
$repo = $this->prophesize(TagRepository::class);
|
||||
/** @var MethodProphecy $find */
|
||||
$find = $repo->findOneBy(Argument::cetera())->willReturn(new Tag());
|
||||
$find = $repo->findOneBy(Argument::cetera())->willReturn(new Tag('foo'));
|
||||
/** @var MethodProphecy $getRepo */
|
||||
$getRepo = $this->em->getRepository(Tag::class)->willReturn($repo->reveal());
|
||||
/** @var MethodProphecy $persist */
|
||||
|
@ -115,7 +115,7 @@ class TagServiceTest extends TestCase
|
|||
*/
|
||||
public function renameValidTagChangesItsName()
|
||||
{
|
||||
$expected = new Tag();
|
||||
$expected = new Tag('foo');
|
||||
|
||||
$repo = $this->prophesize(TagRepository::class);
|
||||
/** @var MethodProphecy $find */
|
||||
|
@ -128,7 +128,7 @@ class TagServiceTest extends TestCase
|
|||
$tag = $this->service->renameTag('foo', 'bar');
|
||||
|
||||
$this->assertSame($expected, $tag);
|
||||
$this->assertEquals('bar', $tag->getName());
|
||||
$this->assertEquals('bar', (string) $tag);
|
||||
$find->shouldHaveBeenCalled();
|
||||
$getRepo->shouldHaveBeenCalled();
|
||||
$flush->shouldHaveBeenCalled();
|
||||
|
|
|
@ -148,7 +148,7 @@ class UrlShortenerTest extends TestCase
|
|||
|
||||
$repo = $this->prophesize(ShortUrlRepositoryInterface::class);
|
||||
/** @var MethodProphecy $findBySlug */
|
||||
$findBySlug = $repo->findOneBy(['shortCode' => 'custom-slug'])->willReturn(new ShortUrl());
|
||||
$findBySlug = $repo->findOneBy(['shortCode' => 'custom-slug'])->willReturn(new ShortUrl(''));
|
||||
$repo->findOneBy(Argument::cetera())->willReturn(null);
|
||||
/** @var MethodProphecy $getRepo */
|
||||
$getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
|
||||
|
@ -174,9 +174,8 @@ class UrlShortenerTest extends TestCase
|
|||
{
|
||||
// 12C1c -> 10
|
||||
$shortCode = '12C1c';
|
||||
$shortUrl = new ShortUrl();
|
||||
$shortUrl->setShortCode($shortCode)
|
||||
->setOriginalUrl('expected_url');
|
||||
$shortUrl = new ShortUrl('expected_url');
|
||||
$shortUrl->setShortCode($shortCode);
|
||||
|
||||
$repo = $this->prophesize(ShortUrlRepositoryInterface::class);
|
||||
$repo->findOneByShortCode($shortCode)->willReturn($shortUrl);
|
||||
|
|
|
@ -6,7 +6,9 @@ namespace ShlinkioTest\Shlink\Core\Service;
|
|||
use Doctrine\ORM\EntityManager;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\Prophecy\ObjectProphecy;
|
||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||
use Shlinkio\Shlink\Core\Repository\VisitRepository;
|
||||
use Shlinkio\Shlink\Core\Service\VisitService;
|
||||
|
||||
|
@ -32,7 +34,7 @@ class VisitServiceTest extends TestCase
|
|||
*/
|
||||
public function saveVisitsPersistsProvidedVisit()
|
||||
{
|
||||
$visit = new Visit();
|
||||
$visit = new Visit(new ShortUrl(''), Visitor::emptyInstance());
|
||||
$this->em->persist($visit)->shouldBeCalledTimes(1);
|
||||
$this->em->flush()->shouldBeCalledTimes(1);
|
||||
$this->visitService->saveVisit($visit);
|
||||
|
|
|
@ -38,7 +38,7 @@ class VisitsTrackerTest extends TestCase
|
|||
{
|
||||
$shortCode = '123ABC';
|
||||
$repo = $this->prophesize(EntityRepository::class);
|
||||
$repo->findOneBy(['shortCode' => $shortCode])->willReturn(new ShortUrl());
|
||||
$repo->findOneBy(['shortCode' => $shortCode])->willReturn(new ShortUrl(''));
|
||||
|
||||
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal())->shouldBeCalledTimes(1);
|
||||
$this->em->persist(Argument::any())->shouldBeCalledTimes(1);
|
||||
|
@ -55,7 +55,7 @@ class VisitsTrackerTest extends TestCase
|
|||
$shortCode = '123ABC';
|
||||
$test = $this;
|
||||
$repo = $this->prophesize(EntityRepository::class);
|
||||
$repo->findOneBy(['shortCode' => $shortCode])->willReturn(new ShortUrl());
|
||||
$repo->findOneBy(['shortCode' => $shortCode])->willReturn(new ShortUrl(''));
|
||||
|
||||
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal())->shouldBeCalledTimes(1);
|
||||
$this->em->persist(Argument::any())->will(function ($args) use ($test) {
|
||||
|
@ -74,14 +74,14 @@ class VisitsTrackerTest extends TestCase
|
|||
public function infoReturnsVisistForCertainShortCode()
|
||||
{
|
||||
$shortCode = '123ABC';
|
||||
$shortUrl = (new ShortUrl())->setOriginalUrl('http://domain.com/foo/bar');
|
||||
$shortUrl = new ShortUrl('http://domain.com/foo/bar');
|
||||
$repo = $this->prophesize(EntityRepository::class);
|
||||
$repo->findOneBy(['shortCode' => $shortCode])->willReturn($shortUrl);
|
||||
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal())->shouldBeCalledTimes(1);
|
||||
|
||||
$list = [
|
||||
new Visit(),
|
||||
new Visit(),
|
||||
new Visit(new ShortUrl(''), Visitor::emptyInstance()),
|
||||
new Visit(new ShortUrl(''), Visitor::emptyInstance()),
|
||||
];
|
||||
$repo2 = $this->prophesize(VisitRepository::class);
|
||||
$repo2->findVisitsByShortUrl($shortUrl, null)->willReturn($list);
|
||||
|
|
|
@ -36,21 +36,11 @@ class ApiKey extends AbstractEntity
|
|||
*/
|
||||
private $enabled;
|
||||
|
||||
public function __construct()
|
||||
public function __construct(?Chronos $expirationDate = null)
|
||||
{
|
||||
$this->enabled = true;
|
||||
$this->key = $this->generateV4Uuid();
|
||||
}
|
||||
|
||||
public function getKey(): string
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function setKey(string $key): self
|
||||
{
|
||||
$this->key = $key;
|
||||
return $this;
|
||||
$this->expirationDate = $expirationDate;
|
||||
$this->enabled = true;
|
||||
}
|
||||
|
||||
public function getExpirationDate(): ?Chronos
|
||||
|
@ -58,12 +48,6 @@ class ApiKey extends AbstractEntity
|
|||
return $this->expirationDate;
|
||||
}
|
||||
|
||||
public function setExpirationDate(Chronos $expirationDate): self
|
||||
{
|
||||
$this->expirationDate = $expirationDate;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isExpired(): bool
|
||||
{
|
||||
if ($this->expirationDate === null) {
|
||||
|
@ -78,15 +62,10 @@ class ApiKey extends AbstractEntity
|
|||
return $this->enabled;
|
||||
}
|
||||
|
||||
public function setEnabled(bool $enabled): self
|
||||
{
|
||||
$this->enabled = $enabled;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function disable(): self
|
||||
{
|
||||
return $this->setEnabled(false);
|
||||
$this->enabled = false;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,6 +78,6 @@ class ApiKey extends AbstractEntity
|
|||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->getKey();
|
||||
return $this->key;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,11 +23,7 @@ class ApiKeyService implements ApiKeyServiceInterface
|
|||
|
||||
public function create(?Chronos $expirationDate = null): ApiKey
|
||||
{
|
||||
$key = new ApiKey();
|
||||
if ($expirationDate !== null) {
|
||||
$key->setExpirationDate($expirationDate);
|
||||
}
|
||||
|
||||
$key = new ApiKey($expirationDate);
|
||||
$this->em->persist($key);
|
||||
$this->em->flush();
|
||||
|
||||
|
@ -57,6 +53,9 @@ class ApiKeyService implements ApiKeyServiceInterface
|
|||
return $apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ApiKey[]
|
||||
*/
|
||||
public function listKeys(bool $enabledOnly = false): array
|
||||
{
|
||||
$conditions = $enabledOnly ? ['enabled' => true] : [];
|
||||
|
|
|
@ -18,6 +18,9 @@ interface ApiKeyServiceInterface
|
|||
*/
|
||||
public function disable(string $key): ApiKey;
|
||||
|
||||
/**
|
||||
* @return ApiKey[]
|
||||
*/
|
||||
public function listKeys(bool $enabledOnly = false): array;
|
||||
|
||||
public function getByKey(string $key): ?ApiKey;
|
||||
|
|
|
@ -74,7 +74,7 @@ class AuthenticateActionTest extends TestCase
|
|||
*/
|
||||
public function invalidApiKeyReturnsErrorResponse()
|
||||
{
|
||||
$this->apiKeyService->getByKey('foo')->willReturn((new ApiKey())->setEnabled(false))
|
||||
$this->apiKeyService->getByKey('foo')->willReturn((new ApiKey())->disable())
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withParsedBody([
|
||||
|
|
|
@ -54,8 +54,7 @@ class CreateShortUrlActionTest extends TestCase
|
|||
{
|
||||
$this->urlShortener->urlToShortCode(Argument::type(Uri::class), Argument::type('array'), Argument::cetera())
|
||||
->willReturn(
|
||||
(new ShortUrl())->setShortCode('abc123')
|
||||
->setLongUrl('')
|
||||
(new ShortUrl(''))->setShortCode('abc123')
|
||||
)
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
|
|
|
@ -82,7 +82,9 @@ class EditShortUrlActionTest extends TestCase
|
|||
->withParsedBody([
|
||||
'maxVisits' => 5,
|
||||
]);
|
||||
$updateMeta = $this->shortUrlService->updateMetadataByShortCode(Argument::cetera())->willReturn(new ShortUrl());
|
||||
$updateMeta = $this->shortUrlService->updateMetadataByShortCode(Argument::cetera())->willReturn(
|
||||
new ShortUrl('')
|
||||
);
|
||||
|
||||
$resp = $this->action->handle($request);
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ class EditShortUrlTagsActionTest extends TestCase
|
|||
public function tagsListIsReturnedIfCorrectShortCodeIsProvided()
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->shortUrlService->setTagsByShortCode($shortCode, [])->willReturn(new ShortUrl())
|
||||
$this->shortUrlService->setTagsByShortCode($shortCode, [])->willReturn(new ShortUrl(''))
|
||||
->shouldBeCalledTimes(1);
|
||||
|
||||
$response = $this->action->handle(
|
||||
|
|
|
@ -55,7 +55,7 @@ class ResolveShortUrlActionTest extends TestCase
|
|||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlShortener->shortCodeToUrl($shortCode)->willReturn(
|
||||
(new ShortUrl())->setLongUrl('http://domain.com/foo/bar')
|
||||
new ShortUrl('http://domain.com/foo/bar')
|
||||
)->shouldBeCalledTimes(1);
|
||||
|
||||
$request = ServerRequestFactory::fromGlobals()->withAttribute('shortCode', $shortCode);
|
||||
|
|
|
@ -103,7 +103,7 @@ class SingleStepCreateShortUrlActionTest extends TestCase
|
|||
null,
|
||||
null,
|
||||
null
|
||||
)->willReturn((new ShortUrl())->setLongUrl(''));
|
||||
)->willReturn(new ShortUrl(''));
|
||||
|
||||
$resp = $this->action->handle($request);
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ class UpdateTagActionTest extends TestCase
|
|||
'newName' => 'bar',
|
||||
]);
|
||||
/** @var MethodProphecy $rename */
|
||||
$rename = $this->tagService->renameTag('foo', 'bar')->willReturn(new Tag());
|
||||
$rename = $this->tagService->renameTag('foo', 'bar')->willReturn(new Tag('bar'));
|
||||
|
||||
$resp = $this->action->handle($request);
|
||||
|
||||
|
|
|
@ -87,8 +87,7 @@ class ApiKeyServiceTest extends TestCase
|
|||
*/
|
||||
public function checkReturnsFalseWhenKeyIsExpired()
|
||||
{
|
||||
$key = new ApiKey();
|
||||
$key->setExpirationDate(Chronos::now()->subDay());
|
||||
$key = new ApiKey(Chronos::now()->subDay());
|
||||
$repo = $this->prophesize(EntityRepository::class);
|
||||
$repo->findOneBy(['key' => '12345'])->willReturn($key)
|
||||
->shouldBeCalledTimes(1);
|
||||
|
|
Loading…
Add table
Reference in a new issue