Files
SkinbaseNova/app/Services/Sitemaps/Builders/AbstractIdShardableSitemapBuilder.php

135 lines
3.4 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Sitemaps\Builders;
use App\Services\Sitemaps\AbstractSitemapBuilder;
use App\Services\Sitemaps\ShardableSitemapBuilder;
use App\Services\Sitemaps\SitemapUrl;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
abstract class AbstractIdShardableSitemapBuilder extends AbstractSitemapBuilder implements ShardableSitemapBuilder
{
/**
* @return Builder<Model>
*/
abstract protected function query(): Builder;
abstract protected function shardConfigKey(): string;
abstract protected function mapRecord(Model $record): ?SitemapUrl;
public function items(): array
{
return $this->query()
->orderBy($this->idColumn())
->cursor()
->map(fn (Model $record): ?SitemapUrl => $this->mapRecord($record))
->filter()
->values()
->all();
}
public function lastModified(): ?DateTimeInterface
{
return $this->newest(...array_map(
fn (SitemapUrl $item): ?DateTimeInterface => $item->lastModified,
$this->items(),
));
}
public function totalItems(): int
{
return (clone $this->query())->count();
}
public function shardSize(): int
{
return max(1, (int) \data_get(\config('sitemaps.shards', []), $this->shardConfigKey() . '.size', 10000));
}
public function itemsForShard(int $shard): array
{
$window = $this->shardWindow($shard);
if ($window === null) {
return [];
}
return $this->applyShardWindow($window['from'], $window['to'])
->get()
->map(fn (Model $record): ?SitemapUrl => $this->mapRecord($record))
->filter()
->values()
->all();
}
public function lastModifiedForShard(int $shard): ?DateTimeInterface
{
return $this->newest(...array_map(
fn (SitemapUrl $item): ?DateTimeInterface => $item->lastModified,
$this->itemsForShard($shard),
));
}
/**
* @return array{from: int, to: int}|null
*/
protected function shardWindow(int $shard): ?array
{
if ($shard < 1) {
return null;
}
$size = $this->shardSize();
$current = 0;
$from = null;
$to = null;
$windowQuery = (clone $this->query())
->setEagerLoads([])
->select([$this->idColumn()])
->orderBy($this->idColumn());
foreach ($windowQuery->cursor() as $record) {
$current++;
if ((int) ceil($current / $size) !== $shard) {
continue;
}
$recordId = (int) $record->getAttribute($this->idColumn());
$from ??= $recordId;
$to = $recordId;
}
if ($from === null || $to === null) {
return null;
}
return ['from' => $from, 'to' => $to];
}
/**
* @return Builder<Model>
*/
protected function applyShardWindow(int $from, int $to): Builder
{
return (clone $this->query())
->whereBetween($this->qualifiedIdColumn(), [$from, $to])
->orderBy($this->idColumn());
}
protected function idColumn(): string
{
return 'id';
}
protected function qualifiedIdColumn(): string
{
return $this->idColumn();
}
}