Extend and normalize output from visits console commands

This commit is contained in:
Alejandro Celaya
2026-01-03 11:45:29 +01:00
parent 0d964f0fde
commit 900de9e800
16 changed files with 105 additions and 200 deletions

View File

@@ -107,7 +107,7 @@ return [
],
Command\Visit\GetOrphanVisitsCommand::class => [Visit\VisitsStatsHelper::class],
Command\Visit\DeleteOrphanVisitsCommand::class => [Visit\VisitsDeleter::class],
Command\Visit\GetNonOrphanVisitsCommand::class => [Visit\VisitsStatsHelper::class, ShortUrlStringifier::class],
Command\Visit\GetNonOrphanVisitsCommand::class => [Visit\VisitsStatsHelper::class],
Command\Api\GenerateKeyCommand::class => [ApiKeyService::class, ApiKey\RoleResolver::class],
Command\Api\DisableKeyCommand::class => [ApiKeyService::class],
@@ -119,11 +119,11 @@ return [
Command\Tag\ListTagsCommand::class => [TagService::class],
Command\Tag\RenameTagCommand::class => [TagService::class],
Command\Tag\DeleteTagsCommand::class => [TagService::class],
Command\Tag\GetTagVisitsCommand::class => [Visit\VisitsStatsHelper::class, ShortUrlStringifier::class],
Command\Tag\GetTagVisitsCommand::class => [Visit\VisitsStatsHelper::class],
Command\Domain\ListDomainsCommand::class => [DomainService::class],
Command\Domain\DomainRedirectsCommand::class => [DomainService::class],
Command\Domain\GetDomainVisitsCommand::class => [Visit\VisitsStatsHelper::class, ShortUrlStringifier::class],
Command\Domain\GetDomainVisitsCommand::class => [Visit\VisitsStatsHelper::class],
Command\RedirectRule\ManageRedirectRulesCommand::class => [
ShortUrl\ShortUrlResolver::class,

View File

@@ -6,8 +6,6 @@ namespace Shlinkio\Shlink\CLI\Command\Domain;
use Shlinkio\Shlink\CLI\Command\Visit\VisitsCommandUtils;
use Shlinkio\Shlink\CLI\Input\VisitsListInput;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
use Shlinkio\Shlink\Core\Visit\Model\VisitsParams;
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
use Symfony\Component\Console\Attribute\Argument;
@@ -22,10 +20,8 @@ class GetDomainVisitsCommand extends Command
{
public const string NAME = 'domain:visits';
public function __construct(
private readonly VisitsStatsHelperInterface $visitsHelper,
private readonly ShortUrlStringifierInterface $shortUrlStringifier,
) {
public function __construct(private readonly VisitsStatsHelperInterface $visitsHelper)
{
parent::__construct();
}
@@ -36,17 +32,8 @@ class GetDomainVisitsCommand extends Command
#[MapInput] VisitsListInput $input,
): int {
$paginator = $this->visitsHelper->visitsForDomain($domain, new VisitsParams($input->dateRange()));
VisitsCommandUtils::renderOutput($io, $input, $paginator, $this->mapExtraFields(...));
VisitsCommandUtils::renderOutput($io, $input, $paginator);
return self::SUCCESS;
}
/**
* @return array<string, string>
*/
protected function mapExtraFields(Visit $visit): array
{
$shortUrl = $visit->shortUrl;
return $shortUrl === null ? [] : ['shortUrl' => $this->shortUrlStringifier->stringify($shortUrl)];
}
}

View File

@@ -7,8 +7,6 @@ namespace Shlinkio\Shlink\CLI\Command\Tag;
use Shlinkio\Shlink\CLI\Command\Visit\VisitsCommandUtils;
use Shlinkio\Shlink\CLI\Input\VisitsListInput;
use Shlinkio\Shlink\Core\Domain\Entity\Domain;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
use Shlinkio\Shlink\Core\Visit\Model\WithDomainVisitsParams;
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
use Symfony\Component\Console\Attribute\Argument;
@@ -24,10 +22,8 @@ class GetTagVisitsCommand extends Command
{
public const string NAME = 'tag:visits';
public function __construct(
private readonly VisitsStatsHelperInterface $visitsHelper,
private readonly ShortUrlStringifierInterface $shortUrlStringifier,
) {
public function __construct(private readonly VisitsStatsHelperInterface $visitsHelper)
{
parent::__construct();
}
@@ -47,17 +43,8 @@ class GetTagVisitsCommand extends Command
domain: $domain,
));
VisitsCommandUtils::renderOutput($io, $input, $paginator, $this->mapExtraFields(...));
VisitsCommandUtils::renderOutput($io, $input, $paginator);
return self::SUCCESS;
}
/**
* @return array<string, string>
*/
private function mapExtraFields(Visit $visit): array
{
$shortUrl = $visit->shortUrl;
return $shortUrl === null ? [] : ['shortUrl' => $this->shortUrlStringifier->stringify($shortUrl)];
}
}

View File

@@ -6,8 +6,6 @@ namespace Shlinkio\Shlink\CLI\Command\Visit;
use Shlinkio\Shlink\CLI\Input\VisitsListInput;
use Shlinkio\Shlink\Core\Domain\Entity\Domain;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
use Shlinkio\Shlink\Core\Visit\Model\WithDomainVisitsParams;
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
use Symfony\Component\Console\Attribute\AsCommand;
@@ -21,10 +19,8 @@ class GetNonOrphanVisitsCommand extends Command
{
public const string NAME = 'visit:non-orphan';
public function __construct(
private readonly VisitsStatsHelperInterface $visitsHelper,
private readonly ShortUrlStringifierInterface $shortUrlStringifier,
) {
public function __construct(private readonly VisitsStatsHelperInterface $visitsHelper)
{
parent::__construct();
}
@@ -42,17 +38,8 @@ class GetNonOrphanVisitsCommand extends Command
dateRange: $input->dateRange(),
domain: $domain,
));
VisitsCommandUtils::renderOutput($io, $input, $paginator, $this->mapExtraFields(...));
VisitsCommandUtils::renderOutput($io, $input, $paginator);
return self::SUCCESS;
}
/**
* @return array<string, string>
*/
private function mapExtraFields(Visit $visit): array
{
$shortUrl = $visit->shortUrl;
return $shortUrl === null ? [] : ['shortUrl' => $this->shortUrlStringifier->stringify($shortUrl)];
}
}

View File

@@ -6,7 +6,6 @@ namespace Shlinkio\Shlink\CLI\Command\Visit;
use Shlinkio\Shlink\CLI\Input\VisitsListInput;
use Shlinkio\Shlink\Core\Domain\Entity\Domain;
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
use Shlinkio\Shlink\Core\Visit\Model\OrphanVisitsParams;
use Shlinkio\Shlink\Core\Visit\Model\OrphanVisitType;
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
@@ -42,16 +41,8 @@ class GetOrphanVisitsCommand extends Command
domain: $domain,
type: $type,
));
VisitsCommandUtils::renderOutput($io, $input, $paginator, $this->mapExtraFields(...));
VisitsCommandUtils::renderOutput($io, $input, $paginator);
return self::SUCCESS;
}
/**
* @return array<string, string>
*/
private function mapExtraFields(Visit $visit): array
{
return ['type' => $visit->type->value];
}
}

View File

@@ -13,16 +13,12 @@ use Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtils;
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
use Symfony\Component\Console\Output\OutputInterface;
use function array_keys;
use function array_map;
use function Shlinkio\Shlink\Core\ArrayUtils\select_keys;
use function Shlinkio\Shlink\Core\camelCaseToHumanFriendly;
class VisitsCommandUtils
{
/**
* @param Paginator<Visit> $paginator
* @param null|callable(Visit $visits): array<string, string> $mapExtraFields
*/
public static function renderOutput(
OutputInterface $output,
@@ -36,25 +32,21 @@ class VisitsCommandUtils
}
match ($inputData->format) {
VisitsListFormat::CSV => self::renderCSVOutput($output, $paginator, $mapExtraFields),
default => self::renderHumanFriendlyOutput($output, $paginator, $mapExtraFields),
VisitsListFormat::CSV => self::renderCSVOutput($output, $paginator),
default => self::renderHumanFriendlyOutput($output, $paginator),
};
}
/**
* @param Paginator<Visit> $paginator
* @param null|callable(Visit $visits): array<string, string> $mapExtraFields
*/
private static function renderCSVOutput(
OutputInterface $output,
Paginator $paginator,
callable|null $mapExtraFields,
): void {
private static function renderCSVOutput(OutputInterface $output, Paginator $paginator): void
{
$page = 1;
do {
$paginator->setCurrentPage($page);
[$rows, $headers] = self::resolveRowsAndHeaders($paginator, $mapExtraFields);
[$rows, $headers] = self::resolveRowsAndHeaders($paginator);
$csv = Writer::fromString();
if ($page === 1) {
$csv->insertOne($headers);
@@ -69,19 +61,15 @@ class VisitsCommandUtils
/**
* @param Paginator<Visit> $paginator
* @param null|callable(Visit $visits): array<string, string> $mapExtraFields
*/
private static function renderHumanFriendlyOutput(
OutputInterface $output,
Paginator $paginator,
callable|null $mapExtraFields,
): void {
private static function renderHumanFriendlyOutput(OutputInterface $output, Paginator $paginator): void
{
$page = 1;
do {
$paginator->setCurrentPage($page);
$page++;
[$rows, $headers] = self::resolveRowsAndHeaders($paginator, $mapExtraFields);
[$rows, $headers] = self::resolveRowsAndHeaders($paginator);
ShlinkTable::default($output)->render(
$headers,
$rows,
@@ -92,35 +80,38 @@ class VisitsCommandUtils
/**
* @param Paginator<Visit> $paginator
* @param null|callable(Visit $visits): array<string, string> $mapExtraFields
*/
private static function resolveRowsAndHeaders(Paginator $paginator, callable|null $mapExtraFields): array
private static function resolveRowsAndHeaders(Paginator $paginator): array
{
$extraKeys = null;
$mapExtraFields ??= static fn (Visit $_) => [];
$rows = array_map(function (Visit $visit) use (&$extraKeys, $mapExtraFields) {
$extraFields = $mapExtraFields($visit);
$extraKeys ??= array_keys($extraFields);
$rowData = [
'referer' => $visit->referer,
'date' => $visit->date->toAtomString(),
'userAgent' => $visit->userAgent,
'potentialBot' => $visit->potentialBot,
'country' => $visit->getVisitLocation()->countryName ?? 'Unknown',
'city' => $visit->getVisitLocation()->cityName ?? 'Unknown',
...$extraFields,
];
// Filter out unknown keys
return select_keys($rowData, ['referer', 'date', 'userAgent', 'country', 'city', ...$extraKeys]);
}, [...$paginator->getCurrentPageResults()]);
$extra = array_map(camelCaseToHumanFriendly(...), $extraKeys ?? []);
return [
$rows,
['Referer', 'Date', 'User agent', 'Country', 'City', ...$extra],
$headers = [
'Date',
'Potential bot',
'User agent',
'Referer',
'Country',
'Region',
'City',
'Visited URL',
'Redirect URL',
'Type',
];
$rows = array_map(function (Visit $visit) {
$visitLocation = $visit->visitLocation;
return [
'date' => $visit->date->toAtomString(),
'potentialBot' => $visit->potentialBot ? 'Potential bot' : '',
'userAgent' => $visit->userAgent,
'referer' => $visit->referer,
'country' => $visitLocation->countryName ?? 'Unknown',
'region' => $visitLocation->regionName ?? 'Unknown',
'city' => $visitLocation->cityName ?? 'Unknown',
'visitedUrl' => $visit->visitedUrl ?? 'Unknown',
'redirectUrl' => $visit->redirectUrl ?? 'Unknown',
'type' => $visit->type->value,
];
}, [...$paginator->getCurrentPageResults()]);
return [$rows, $headers];
}
}

View File

@@ -11,10 +11,10 @@ use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\CLI\Command\Domain\GetDomainVisitsCommand;
use Shlinkio\Shlink\Common\Paginator\Paginator;
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation;
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
use Shlinkio\Shlink\Core\Visit\Model\VisitType;
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
use Shlinkio\Shlink\IpGeolocation\Model\Location;
use ShlinkioTest\Shlink\CLI\Util\CliTestUtils;
@@ -24,16 +24,11 @@ class GetDomainVisitsCommandTest extends TestCase
{
private CommandTester $commandTester;
private MockObject & VisitsStatsHelperInterface $visitsHelper;
private MockObject & ShortUrlStringifierInterface $stringifier;
protected function setUp(): void
{
$this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class);
$this->stringifier = $this->createMock(ShortUrlStringifierInterface::class);
$this->commandTester = CliTestUtils::testerForCommand(
new GetDomainVisitsCommand($this->visitsHelper, $this->stringifier),
);
$this->commandTester = CliTestUtils::testerForCommand(new GetDomainVisitsCommand($this->visitsHelper));
}
#[Test]
@@ -48,22 +43,22 @@ class GetDomainVisitsCommandTest extends TestCase
$domain,
$this->anything(),
)->willReturn(new Paginator(new ArrayAdapter([$visit])));
$this->stringifier->expects($this->once())->method('stringify')->with($shortUrl)->willReturn(
'the_short_url',
);
$this->commandTester->execute(['domain' => $domain]);
$output = $this->commandTester->getDisplay();
$type = VisitType::VALID_SHORT_URL->value;
self::assertEquals(
// phpcs:disable Generic.Files.LineLength
<<<OUTPUT
+---------+---------------------------+------------+---------+--------+---------------+
| Referer | Date | User agent | Country | City | Short Url |
+---------+---------------------------+------------+---------+--------+---------------+
| foo | {$visit->date->toAtomString()} | bar | Spain | Madrid | the_short_url |
+---------+-------------------------- Page 1 of 1 -+---------+--------+---------------+
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
| {$visit->date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | {$type} |
+---------------------------+---------------+------------+------- Page 1 of 1 --------+--------+-------------+--------------+-----------------+
OUTPUT,
// phpcs:enable
$output,
);
}

View File

@@ -107,20 +107,22 @@ class GetShortUrlVisitsCommandTest extends TestCase
{
yield 'regular' => [
VisitsListFormat::FULL,
// phpcs:disable Generic.Files.LineLength
static fn (Chronos $date) => <<<OUTPUT
+---------+---------------------------+------------+---------+--------+
| Referer | Date | User agent | Country | City |
+---------+---------------------------+------------+---------+--------+
| foo | {$date->toAtomString()} | bar | Spain | Madrid |
+---------+------------------ Page 1 of 1 ---------+---------+--------+
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
| {$date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | valid_short_url |
+---------------------------+---------------+------------+------- Page 1 of 1 --------+--------+-------------+--------------+-----------------+
OUTPUT,
// phpcs:enable
];
yield 'CSV' => [
VisitsListFormat::CSV,
static fn (Chronos $date) => <<<OUTPUT
Referer,Date,"User agent",Country,City
foo,{$date->toAtomString()},bar,Spain,Madrid
Date,"Potential bot","User agent",Referer,Country,Region,City,"Visited URL","Redirect URL",Type
{$date->toAtomString()},,bar,foo,Spain,,Madrid,,Unknown,valid_short_url
OUTPUT,
];

View File

@@ -11,10 +11,10 @@ use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\CLI\Command\Tag\GetTagVisitsCommand;
use Shlinkio\Shlink\Common\Paginator\Paginator;
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation;
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
use Shlinkio\Shlink\Core\Visit\Model\VisitType;
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
use Shlinkio\Shlink\IpGeolocation\Model\Location;
use ShlinkioTest\Shlink\CLI\Util\CliTestUtils;
@@ -24,16 +24,11 @@ class GetTagVisitsCommandTest extends TestCase
{
private CommandTester $commandTester;
private MockObject & VisitsStatsHelperInterface $visitsHelper;
private MockObject & ShortUrlStringifierInterface $stringifier;
protected function setUp(): void
{
$this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class);
$this->stringifier = $this->createMock(ShortUrlStringifierInterface::class);
$this->commandTester = CliTestUtils::testerForCommand(
new GetTagVisitsCommand($this->visitsHelper, $this->stringifier),
);
$this->commandTester = CliTestUtils::testerForCommand(new GetTagVisitsCommand($this->visitsHelper));
}
#[Test]
@@ -47,20 +42,22 @@ class GetTagVisitsCommandTest extends TestCase
$this->visitsHelper->expects($this->once())->method('visitsForTag')->with($tag, $this->anything())->willReturn(
new Paginator(new ArrayAdapter([$visit])),
);
$this->stringifier->expects($this->once())->method('stringify')->with($shortUrl)->willReturn('the_short_url');
$this->commandTester->execute(['tag' => $tag]);
$output = $this->commandTester->getDisplay();
$type = VisitType::VALID_SHORT_URL->value;
self::assertEquals(
// phpcs:disable Generic.Files.LineLength
<<<OUTPUT
+---------+---------------------------+------------+---------+--------+---------------+
| Referer | Date | User agent | Country | City | Short Url |
+---------+---------------------------+------------+---------+--------+---------------+
| foo | {$visit->date->toAtomString()} | bar | Spain | Madrid | the_short_url |
+---------+-------------------------- Page 1 of 1 -+---------+--------+---------------+
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
| {$visit->date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | {$type} |
+---------------------------+---------------+------------+------- Page 1 of 1 --------+--------+-------------+--------------+-----------------+
OUTPUT,
// phpcs:enable
$output,
);
}

View File

@@ -11,10 +11,10 @@ use PHPUnit\Framework\TestCase;
use Shlinkio\Shlink\CLI\Command\Visit\GetNonOrphanVisitsCommand;
use Shlinkio\Shlink\Common\Paginator\Paginator;
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation;
use Shlinkio\Shlink\Core\Visit\Model\Visitor;
use Shlinkio\Shlink\Core\Visit\Model\VisitType;
use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface;
use Shlinkio\Shlink\IpGeolocation\Model\Location;
use ShlinkioTest\Shlink\CLI\Util\CliTestUtils;
@@ -24,16 +24,11 @@ class GetNonOrphanVisitsCommandTest extends TestCase
{
private CommandTester $commandTester;
private MockObject & VisitsStatsHelperInterface $visitsHelper;
private MockObject & ShortUrlStringifierInterface $stringifier;
protected function setUp(): void
{
$this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class);
$this->stringifier = $this->createMock(ShortUrlStringifierInterface::class);
$this->commandTester = CliTestUtils::testerForCommand(
new GetNonOrphanVisitsCommand($this->visitsHelper, $this->stringifier),
);
$this->commandTester = CliTestUtils::testerForCommand(new GetNonOrphanVisitsCommand($this->visitsHelper));
}
#[Test]
@@ -46,20 +41,22 @@ class GetNonOrphanVisitsCommandTest extends TestCase
$this->visitsHelper->expects($this->once())->method('nonOrphanVisits')->withAnyParameters()->willReturn(
new Paginator(new ArrayAdapter([$visit])),
);
$this->stringifier->expects($this->once())->method('stringify')->with($shortUrl)->willReturn('the_short_url');
$this->commandTester->execute([]);
$output = $this->commandTester->getDisplay();
$type = VisitType::VALID_SHORT_URL->value;
self::assertEquals(
// phpcs:disable Generic.Files.LineLength
<<<OUTPUT
+---------+---------------------------+------------+---------+--------+---------------+
| Referer | Date | User agent | Country | City | Short Url |
+---------+---------------------------+------------+---------+--------+---------------+
| foo | {$visit->date->toAtomString()} | bar | Spain | Madrid | the_short_url |
+---------+-------------------------- Page 1 of 1 -+---------+--------+---------------+
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+-----------------+
| {$visit->date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | {$type} |
+---------------------------+---------------+------------+------- Page 1 of 1 --------+--------+-------------+--------------+-----------------+
OUTPUT,
// phpcs:enable
$output,
);
}

View File

@@ -48,16 +48,19 @@ class GetOrphanVisitsCommandTest extends TestCase
$this->commandTester->execute($args);
$output = $this->commandTester->getDisplay();
$type = OrphanVisitType::BASE_URL->value;
self::assertEquals(
// phpcs:disable Generic.Files.LineLength
<<<OUTPUT
+---------+---------------------------+------------+---------+--------+----------+
| Referer | Date | User agent | Country | City | Type |
+---------+---------------------------+------------+---------+--------+----------+
| foo | {$visit->date->toAtomString()} | bar | Spain | Madrid | base_url |
+---------+----------------------- Page 1 of 1 ----+---------+--------+----------+
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+----------+
| Date | Potential bot | User agent | Referer | Country | Region | City | Visited URL | Redirect URL | Type |
+---------------------------+---------------+------------+---------+---------+--------+--------+-------------+--------------+----------+
| {$visit->date->toAtomString()} | | bar | foo | Spain | | Madrid | | Unknown | {$type} |
+---------------------------+---------------+------------+--- Page 1 of 1 ---+--------+--------+-------------+--------------+----------+
OUTPUT,
// phpcs:enable
$output,
);
}

View File

@@ -4,12 +4,8 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\ArrayUtils;
use function array_filter;
use function array_reduce;
use function in_array;
use const ARRAY_FILTER_USE_KEY;
/**
* @template T
* @param T $value
@@ -20,18 +16,6 @@ function contains(mixed $value, array $array): bool
return in_array($value, $array, strict: true);
}
/**
* @param array[] $multiArray
*/
function flatten(array $multiArray): array
{
return array_reduce(
$multiArray,
static fn (array $carry, array $value) => [...$carry, ...$value],
initial: [],
);
}
/**
* Checks if a callback returns true for at least one item in a collection.
* @param callable(mixed $value, mixed $key): bool $callback
@@ -62,21 +46,6 @@ function every(iterable $collection, callable $callback): bool
return true;
}
/**
* Returns an array containing only those entries in the array whose key is in the supplied keys.
*/
function select_keys(array $array, array $keys): array
{
return array_filter(
$array,
static fn (string $key) => contains(
$key,
$keys,
),
ARRAY_FILTER_USE_KEY,
);
}
/**
* @template T
* @template R

View File

@@ -58,7 +58,7 @@ readonly class MatomoVisitSender implements MatomoVisitSenderInterface
->setUrlReferrer($visit->referer)
->setForceVisitDateTime($visit->date->setTimezone('UTC')->toDateTimeString());
$location = $visit->getVisitLocation();
$location = $visit->visitLocation;
if ($location !== null) {
$tracker
->setCity($location->cityName)

View File

@@ -29,7 +29,7 @@ class Visit extends AbstractEntity implements JsonSerializable
public readonly string|null $remoteAddr = null,
public readonly string|null $visitedUrl = null,
public readonly string|null $redirectUrl = null,
private VisitLocation|null $visitLocation = null,
private(set) VisitLocation|null $visitLocation = null,
public readonly Chronos $date = new Chronos(),
) {
}
@@ -124,11 +124,6 @@ class Visit extends AbstractEntity implements JsonSerializable
return ! empty($this->remoteAddr);
}
public function getVisitLocation(): VisitLocation|null
{
return $this->visitLocation;
}
public function locate(VisitLocation $visitLocation): self
{
$this->visitLocation = $visitLocation;

View File

@@ -72,7 +72,7 @@ readonly class VisitLocator implements VisitLocatorInterface
private function locateVisit(Visit $visit, VisitLocation $location, VisitGeolocationHelperInterface $helper): void
{
$prevLocation = $visit->getVisitLocation();
$prevLocation = $visit->visitLocation;
$visit->locate($location);
$this->em->persist($visit);