Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/Resources/ModelResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,47 @@
*/
class ModelResolver
{
/**
* Per-process cache of resolved model classes, keyed by resource class.
*
* @var array<string, string>
*/
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();
Expand Down
16 changes: 16 additions & 0 deletions tests/Fixtures/ModelResolverCacheTestModel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Ginkelsoft\Buildora\Tests\Fixtures;

use Illuminate\Database\Eloquent\Model;
use Ginkelsoft\Buildora\Traits\HasBuildora;

/**
* Fixture model used to test ModelResolver caching behaviour.
*/
class ModelResolverCacheTestModel extends Model
{
use HasBuildora;

protected $table = 'model_resolver_cache_test_models';
}
27 changes: 27 additions & 0 deletions tests/Fixtures/ModelResolverCacheTestResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Ginkelsoft\Buildora\Tests\Fixtures;

/**
* Fixture resource used to test ModelResolver caching behaviour.
*
* Tracks how many times modelClass() has been called so tests can assert
* that ModelResolver::resolve() only computes the resolution once per
* resource class.
*/
class ModelResolverCacheTestResource
{
public static int $callCount = 0;

public static function modelClass(): string
{
self::$callCount++;

return ModelResolverCacheTestModel::class;
}

public static function reset(): void
{
self::$callCount = 0;
}
}
26 changes: 26 additions & 0 deletions tests/Fixtures/ModelResolverCacheTestResourceTwo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Ginkelsoft\Buildora\Tests\Fixtures;

/**
* Second fixture resource used to test ModelResolver caching behaviour.
*
* Used alongside ModelResolverCacheTestResource to assert that cache
* entries are kept separate per resource class.
*/
class ModelResolverCacheTestResourceTwo
{
public static int $callCount = 0;

public static function modelClass(): string
{
self::$callCount++;

return ModelResolverCacheTestModel::class;
}

public static function reset(): void
{
self::$callCount = 0;
}
}
75 changes: 75 additions & 0 deletions tests/Unit/Resources/ModelResolverTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace Ginkelsoft\Buildora\Tests\Unit\Resources;

use Ginkelsoft\Buildora\Resources\ModelResolver;
use Ginkelsoft\Buildora\Tests\Fixtures\ModelResolverCacheTestModel;
use Ginkelsoft\Buildora\Tests\Fixtures\ModelResolverCacheTestResource;
use Ginkelsoft\Buildora\Tests\Fixtures\ModelResolverCacheTestResourceTwo;
use Ginkelsoft\Buildora\Tests\TestCase;

class ModelResolverTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();

// Avoid state leaking between tests: each test starts with an empty
// resolver cache and a clean call counter on the fixture resources.
ModelResolver::clearCache();
ModelResolverCacheTestResource::reset();
ModelResolverCacheTestResourceTwo::reset();
}

protected function tearDown(): void
{
ModelResolver::clearCache();
ModelResolverCacheTestResource::reset();
ModelResolverCacheTestResourceTwo::reset();

parent::tearDown();
}

/** @test */
public function it_resolves_the_model_class_for_a_resource(): void
{
$modelClass = ModelResolver::resolve(ModelResolverCacheTestResource::class);

$this->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);
}
}
Loading