mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-08 16:23:12 +08:00
Support paginating the output of visits commands to avoid out of memory errors
This commit is contained in:
@@ -4,8 +4,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\CLI\Command\Visit;
|
||||
|
||||
use Shlinkio\Shlink\CLI\Input\VisitsListFormat;
|
||||
use Shlinkio\Shlink\CLI\Input\VisitsListInput;
|
||||
use Shlinkio\Shlink\CLI\Util\ShlinkTable;
|
||||
use Shlinkio\Shlink\Common\Paginator\Paginator;
|
||||
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;
|
||||
@@ -18,14 +23,70 @@ class VisitsCommandUtils
|
||||
* @param Paginator<Visit> $paginator
|
||||
* @param null|callable(Visit $visits): array<string, string> $mapExtraFields
|
||||
*/
|
||||
public static function resolveRowsAndHeaders(Paginator $paginator, callable|null $mapExtraFields = null): array
|
||||
public static function renderOutput(
|
||||
OutputInterface $output,
|
||||
VisitsListInput $inputData,
|
||||
Paginator $paginator,
|
||||
callable|null $mapExtraFields = null,
|
||||
): void {
|
||||
if ($inputData->format !== VisitsListFormat::FULL) {
|
||||
// Avoid running out of memory by loading visits in chunks
|
||||
$paginator->setMaxPerPage(1000);
|
||||
}
|
||||
|
||||
match ($inputData->format) {
|
||||
VisitsListFormat::CSV => self::renderCSVOutput($output, $paginator, $mapExtraFields),
|
||||
default => self::renderHumanFriendlyOutput($output, $paginator, $mapExtraFields),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 {
|
||||
$page = 1;
|
||||
do {
|
||||
$paginator->setCurrentPage($page);
|
||||
$page++;
|
||||
|
||||
[$rows, $headers] = self::resolveRowsAndHeaders($paginator, $mapExtraFields);
|
||||
ShlinkTable::default($output)->render(
|
||||
$headers,
|
||||
$rows,
|
||||
footerTitle: PagerfantaUtils::formatCurrentPageMessage($paginator, 'Page %s of %s'),
|
||||
);
|
||||
} while ($paginator->hasNextPage());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Paginator<Visit> $paginator
|
||||
* @param null|callable(Visit $visits): array<string, string> $mapExtraFields
|
||||
*/
|
||||
private static function resolveRowsAndHeaders(Paginator $paginator, callable|null $mapExtraFields): array
|
||||
{
|
||||
$extraKeys = [];
|
||||
$extraKeys = null;
|
||||
$mapExtraFields ??= static fn (Visit $_) => [];
|
||||
|
||||
$rows = array_map(function (Visit $visit) use (&$extraKeys, $mapExtraFields) {
|
||||
$extraFields = $mapExtraFields($visit);
|
||||
$extraKeys = array_keys($extraFields);
|
||||
$extraKeys ??= array_keys($extraFields);
|
||||
|
||||
$rowData = [
|
||||
'referer' => $visit->referer,
|
||||
@@ -40,7 +101,7 @@ class VisitsCommandUtils
|
||||
// Filter out unknown keys
|
||||
return select_keys($rowData, ['referer', 'date', 'userAgent', 'country', 'city', ...$extraKeys]);
|
||||
}, [...$paginator->getCurrentPageResults()]);
|
||||
$extra = array_map(camelCaseToHumanFriendly(...), $extraKeys);
|
||||
$extra = array_map(camelCaseToHumanFriendly(...), $extraKeys ?? []);
|
||||
|
||||
return [
|
||||
$rows,
|
||||
|
||||
Reference in New Issue
Block a user