From a97e22b832c00e78f9aab7e0e507dab06a3b491e Mon Sep 17 00:00:00 2001 From: wadakatu Date: Tue, 17 Mar 2026 18:05:46 +0900 Subject: [PATCH] refactor: add clearCache/evict to OpenApiSpecLoader and free cache after coverage Add clearCache() and evict() methods to OpenApiSpecLoader so cached spec data can be released without losing configuration. Call clearCache() in the coverage extension after computing results, freeing large spec arrays from memory at the earliest opportunity. --- src/OpenApiSpecLoader.php | 16 +++++++++ src/PHPUnit/OpenApiCoverageExtension.php | 3 ++ tests/Unit/OpenApiSpecLoaderTest.php | 41 ++++++++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/src/OpenApiSpecLoader.php b/src/OpenApiSpecLoader.php index 8e0ed1c..8a8cc9b 100644 --- a/src/OpenApiSpecLoader.php +++ b/src/OpenApiSpecLoader.php @@ -79,6 +79,22 @@ public static function load(string $specName): array return $decoded; } + /** + * Clear only the cached specs, keeping basePath and stripPrefixes intact. + */ + public static function clearCache(): void + { + self::$cache = []; + } + + /** + * Remove a single spec from the cache. + */ + public static function evict(string $specName): void + { + unset(self::$cache[$specName]); + } + public static function reset(): void { self::$basePath = null; diff --git a/src/PHPUnit/OpenApiCoverageExtension.php b/src/PHPUnit/OpenApiCoverageExtension.php index a995ad6..87b1f94 100644 --- a/src/PHPUnit/OpenApiCoverageExtension.php +++ b/src/PHPUnit/OpenApiCoverageExtension.php @@ -79,6 +79,9 @@ public function notify(ExecutionFinished $event): void { $results = $this->computeAllResults(); + // Free cached spec data now that coverage has been computed + OpenApiSpecLoader::clearCache(); + if ($results === []) { return; } diff --git a/tests/Unit/OpenApiSpecLoaderTest.php b/tests/Unit/OpenApiSpecLoaderTest.php index 2c1b62c..57aa95d 100644 --- a/tests/Unit/OpenApiSpecLoaderTest.php +++ b/tests/Unit/OpenApiSpecLoaderTest.php @@ -105,4 +105,45 @@ public function reset_clears_all_state(): void $this->expectException(RuntimeException::class); OpenApiSpecLoader::getBasePath(); } + + #[Test] + public function clear_cache_keeps_config(): void + { + $fixturesPath = __DIR__ . '/../fixtures/specs'; + OpenApiSpecLoader::configure($fixturesPath, ['/api']); + + // Load to populate cache + OpenApiSpecLoader::load('petstore-3.0'); + + OpenApiSpecLoader::clearCache(); + + // Config is preserved + $this->assertSame($fixturesPath, OpenApiSpecLoader::getBasePath()); + $this->assertSame(['/api'], OpenApiSpecLoader::getStripPrefixes()); + + // Cache is cleared — next load reads from disk again + $spec = OpenApiSpecLoader::load('petstore-3.0'); + $this->assertSame('3.0.3', $spec['openapi']); + } + + #[Test] + public function evict_removes_single_spec_from_cache(): void + { + $fixturesPath = __DIR__ . '/../fixtures/specs'; + OpenApiSpecLoader::configure($fixturesPath); + + // Load two specs + $first30 = OpenApiSpecLoader::load('petstore-3.0'); + $first31 = OpenApiSpecLoader::load('petstore-3.1'); + + // Evict only 3.0 + OpenApiSpecLoader::evict('petstore-3.0'); + + // 3.1 still cached (same reference) + $this->assertSame($first31, OpenApiSpecLoader::load('petstore-3.1')); + + // 3.0 reloaded from disk (equal but fresh instance) + $reloaded30 = OpenApiSpecLoader::load('petstore-3.0'); + $this->assertSame($first30, $reloaded30); + } }