활성화된 템플릿 identifier 배열 */ public static function getActiveTemplateIdentifiers(): array { if (! self::isTemplateTableReady()) { return []; } return self::rememberNonEmptyTemplateList( 'ext.templates.active_identifiers', fn () => Template::where('status', ExtensionStatus::Active->value) ->pluck('identifier') ->toArray() ); } /** * 특정 타입의 활성화된 템플릿 identifier 목록을 조회합니다. * * @param string $type 템플릿 타입 (admin, user) * @return array 활성화된 템플릿 identifier 배열 */ public static function getActiveTemplateIdentifiersByType(string $type): array { if (! self::isTemplateTableReady()) { return []; } return self::rememberNonEmptyTemplateList( "ext.templates.active_identifiers_{$type}", fn () => Template::where('status', ExtensionStatus::Active->value) ->where('type', $type) ->pluck('identifier') ->toArray() ); } /** * 설치된 템플릿 (active + inactive) identifier 목록을 조회합니다. * * @return array 설치된 템플릿 identifier 배열 */ public static function getInstalledTemplateIdentifiers(): array { if (! self::isTemplateTableReady()) { return []; } return self::rememberNonEmptyTemplateList( 'ext.templates.installed_identifiers', fn () => Template::whereIn('status', [ ExtensionStatus::Active->value, ExtensionStatus::Inactive->value, ])->pluck('identifier')->toArray() ); } /** * 목록 캐시를 조회하되, 빈 결과는 캐시에 남기지 않습니다. * * 확장 상태는 install/activate/update 도중 잠시 active 가 아닌 값으로 바뀐다. 그 창에서 * 누가 이 목록을 읽으면 빈 배열이 TTL(기본 하루) 동안 굳어, 작업이 끝난 뒤에도 모든 * 템플릿이 꺼진 것처럼 동작하며 스스로 회복되지 않는다. 빈 결과는 재계산 비용이 사실상 * 없는 단순 조회이므로 캐시하지 않는 편이 안전하다. * * @param string $key 캐시 키 * @param \Closure $resolver 목록 계산 클로저 * @return array 조회된 identifier 배열 */ private static function rememberNonEmptyTemplateList(string $key, \Closure $resolver): array { $cache = self::resolveTemplateStatusCache(); $cached = $cache->get($key); if (is_array($cached) && $cached !== []) { return $cached; } $fresh = $resolver(); if ($fresh !== []) { $cache->put( $key, $fresh, (int) g7_core_settings('cache.extension_status_ttl', 86400), ['ext.status', 'ext.templates'] ); } return $fresh; } /** * 템플릿 상태 캐시를 무효화합니다. * 템플릿 상태 변경 시 (install, activate, deactivate, uninstall) 호출해야 합니다. */ public static function invalidateTemplateStatusCache(): void { $cache = self::resolveTemplateStatusCache(); $cache->forget('ext.templates.active_identifiers'); $cache->forget('ext.templates.active_identifiers_admin'); $cache->forget('ext.templates.active_identifiers_user'); $cache->forget('ext.templates.installed_identifiers'); } /** * 설치 완료 상태에서는 `Schema::hasTable()` 호출을 건너뜁니다. * 인스톨러 이전 환경이나 테스트에서는 기존 체크 경로로 폴백합니다. * * 단, 마이그레이션 계열 명령 실행 중에는 `INSTALLER_COMPLETED=true` 라도 테이블이 * 아직 없을 수 있으므로(빈 DB 새 서버에 .env 복사 후 migrate 전) fast-path 를 * 신뢰하지 않고 실제 `Schema::hasTable()` 폴백으로 진입합니다. */ private static function isTemplateTableReady(): bool { if (! InstallerContext::isSchemaMutatingCommand() && config('app.installer_completed')) { return true; } try { DB::connection()->getPdo(); return Schema::hasTable('templates'); } catch (\Throwable $e) { return false; } } private static function resolveTemplateStatusCache(): CacheInterface { try { return app(CacheInterface::class); } catch (\Throwable $e) { return new CoreCacheDriver(config('cache.default', 'array')); } } }