driver(); $filters = $this->parseFilters(); if (! $manager->hasMaintainer()) { $this->components->warn(__('search.index.no_maintainer', ['driver' => $driver])); return self::SUCCESS; } if (($reason = $manager->unavailableReason()) !== null) { $this->components->warn($reason); return self::SUCCESS; } $results = $manager->inspect($filters); if ($results === []) { $this->components->warn(__('search.index.no_targets', ['driver' => $driver])); return self::SUCCESS; } $report = null; if ($this->option('repair')) { $targets = array_values(array_filter($results, fn (SearchIndexHealth $h) => $h->needsRebuild())); if ($targets !== [] && $this->confirmRebuild($targets)) { $report = $manager->repairStale($filters); $results = $manager->inspect($filters); } } if ($this->option('json')) { $this->line((string) json_encode([ 'driver' => $driver, 'results' => array_map(fn (SearchIndexHealth $h) => $h->toArray(), $results), 'repair' => $report?->toArray(), ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); } else { $this->render($driver, $results, $report?->summary()); } $remaining = array_filter($results, fn (SearchIndexHealth $h) => $h->needsRebuild()); return ($remaining === [] && ($report === null || $report->failed === [])) ? self::SUCCESS : self::FAILURE; } /** * `--filter=key=value` 옵션을 연관 배열로 해석합니다. * * @return array */ private function parseFilters(): array { $filters = []; foreach ((array) $this->option('filter') as $raw) { if (! is_string($raw) || ! str_contains($raw, '=')) { continue; } [$key, $value] = explode('=', $raw, 2); $key = trim($key); if ($key !== '') { $filters[$key] = trim($value); } } return $filters; } /** * 재생성 대상을 보여 주고 진행 여부를 확인합니다. * * @param array $targets 재생성 대상 * @return bool 진행 여부 */ private function confirmRebuild(array $targets): bool { $this->newLine(); $this->components->info(__('search.index.rebuild_targets', ['count' => count($targets)])); foreach ($targets as $target) { $this->line(' - '.$target->identifier.' ('.$target->measurement.')'); } $this->components->warn(__('search.index.rebuild_cost_warning')); if ($this->unifiedConfirm(__('search.index.rebuild_confirm'), true)) { return true; } $this->components->info(__('search.index.rebuild_skipped')); return false; } /** * 판정 결과를 표로 출력합니다. * * @param string $driver 드라이버명 * @param array $results 판정 결과 * @param string|null $repairSummary 재생성 요약 (수행했을 때만) * @return void */ private function render(string $driver, array $results, ?string $repairSummary): void { $rows = array_map(fn (SearchIndexHealth $health) => [ $health->identifier, 'status->consoleColor().'>'.$health->status->label().'', $health->measurement, ], $results); $this->newLine(); $this->line(' '.__('search.index.driver_label', ['driver' => $driver])); $this->table([__('search.index.col.index'), __('search.index.col.status'), __('search.index.col.measurement')], $rows); $counts = []; foreach (SearchIndexStatus::cases() as $case) { $counts[$case->value] = count(array_filter($results, fn (SearchIndexHealth $h) => $h->status === $case)); } $this->line(' '.__('search.index.counts', [ 'healthy' => $counts[SearchIndexStatus::Healthy->value], 'degraded' => $counts[SearchIndexStatus::Degraded->value], 'stale' => $counts[SearchIndexStatus::Stale->value], 'skipped' => $counts[SearchIndexStatus::Skipped->value], 'total' => count($results), ])); if ($repairSummary !== null) { $this->newLine(); $this->components->info($repairSummary); } if ($counts[SearchIndexStatus::Stale->value] > 0 && ! $this->option('repair')) { $this->newLine(); $this->components->warn(__('search.index.stale_hint')); } if ($counts[SearchIndexStatus::Degraded->value] > 0) { $this->line(' '.__('search.index.degraded_hint').''); } } }