From d4c0d2d864f8579a63cdb2d60d2ae641f6e5f34e Mon Sep 17 00:00:00 2001 From: Zyra Date: Thu, 3 Sep 2026 11:06:43 +0000 Subject: [PATCH] Fix #127: ModelResolver cachet resultaten per process MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolve() deed voorheen bij elke aanroep opnieuw method_exists/ property_exists-checks, de config-lookup voor de namespace en de BuildoraValidator-validatie, ook al is de uitkomst voor een gegeven resourceClass altijd hetzelfde binnen één process. Dat werk wordt nu één keer per resourceClass gedaan en daarna uit een static array gecached (private static array $cache, resolve() -> ??= doResolve()). clearCache() toegevoegd zodat tests state kunnen resetten en de cache niet ongewenst tussen testcases lekt. --- src/Resources/ModelResolver.php | 34 +++++++++ .../Fixtures/ModelResolverCacheTestModel.php | 16 ++++ .../ModelResolverCacheTestResource.php | 27 +++++++ .../ModelResolverCacheTestResourceTwo.php | 26 +++++++ tests/Unit/Resources/ModelResolverTest.php | 75 +++++++++++++++++++ 5 files changed, 178 insertions(+) create mode 100644 tests/Fixtures/ModelResolverCacheTestModel.php create mode 100644 tests/Fixtures/ModelResolverCacheTestResource.php create mode 100644 tests/Fixtures/ModelResolverCacheTestResourceTwo.php create mode 100644 tests/Unit/Resources/ModelResolverTest.php diff --git a/src/Resources/ModelResolver.php b/src/Resources/ModelResolver.php index 47b2439..4e6759f 100644 --- a/src/Resources/ModelResolver.php +++ b/src/Resources/ModelResolver.php @@ -11,13 +11,47 @@ */ class ModelResolver { + /** + * Per-process cache of resolved model classes, keyed by resource class. + * + * @var array + */ + private static array $cache = []; + /** * Resolve the model class for a given Buildora resource class. * + * Results are cached per process so that repeated resolutions of the + * same resource class (e.g. across a single request) don't redo the + * same reflection/config lookups and validation. + * * @param string $resourceClass The fully qualified class name of the resource. * @return string The fully qualified class name of the associated model. */ public static function resolve(string $resourceClass): string + { + return self::$cache[$resourceClass] ??= self::doResolve($resourceClass); + } + + /** + * Clear the per-process resolution cache. + * + * Mainly useful in tests to avoid state leaking between test cases. + * + * @return void + */ + public static function clearCache(): void + { + self::$cache = []; + } + + /** + * Perform the actual resolution of the model class for a resource. + * + * @param string $resourceClass The fully qualified class name of the resource. + * @return string The fully qualified class name of the associated model. + */ + private static function doResolve(string $resourceClass): string { if (method_exists($resourceClass, 'modelClass')) { $modelClass = $resourceClass::modelClass(); diff --git a/tests/Fixtures/ModelResolverCacheTestModel.php b/tests/Fixtures/ModelResolverCacheTestModel.php new file mode 100644 index 0000000..8837584 --- /dev/null +++ b/tests/Fixtures/ModelResolverCacheTestModel.php @@ -0,0 +1,16 @@ +assertSame(ModelResolverCacheTestModel::class, $modelClass); + } + + /** @test */ + public function it_caches_the_resolution_so_repeated_calls_do_not_redo_the_work(): void + { + ModelResolver::resolve(ModelResolverCacheTestResource::class); + ModelResolver::resolve(ModelResolverCacheTestResource::class); + $modelClass = ModelResolver::resolve(ModelResolverCacheTestResource::class); + + // modelClass() should only have been invoked once: subsequent calls + // must be served from the per-process cache. + $this->assertSame(1, ModelResolverCacheTestResource::$callCount); + $this->assertSame(ModelResolverCacheTestModel::class, $modelClass); + } + + /** @test */ + public function it_keeps_separate_cache_entries_per_resource_class(): void + { + ModelResolver::resolve(ModelResolverCacheTestResource::class); + ModelResolver::resolve(ModelResolverCacheTestResourceTwo::class); + ModelResolver::resolve(ModelResolverCacheTestResource::class); + ModelResolver::resolve(ModelResolverCacheTestResourceTwo::class); + + $this->assertSame(1, ModelResolverCacheTestResource::$callCount); + $this->assertSame(1, ModelResolverCacheTestResourceTwo::$callCount); + } + + /** @test */ + public function clear_cache_forces_the_next_call_to_resolve_again(): void + { + ModelResolver::resolve(ModelResolverCacheTestResource::class); + ModelResolver::clearCache(); + ModelResolver::resolve(ModelResolverCacheTestResource::class); + + $this->assertSame(2, ModelResolverCacheTestResource::$callCount); + } +}