활성화된 모듈 identifier 배열 */ public static function getActiveModuleIdentifiers(): array { if (! self::isExtensionTableReady('modules')) { return []; } return self::resolveStatusCache()->remember( 'ext.modules.active_identifiers', fn () => Module::where('status', ExtensionStatus::Active->value) ->pluck('identifier') ->toArray(), (int) g7_core_settings('cache.extension_status_ttl', 86400), ['ext.status', 'ext.modules'] ); } /** * 설치된 모듈 (active + inactive) identifier 목록을 조회합니다. * * @return array 설치된 모듈 identifier 배열 */ public static function getInstalledModuleIdentifiers(): array { if (! self::isExtensionTableReady('modules')) { return []; } return self::resolveStatusCache()->remember( 'ext.modules.installed_identifiers', fn () => Module::whereIn('status', [ ExtensionStatus::Active->value, ExtensionStatus::Inactive->value, ])->pluck('identifier')->toArray(), (int) g7_core_settings('cache.extension_status_ttl', 86400), ['ext.status', 'ext.modules'] ); } /** * 모듈 상태 캐시를 무효화합니다. * 모듈 상태 변경 시 (install, activate, deactivate, uninstall) 호출해야 합니다. */ public static function invalidateModuleStatusCache(): void { $cache = self::resolveStatusCache(); $cache->forget('ext.modules.active_identifiers'); $cache->forget('ext.modules.installed_identifiers'); } /** * DB 연결 + 테이블 존재 여부를 확인합니다 (인스톨러 안전성). * * 설치 완료 상태(`config('app.installer_completed')`)일 때는 테이블 존재를 * 전제로 하여 `Schema::hasTable()` 호출을 건너뜁니다. 인스톨러 이전 환경이나 * 테스트에서는 기존 체크 경로로 폴백합니다. * * 단, 마이그레이션 계열 명령 실행 중에는 `INSTALLER_COMPLETED=true` 라도 테이블이 * 아직 없을 수 있으므로(빈 DB 새 서버에 .env 복사 후 migrate 전) fast-path 를 * 신뢰하지 않고 실제 `Schema::hasTable()` 폴백으로 진입합니다. */ private static function isExtensionTableReady(string $table): bool { if (! InstallerContext::isSchemaMutatingCommand() && config('app.installer_completed')) { return true; } try { DB::connection()->getPdo(); return Schema::hasTable($table); } catch (\Throwable $e) { return false; } } /** * CacheInterface 인스턴스를 lazy 조회합니다 (register 시점 안전). */ private static function resolveStatusCache(): CacheInterface { try { return app(CacheInterface::class); } catch (\Throwable $e) { return new CoreCacheDriver(config('cache.default', 'array')); } } }