= 신버전` 요구 시 * `validateAndDeactivateIncompatibleExtensions` 가 전 확장을 자동 비활성화). * * env 를 우선 읽는 본 구현은 config cache 유무와 무관하게 spawn 이 전달한 버전을 그대로 * 신뢰한다. 일반 요청 경로에서는 `APP_VERSION` 이 `.env` 에 기록된 값 그대로이므로 * 동작에 차이가 없다. * * 규정 예외: "env() 는 config 파일에서만 사용" 규칙의 본문 예외. 정당성은 버전 판정이 * config cache 우회를 요구하기 때문이다. * * @return string 코어 버전 문자열 (예: "7.0.0-beta.4") */ public static function getCoreVersion(): string { $envVersion = $_ENV['APP_VERSION'] ?? $_SERVER['APP_VERSION'] ?? getenv('APP_VERSION'); if (is_string($envVersion) && $envVersion !== '') { return $envVersion; } return config('app.version'); } /** * 버전 제약 조건 충족 여부 확인 * * @param string $constraint Semantic Versioning 제약 문자열 * @return bool 충족 여부 */ public static function satisfies(string $constraint): bool { try { return Semver::satisfies(self::getCoreVersion(), $constraint); } catch (Exception $e) { Log::warning(__('extensions.errors.version_check_failed'), [ 'constraint' => $constraint, 'error' => $e->getMessage(), ]); return false; } } /** * 확장의 코어 버전 호환성 검증 * * @param string|null $requiredVersion 요구 버전 제약 * @param string $identifier 확장 식별자 * @param string $type 확장 타입 (module, plugin, template) * @return bool 항상 true (검증 실패 시 예외 발생) * * @throws Exception 버전 미충족 시 */ public static function validateExtension( ?string $requiredVersion, string $identifier, string $type ): bool { if ($requiredVersion === null) { return true; } if (! self::satisfies($requiredVersion)) { throw new CoreVersionMismatchException( $type, $identifier, $requiredVersion, self::getCoreVersion(), ); } return true; } /** * 확장의 코어 버전 호환성 확인 (예외 없음) * * @param string|null $requiredVersion 요구 버전 제약 * @return bool 호환 여부 */ public static function isCompatible(?string $requiredVersion): bool { if ($requiredVersion === null) { return true; } return self::satisfies($requiredVersion); } /** * 버전 검증 캐시 키 생성 * * @param string $type 확장 타입 (modules, plugins, templates) * @return string 캐시 키 */ public static function getCacheKey(string $type): string { return self::CACHE_PREFIX.$type.'.'.self::getCoreVersion(); } /** * 모든 버전 검증 캐시 삭제 */ public static function clearCache(): void { $cache = self::resolveCache(); $cache->forget(self::getCacheKey('modules')); $cache->forget(self::getCacheKey('plugins')); $cache->forget(self::getCacheKey('templates')); } /** * CacheInterface 인스턴스를 컨테이너에서 lazy 조회합니다. * * 컨테이너 미구성 환경(예: 일부 단위 테스트)에서도 동작하도록 * fallback 으로 직접 CoreCacheDriver 를 생성합니다. * * @return CacheInterface */ private static function resolveCache(): CacheInterface { try { return app(CacheInterface::class); } catch (\Throwable $e) { return new CoreCacheDriver(config('cache.default', 'array')); } } /** * 캐시 TTL 반환 * * @return int 캐시 유효 시간 (초) */ public static function getCacheTtl(): int { return (int) g7_core_settings('cache.version_check_ttl', self::CACHE_TTL); } }