Files
SkinbaseNova/app/Console/Commands/RecomputeUserStatsCommand.php
2026-02-26 21:12:32 +01:00

148 lines
5.1 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Jobs\RecomputeUserStatsJob;
use App\Services\UserStatsService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
/**
* Recompute user_statistics counters from authoritative source tables.
*
* Usage:
* # Recompute a single user (live)
* php artisan skinbase:recompute-user-stats 42
*
* # Dry-run for a single user
* php artisan skinbase:recompute-user-stats 42 --dry-run
*
* # Recompute all users in chunks of 500
* php artisan skinbase:recompute-user-stats --all --chunk=500
*
* # Recompute all users via queue (one job per chunk)
* php artisan skinbase:recompute-user-stats --all --queue
*/
class RecomputeUserStatsCommand extends Command
{
protected $signature = 'skinbase:recompute-user-stats
{user_id? : The ID of a single user to recompute}
{--all : Recompute stats for ALL non-deleted users}
{--chunk=1000 : Chunk size when --all is used}
{--dry-run : Show what would be written without saving}
{--queue : Dispatch recompute jobs to the queue (--all mode only)}';
protected $description = 'Rebuild user_statistics counters from authoritative source tables';
public function handle(UserStatsService $statsService): int
{
$dryRun = (bool) $this->option('dry-run');
$all = (bool) $this->option('all');
$userId = $this->argument('user_id');
$chunk = max(1, (int) $this->option('chunk'));
$queue = (bool) $this->option('queue');
if ($userId !== null && $all) {
$this->error('Provide either a user_id OR --all, not both.');
return self::FAILURE;
}
if ($userId !== null) {
return $this->recomputeSingle((int) $userId, $statsService, $dryRun);
}
if ($all) {
return $this->recomputeAll($statsService, $chunk, $dryRun, $queue);
}
$this->error('Provide a user_id or use --all.');
return self::FAILURE;
}
// ─── Single user ─────────────────────────────────────────────────────────
private function recomputeSingle(int $userId, UserStatsService $statsService, bool $dryRun): int
{
$exists = DB::table('users')->where('id', $userId)->exists();
if (! $exists) {
$this->error("User {$userId} not found.");
return self::FAILURE;
}
$label = $dryRun ? '[DRY-RUN]' : '[LIVE]';
$this->line("{$label} Recomputing stats for user #{$userId}");
$computed = $statsService->recomputeUser($userId, $dryRun);
$rows = [];
foreach ($computed as $col => $val) {
$rows[] = [$col, $val ?? '(null)'];
}
$this->table(['Column', 'Value'], $rows);
if ($dryRun) {
$this->warn('Dry-run: no changes written.');
} else {
$this->info("Stats saved for user #{$userId}.");
}
return self::SUCCESS;
}
// ─── All users ────────────────────────────────────────────────────────────
private function recomputeAll(
UserStatsService $statsService,
int $chunk,
bool $dryRun,
bool $useQueue
): int {
$total = DB::table('users')->whereNull('deleted_at')->count();
$label = $dryRun ? '[DRY-RUN]' : ($useQueue ? '[QUEUE]' : '[LIVE]');
$this->info("{$label} Recomputing stats for {$total} users (chunk={$chunk})…");
if ($useQueue && ! $dryRun) {
$dispatched = 0;
DB::table('users')
->whereNull('deleted_at')
->orderBy('id')
->chunkById($chunk, function ($users) use (&$dispatched) {
$ids = $users->pluck('id')->all();
RecomputeUserStatsJob::dispatch($ids);
$dispatched += count($ids);
$this->line(" Queued chunk of " . count($ids) . " users (total dispatched: {$dispatched})");
});
$this->info("Done {$dispatched} users queued for recompute.");
return self::SUCCESS;
}
$processed = 0;
$bar = $this->output->createProgressBar($total);
$bar->start();
DB::table('users')
->whereNull('deleted_at')
->orderBy('id')
->chunkById($chunk, function ($users) use ($statsService, $dryRun, &$processed, $bar) {
foreach ($users as $user) {
$statsService->recomputeUser((int) $user->id, $dryRun);
$processed++;
$bar->advance();
}
});
$bar->finish();
$this->newLine();
$suffix = $dryRun ? ' (no changes written dry-run)' : '';
$this->info("Done {$processed} users recomputed{$suffix}.");
return self::SUCCESS;
}
}