> 드라이버명 => 구현 클래스 */ public function registered(): array { $maintainers = [ 'mysql-fulltext' => FulltextIndexMaintainer::class, ]; $maintainers = HookManager::applyFilters(self::MAINTAINERS_FILTER, $maintainers); return is_array($maintainers) ? $maintainers : []; } /** * 활성 드라이버의 유지보수기를 반환합니다. * * @return SearchIndexMaintainer|null 등록되지 않았으면 null */ public function maintainer(): ?SearchIndexMaintainer { if ($this->resolvedAttempted) { return $this->resolved; } $this->resolvedAttempted = true; $class = $this->registered()[$this->driver()] ?? null; if ($class === null) { return $this->resolved = null; } try { $instance = app($class); } catch (Throwable $e) { Log::warning('검색 인덱스 유지보수기 생성 실패', [ 'driver' => $this->driver(), 'class' => $class, 'error' => $e->getMessage(), ]); return $this->resolved = null; } if (! $instance instanceof SearchIndexMaintainer) { Log::warning('검색 인덱스 유지보수기가 계약을 구현하지 않음', [ 'driver' => $this->driver(), 'class' => $class, ]); return $this->resolved = null; } return $this->resolved = $instance; } /** * 활성 엔진이 점검을 제공하는지 여부. * * @return bool */ public function hasMaintainer(): bool { return $this->maintainer() !== null; } /** * 활성 엔진의 인덱스를 점검합니다. * * @param array $filters 엔진별 필터 * @return array */ public function inspect(array $filters = []): array { $maintainer = $this->maintainer(); if ($maintainer === null || ! $maintainer->isAvailable()) { return []; } return $maintainer->inspect($filters); } /** * 점검 불가 사유를 반환합니다. * * @return string|null 점검 가능하면 null */ public function unavailableReason(): ?string { $maintainer = $this->maintainer(); if ($maintainer === null) { return __('search.index.no_maintainer', ['driver' => $this->driver()]); } return $maintainer->isAvailable() ? null : $maintainer->unavailableReason(); } /** * 색인이 누락된 인덱스를 재생성합니다. * * 재생성 후 **다시 점검해서** 잔존 여부를 담습니다 — 재생성했다는 사실만으로 * 복구됐다고 단정하지 않습니다. * * @param array $filters 엔진별 필터 * @return SearchIndexRepairReport 실행 보고 */ public function repairStale(array $filters = []): SearchIndexRepairReport { $maintainer = $this->maintainer(); if ($maintainer === null) { return SearchIndexRepairReport::unavailable($this->driver(), $this->unavailableReason()); } if (! $maintainer->isAvailable()) { return SearchIndexRepairReport::unavailable($this->driver(), $maintainer->unavailableReason()); } $before = $maintainer->inspect($filters); $targets = array_values(array_filter($before, fn (SearchIndexHealth $h) => $h->needsRebuild())); if ($targets === []) { return SearchIndexRepairReport::nothingToDo($this->driver(), count($before)); } $repaired = []; $failed = []; foreach ($targets as $target) { try { $maintainer->rebuild($target); $repaired[] = $target->identifier; } catch (Throwable $e) { $failed[$target->identifier] = $e->getMessage(); Log::error('검색 인덱스 재생성 실패', [ 'driver' => $this->driver(), 'index' => $target->identifier, 'error' => $e->getMessage(), ]); } } $after = $maintainer->inspect($filters); $remaining = array_values(array_map( fn (SearchIndexHealth $h) => $h->identifier, array_filter($after, fn (SearchIndexHealth $h) => $h->needsRebuild()) )); Log::info('검색 인덱스 재생성 수행', [ 'driver' => $this->driver(), 'inspected' => count($after), 'repaired' => $repaired, 'failed' => array_keys($failed), 'remaining' => $remaining, ]); return new SearchIndexRepairReport( driver: $this->driver(), inspected: count($after), repaired: $repaired, failed: $failed, remaining: $remaining, ); } }