Files
Investidor-V2/app/Services/FederatedSearch.php
T

79 lines
3.2 KiB
PHP

<?php
namespace App\Services;
use Illuminate\Database\QueryException;
/** Combines independent, already-authorised sources without loading complete catalogues. */
final class FederatedSearch
{
/** @param array<string, callable(): array{count:int, fetch:callable(int,int): iterable}> $factories */
public function page(array $factories, int $requestedPage, int $size = 5, ?callable $score = null): array
{
$sources = [];
$unavailable = [];
foreach ($factories as $label => $factory) {
try {
$sources[$label] = $factory();
} catch (QueryException $exception) {
report($exception);
$unavailable[] = $label;
}
}
// If a source fails after its count, remove it and recompute the page offsets.
do {
$failed = false;
$total = array_sum(array_column($sources, 'count'));
$last = max(1, (int) ceil($total / $size));
$page = min(max(1, $requestedPage), $last);
$first = ($page - 1) * $size;
$items = [];
$positions = array_fill_keys(array_keys($sources), 0);
$buffers = array_fill_keys(array_keys($sources), []);
for ($position = 0; $position < min($total, $first + $size); $position++) {
$best = null;
$bestScore = PHP_INT_MIN;
foreach ($sources as $label => $source) {
if ($buffers[$label] === [] && $positions[$label] < $source['count']) {
try {
$batch = iterator_to_array((function () use ($source, $positions, $label, $size) {
yield from ($source['fetch'])($positions[$label], $size);
})(), false);
$positions[$label] += count($batch);
$buffers[$label] = $batch;
if ($batch === []) {
$positions[$label] = $source['count'];
}
} catch (QueryException $exception) {
report($exception);
$unavailable[] = $label;
unset($sources[$label]);
$failed = true;
break;
}
}
if ($buffers[$label] === []) {
continue;
}
$candidateScore = $score ? $score($buffers[$label][0]) : 0;
if ($best === null || $candidateScore > $bestScore) {
$best = $label;
$bestScore = $candidateScore;
}
}
if ($failed || $best === null) {
break;
}
$item = array_shift($buffers[$best]);
if ($position >= $first) {
$items[] = [...$item, 'category' => $best];
}
}
} while ($failed);
return ['data' => $items, 'total' => $total, 'current_page' => $page, 'last_page' => $last,
'per_page' => $size, 'unavailable' => $unavailable];
}
}