From 1bb7ea9a94324a2633b32b1b5d9de63ee2a81e9e Mon Sep 17 00:00:00 2001 From: Mazen Touati Date: Wed, 3 Aug 2022 12:35:05 +0200 Subject: [PATCH 01/14] Add back support for older versions of php --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 35f0307..13008b4 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,8 @@ "require": { "php" : "^7.4|^8.0|^8.1", "illuminate/support": "^7.0|^8.0|^9.0", - "illuminate/http": "^7.0|^8.0|^9.0" + "illuminate/http": "^7.0|^8.0|^9.0", + "ext-json": "*" }, "require-dev": { "phpunit/phpunit": "^9.5", From 66b5cf9e9561b9f56784c8d0aba69bae95ca6f4e Mon Sep 17 00:00:00 2001 From: Mazen Touati Date: Wed, 3 Aug 2022 12:35:58 +0200 Subject: [PATCH 02/14] Add routing manifest resolution strategy --- publishable/config/api.php | 11 +++++++++++ src/APIResourceManager.php | 30 +++++++++++++++++++++++------ tests/Fixtures/config/multi.php | 10 ++++++++++ tests/Fixtures/config/simple.php | 10 ++++++++++ tests/ResourcePathResolveTest.php | 32 +++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 6 deletions(-) diff --git a/publishable/config/api.php b/publishable/config/api.php index 34e6d3a..60c197b 100644 --- a/publishable/config/api.php +++ b/publishable/config/api.php @@ -49,4 +49,15 @@ // 'route_prefix' => 'app' + /* + |-------------------------------------------------------------------------- + | Routing manifest file + |-------------------------------------------------------------------------- + | + | Here is the path for the routing manifest that will cache the resolution + | strategy for each Resource in every version. + */ + + 'routing_manifest_path' => storage_path('api-resources/manifest.json'), + ]; diff --git a/src/APIResourceManager.php b/src/APIResourceManager.php index 0f0fc53..0c76fdf 100644 --- a/src/APIResourceManager.php +++ b/src/APIResourceManager.php @@ -4,6 +4,8 @@ use Exception; use Illuminate\Http\Resources\Json\JsonResource; +use Illuminate\Session\Store; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Juampi92\APIResources\Exceptions\ResourceNotFoundException; use Juampi92\APIResources\Exceptions\WrongConfigurationException; @@ -207,7 +209,6 @@ public function resolveClassname(string $classname) /** * Returns the classname with the version considering. * - * @param string $classname * @param bool $forceLatest Set to true if last version is required * * @return class-string @@ -216,15 +217,32 @@ protected function parseClassname(string $classname, bool $forceLatest = false): { $version = $forceLatest ? $this->latest : $this->current; + $version = Str::start($version, 'v'); + + return $this->resolveClassnameFromRoutingManifest($classname, $version) + ?? $this->resolveClassnameFromFilesystem($classname, $version); + } + + protected function resolveClassnameFromRoutingManifest(string $classname, string $version): ?string + { + $routingManifest = json_decode(Storage::get(config('api.routing_manifest_path')), true); + + if (! $routingManifest) { + return null; + } + + return $routingManifest[$classname][$version] ?? null; + } + + protected function resolveClassnameFromFilesystem(string $classname, string $version): ?string + { if (! empty($this->resources)) { - $path = $this->resources . "\\v{$version}\\" . Str::after($classname, $this->resources . "\\"); + $path = $this->resources . "\\{$version}\\" . Str::after($classname, $this->resources . "\\"); } else { - $path = "v{$version}\\" . $classname; + $path = "{$version}\\" . $classname; } - $path = "\\{$this->path}\\{$path}"; - - return $path; + return "\\{$this->path}\\{$path}"; } /** diff --git a/tests/Fixtures/config/multi.php b/tests/Fixtures/config/multi.php index 09dbaea..e7637ae 100644 --- a/tests/Fixtures/config/multi.php +++ b/tests/Fixtures/config/multi.php @@ -53,4 +53,14 @@ 'collection' => 'Collections', ], + /* + |-------------------------------------------------------------------------- + | Routing manifest file + |-------------------------------------------------------------------------- + | + | Here is the path for the routing manifest that will cache the resolution + | strategy for each Resource in every version. + */ + + 'routing_manifest_path' => storage_path('api-resources/manifest.json'), ]; diff --git a/tests/Fixtures/config/simple.php b/tests/Fixtures/config/simple.php index b2f9f57..d531f07 100644 --- a/tests/Fixtures/config/simple.php +++ b/tests/Fixtures/config/simple.php @@ -35,4 +35,14 @@ 'resources' => 'App', + /* + |-------------------------------------------------------------------------- + | Routing manifest file + |-------------------------------------------------------------------------- + | + | Here is the path for the routing manifest that will cache the resolution + | strategy for each Resource in every version. + */ + + 'routing_manifest_path' => storage_path('api-resources/manifest.json'), ]; diff --git a/tests/ResourcePathResolveTest.php b/tests/ResourcePathResolveTest.php index bdc929d..43c7141 100644 --- a/tests/ResourcePathResolveTest.php +++ b/tests/ResourcePathResolveTest.php @@ -2,6 +2,7 @@ namespace Juampi92\APIResources\Tests; +use Illuminate\Support\Facades\Storage; use Juampi92\APIResources\APIResourceManager; class ResourcePathResolveTest extends TestCase @@ -74,4 +75,35 @@ public function test_it_can_resolve_resources_prefix_empty() $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['User']); $this->assertEquals('\\App\\Http\\Resources\\v1\\User', $classname); } + + public function test_it_prioritize_resolving_from_route_manifest() + { + $manifest = [ + 'App\User' => [ + 'v1' => '\\App\\Http\\Resources\\App\\v2\\Cached\\User', + 'v2' => '\\App\\Http\\Resources\\App\\v2\\Cached\\User', + 'v3' => '\\App\\Http\\Resources\\App\\v3\\Cached\\User', + ], + ]; + + Storage::put(config('api.routing_manifest_path'), json_encode($manifest)); + + $this->apiResourceManager->setVersion('1'); + $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['App\User']); + $this->assertEquals($manifest['App\User']['v1'], $classname); + + $this->apiResourceManager->setVersion('2'); + $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['App\User']); + $this->assertEquals($manifest['App\User']['v2'], $classname); + + $this->apiResourceManager->setVersion('3'); + $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['App\User']); + $this->assertEquals($manifest['App\User']['v3'], $classname); + + $this->apiResourceManager->setVersion('4'); + $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['App\Article']); + $this->assertEquals('\\App\\Http\\Resources\\App\\v4\\Article', $classname); + + Storage::delete(config('api.routing_manifest_path')); + } } From 0dcf7709e6880e5cb56db366df178e11c5822037 Mon Sep 17 00:00:00 2001 From: Mazen Touati Date: Wed, 3 Aug 2022 12:36:46 +0200 Subject: [PATCH 03/14] Link the facade to the implementation --- src/Facades/APIResource.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Facades/APIResource.php b/src/Facades/APIResource.php index 7740cdd..cb93456 100644 --- a/src/Facades/APIResource.php +++ b/src/Facades/APIResource.php @@ -14,6 +14,8 @@ * @method static \Illuminate\Http\Resources\Json\Resource collection(string $classname, ...$args) Resolves the classname and instantiates the resource as a collection * @method static string getRoute(string $name, array $parameters, bool $absolute) * @method static string getRouteName(string $name) + * + * @see \Juampi92\APIResources\APIResourceManager */ class APIResource extends Facade { From ae2a23eb08244581e2b4ebfa8f9de294e60e6d63 Mon Sep 17 00:00:00 2001 From: Mazen Touati Date: Wed, 3 Aug 2022 12:40:53 +0200 Subject: [PATCH 04/14] Cleanup if statement --- src/APIResourceManager.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/APIResourceManager.php b/src/APIResourceManager.php index 0c76fdf..39473b3 100644 --- a/src/APIResourceManager.php +++ b/src/APIResourceManager.php @@ -236,11 +236,9 @@ protected function resolveClassnameFromRoutingManifest(string $classname, string protected function resolveClassnameFromFilesystem(string $classname, string $version): ?string { - if (! empty($this->resources)) { - $path = $this->resources . "\\{$version}\\" . Str::after($classname, $this->resources . "\\"); - } else { - $path = "{$version}\\" . $classname; - } + $path = ! empty($this->resources) + ? $this->resources . "\\{$version}\\" . Str::after($classname, $this->resources . "\\") + : "{$version}\\" . $classname; return "\\{$this->path}\\{$path}"; } From 0e7adbd7ba8a134159d065a3658de2bdda68ded2 Mon Sep 17 00:00:00 2001 From: Mazen Touati Date: Wed, 3 Aug 2022 13:56:31 +0200 Subject: [PATCH 05/14] User bootstrap path to store the routing maifest --- publishable/config/api.php | 2 +- tests/Fixtures/config/multi.php | 2 +- tests/Fixtures/config/simple.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/publishable/config/api.php b/publishable/config/api.php index 60c197b..606d048 100644 --- a/publishable/config/api.php +++ b/publishable/config/api.php @@ -58,6 +58,6 @@ | strategy for each Resource in every version. */ - 'routing_manifest_path' => storage_path('api-resources/manifest.json'), + 'routing_manifest_path' => app()->bootstrapPath('api-resources-manifest.json'), ]; diff --git a/tests/Fixtures/config/multi.php b/tests/Fixtures/config/multi.php index e7637ae..9cd98d3 100644 --- a/tests/Fixtures/config/multi.php +++ b/tests/Fixtures/config/multi.php @@ -62,5 +62,5 @@ | strategy for each Resource in every version. */ - 'routing_manifest_path' => storage_path('api-resources/manifest.json'), + 'routing_manifest_path' => app()->bootstrapPath('api-resources-manifest.json'), ]; diff --git a/tests/Fixtures/config/simple.php b/tests/Fixtures/config/simple.php index d531f07..8af94dd 100644 --- a/tests/Fixtures/config/simple.php +++ b/tests/Fixtures/config/simple.php @@ -44,5 +44,5 @@ | strategy for each Resource in every version. */ - 'routing_manifest_path' => storage_path('api-resources/manifest.json'), + 'routing_manifest_path' => app()->bootstrapPath('api-resources-manifest.json'), ]; From 02f3b966e8be1921b90611ba48d79dd346b40725 Mon Sep 17 00:00:00 2001 From: Mazen Touati Date: Wed, 3 Aug 2022 16:15:56 +0200 Subject: [PATCH 06/14] Fix fixture classname --- tests/Fixtures/Models/Post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Fixtures/Models/Post.php b/tests/Fixtures/Models/Post.php index 760754f..dddc3b2 100644 --- a/tests/Fixtures/Models/Post.php +++ b/tests/Fixtures/Models/Post.php @@ -2,7 +2,7 @@ namespace Juampi92\APIResources\Tests\Fixtures\Models; -class User +class Post { // Simulate Eloquent's dynamic attributes public $id = 2; From 58c663620bd733c295eca418b855ab28148c73ba Mon Sep 17 00:00:00 2001 From: Mazen Touati Date: Wed, 3 Aug 2022 16:16:26 +0200 Subject: [PATCH 07/14] Add cached models fixtures --- tests/Fixtures/Resources/Cached/v2/User.php | 20 ++++++++++++++++++++ tests/Fixtures/Resources/Cached/v3/User.php | 18 ++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 tests/Fixtures/Resources/Cached/v2/User.php create mode 100644 tests/Fixtures/Resources/Cached/v3/User.php diff --git a/tests/Fixtures/Resources/Cached/v2/User.php b/tests/Fixtures/Resources/Cached/v2/User.php new file mode 100644 index 0000000..a2f7caf --- /dev/null +++ b/tests/Fixtures/Resources/Cached/v2/User.php @@ -0,0 +1,20 @@ + $this->id, + 'name' => $this->name, + 'rank' => api_resource('App\Rank')->make($this->rank()), + 'v' => 2, + ]; + } +} diff --git a/tests/Fixtures/Resources/Cached/v3/User.php b/tests/Fixtures/Resources/Cached/v3/User.php new file mode 100644 index 0000000..0a6e11f --- /dev/null +++ b/tests/Fixtures/Resources/Cached/v3/User.php @@ -0,0 +1,18 @@ + $this->id, + 'name' => $this->name, + 'rank' => api_resource('App\Rank')->make($this->rank()), + 'v' => 3, + ]; + } +} From 222366a72adab1039a41c723a89a36f2890be764 Mon Sep 17 00:00:00 2001 From: Mazen Touati Date: Wed, 3 Aug 2022 16:17:08 +0200 Subject: [PATCH 08/14] Migrate resolution test and use facade instead --- tests/APIResourceCollectionTest.php | 2 +- tests/APIResourceTest.php | 79 ++++++++++++++++++++++++----- tests/APIResourcesMultipleTest.php | 11 ++-- tests/ResourcePathResolveTest.php | 32 ------------ 4 files changed, 73 insertions(+), 51 deletions(-) diff --git a/tests/APIResourceCollectionTest.php b/tests/APIResourceCollectionTest.php index 1c1e723..60986c8 100644 --- a/tests/APIResourceCollectionTest.php +++ b/tests/APIResourceCollectionTest.php @@ -48,7 +48,7 @@ public function test_collection_resource() { $users = collect([ new Fixtures\Models\User(), - new Fixtures\Models\User() + new Fixtures\Models\User(), ]); APIResourceFacade::setVersion('2'); diff --git a/tests/APIResourceTest.php b/tests/APIResourceTest.php index 3a086fe..26ba9ef 100644 --- a/tests/APIResourceTest.php +++ b/tests/APIResourceTest.php @@ -2,10 +2,13 @@ namespace Juampi92\APIResources\Tests; +use Illuminate\Support\Facades\Storage; use Juampi92\APIResources\APIResource; -use Juampi92\APIResources\APIResourceManager; use Juampi92\APIResources\Exceptions\ResourceNotFoundException; use Juampi92\APIResources\Facades\APIResource as APIResourceFacade; +use Juampi92\APIResources\Tests\Fixtures\Resources\App\v3\Post as PostV3AppResource; +use Juampi92\APIResources\Tests\Fixtures\Resources\Cached\v2\User as UserV2Cached; +use Juampi92\APIResources\Tests\Fixtures\Resources\Cached\v3\User as UserV3Cached; class APIResourceTest extends TestCase { @@ -66,15 +69,14 @@ public function test_fallback_to_latest_version() { // Set latest as 3 config(['api.version' => 3]); - $resourceManager = new APIResourceManager(); - $resourceManager->setVersion('2'); + APIResourceFacade::setVersion('2'); - $this->assertEquals(2, $resourceManager->getVersion()); - $this->assertEquals(3, $resourceManager->getLatestVersion()); + $this->assertEquals(2, APIResourceFacade::getVersion()); + $this->assertEquals(3, APIResourceFacade::getLatestVersion()); - $resource = $resourceManager->resolve('App\Post'); - $this->assertEquals('\\'.\Juampi92\APIResources\Tests\Fixtures\Resources\App\v3\Post::class, $resource->getClass()); + $resource = APIResourceFacade::resolve('App\Post'); + $this->assertEquals('\\'. PostV3AppResource::class, $resource->getClass()); } public function test_fails_if_no_fallback() @@ -83,9 +85,10 @@ public function test_fails_if_no_fallback() // Set latest as 2 config(['api.version' => 2]); - $resourceManager = new APIResourceManager(); - $resource = $resourceManager->resolve('App\Comment'); + APIResourceFacade::setVersion(2); + + APIResourceFacade::resolve('App\Comment'); } public function test_nested_resources_simple() @@ -139,12 +142,11 @@ public function test_nested_resources_with_fallback() public function test_without_resource_folder() { config(['api.resources' => '']); - $resourceManager = new APIResourceManager(); $user = new Fixtures\Models\User(); - $resourceManager->setVersion('1'); - $resource = $resourceManager->resolve('User')->make($user); + APIResourceFacade::setVersion('1'); + $resource = APIResourceFacade::resolve('User')->make($user); $this->assertInstanceOf(Fixtures\Resources\v1\User::class, $resource); @@ -154,4 +156,57 @@ public function test_without_resource_folder() 'v' => 1, ]); } + + public function test_it_prioritize_resolving_from_route_manifest(): void + { + // Arrange + + $manifest = [ + 'App\User' => [ + 'v1' => UserV2Cached::class, + 'v2' => UserV2Cached::class, + 'v3' => UserV3Cached::class, + ], + ]; + + Storage::put(config('api.routing_manifest_path'), json_encode($manifest)); + + $user = new Fixtures\Models\User(); + + // Act & Assert + + APIResourceFacade::setVersion('1'); + + $this->assertInstanceOf( + UserV2Cached::class, + APIResourceFacade::resolve('App\User')->make($user) + ); + + // Act & Assert + + APIResourceFacade::setVersion('2'); + + $this->assertInstanceOf( + UserV2Cached::class, + APIResourceFacade::resolve('App\User')->make($user) + ); + + // Act & Assert + + APIResourceFacade::setVersion('3'); + + $this->assertInstanceOf( + UserV3Cached::class, + APIResourceFacade::resolve('App\User')->make($user) + ); + + $this->assertInstanceOf( + PostV3AppResource::class, + APIResourceFacade::resolve('App\Post')->make(new Fixtures\Models\Post()) + ); + + // Cleanup + + Storage::delete(config('api.routing_manifest_path')); + } } diff --git a/tests/APIResourcesMultipleTest.php b/tests/APIResourcesMultipleTest.php index 1f32939..b991dbd 100644 --- a/tests/APIResourcesMultipleTest.php +++ b/tests/APIResourcesMultipleTest.php @@ -2,7 +2,7 @@ namespace Juampi92\APIResources\Tests; -use Juampi92\APIResources\APIResourceManager; +use Juampi92\APIResources\Facades\APIResource; class APIResourcesMultipleTest extends TestCase { @@ -23,12 +23,11 @@ public function test_nested_resources_with_fallback() ], 'api.default' => 'app', ]); - $resourceManager = new APIResourceManager(); $user = new Fixtures\Models\User(); - $resourceManager->setVersion('1', 'app'); - $resource = $resourceManager->resolve('App\User')->make($user); + APIResource::setVersion('1', 'app'); + $resource = APIResource::resolve('App\User')->make($user); $this->assertInstanceOf(Fixtures\Resources\App\v2\User::class, $resource); @@ -36,8 +35,8 @@ public function test_nested_resources_with_fallback() * Now change to the desktop API */ - $resourceManager->setVersion('2', 'desktop'); - $resource = $resourceManager->resolve('Api\User')->make($user); + APIResource::setVersion('2', 'desktop'); + $resource = APIResource::resolve('Api\User')->make($user); $this->assertInstanceOf(Fixtures\Resources\Api\v2\User::class, $resource); } diff --git a/tests/ResourcePathResolveTest.php b/tests/ResourcePathResolveTest.php index 43c7141..bdc929d 100644 --- a/tests/ResourcePathResolveTest.php +++ b/tests/ResourcePathResolveTest.php @@ -2,7 +2,6 @@ namespace Juampi92\APIResources\Tests; -use Illuminate\Support\Facades\Storage; use Juampi92\APIResources\APIResourceManager; class ResourcePathResolveTest extends TestCase @@ -75,35 +74,4 @@ public function test_it_can_resolve_resources_prefix_empty() $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['User']); $this->assertEquals('\\App\\Http\\Resources\\v1\\User', $classname); } - - public function test_it_prioritize_resolving_from_route_manifest() - { - $manifest = [ - 'App\User' => [ - 'v1' => '\\App\\Http\\Resources\\App\\v2\\Cached\\User', - 'v2' => '\\App\\Http\\Resources\\App\\v2\\Cached\\User', - 'v3' => '\\App\\Http\\Resources\\App\\v3\\Cached\\User', - ], - ]; - - Storage::put(config('api.routing_manifest_path'), json_encode($manifest)); - - $this->apiResourceManager->setVersion('1'); - $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['App\User']); - $this->assertEquals($manifest['App\User']['v1'], $classname); - - $this->apiResourceManager->setVersion('2'); - $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['App\User']); - $this->assertEquals($manifest['App\User']['v2'], $classname); - - $this->apiResourceManager->setVersion('3'); - $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['App\User']); - $this->assertEquals($manifest['App\User']['v3'], $classname); - - $this->apiResourceManager->setVersion('4'); - $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['App\Article']); - $this->assertEquals('\\App\\Http\\Resources\\App\\v4\\Article', $classname); - - Storage::delete(config('api.routing_manifest_path')); - } } From c2c84f5bdbc6d05439ea52a0a717b57447ab509b Mon Sep 17 00:00:00 2001 From: Mazen Touati Date: Wed, 3 Aug 2022 16:17:48 +0200 Subject: [PATCH 09/14] Refactor to resolver strategies --- src/APIResourceManager.php | 65 ++++++------------------------ src/Facades/APIResource.php | 3 ++ src/Resolvers/CacheResolver.php | 28 +++++++++++++ src/Resolvers/PathResolver.php | 43 ++++++++++++++++++++ src/Resolvers/Resolver.php | 67 +++++++++++++++++++++++++++++++ src/Resolvers/ResolverFactory.php | 32 +++++++++++++++ 6 files changed, 185 insertions(+), 53 deletions(-) create mode 100644 src/Resolvers/CacheResolver.php create mode 100644 src/Resolvers/PathResolver.php create mode 100644 src/Resolvers/Resolver.php create mode 100644 src/Resolvers/ResolverFactory.php diff --git a/src/APIResourceManager.php b/src/APIResourceManager.php index 39473b3..74071d7 100644 --- a/src/APIResourceManager.php +++ b/src/APIResourceManager.php @@ -4,11 +4,11 @@ use Exception; use Illuminate\Http\Resources\Json\JsonResource; -use Illuminate\Session\Store; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Juampi92\APIResources\Exceptions\ResourceNotFoundException; use Juampi92\APIResources\Exceptions\WrongConfigurationException; +use Juampi92\APIResources\Resolvers\ResolverFactory; class APIResourceManager { @@ -170,40 +170,9 @@ public function getBasePath(): string return $this->path; } - /** - * Returns the classname of the versioned resource, - * or it's latest version if it doesn't exist. - * - * Throws an exception if it cannot find it. - * - * @param string $classname - * - * @return string - * @throws ResourceNotFoundException - */ - public function resolveClassname(string $classname) + public function getResourcesPath(): string { - $path = $this->parseClassname($classname); - - // Check if the resource was found - if (class_exists($path)) { - return $path; - } - - // If we are on the latest version, stop searching - if ($this->isLatest()) { - throw new Exceptions\ResourceNotFoundException($classname, $path); - } - - // Search on the latest version - $path = $this->parseClassname($classname, true); - - // If still does not exists, fail - if (! class_exists($path)) { - throw new Exceptions\ResourceNotFoundException($classname, $path); - } - - return $path; + return $this->resources; } /** @@ -212,6 +181,7 @@ public function resolveClassname(string $classname) * @param bool $forceLatest Set to true if last version is required * * @return class-string + * @deprecated */ protected function parseClassname(string $classname, bool $forceLatest = false): string { @@ -255,26 +225,15 @@ protected function resolveClassnameFromFilesystem(string $classname, string $ver */ public function resolve(string $classname): APIResource { - $path = $this->resolveClassname($classname); - - // Check if the resource was found - if (! class_exists($path)) { - - // If we are on the latest version, stop searching - if ($this->isLatest()) { - throw new Exceptions\ResourceNotFoundException($classname, $path); - } - - // Search on the latest version - $path = $this->resolveClassname($classname, true); - - // If still does not exists, fail - if (! class_exists($path)) { - throw new Exceptions\ResourceNotFoundException($classname, $path); - } - } + return ResolverFactory::make($classname)->run(); + } - return new APIResource($path); + /** + * @throws ResourceNotFoundException + */ + public function resolveClassname(string $classname): string + { + return ResolverFactory::make($classname)->dryRun(); } /** diff --git a/src/Facades/APIResource.php b/src/Facades/APIResource.php index cb93456..7d429a6 100644 --- a/src/Facades/APIResource.php +++ b/src/Facades/APIResource.php @@ -7,6 +7,7 @@ /** * @method static \Juampi92\APIResources\APIResourceManager setVersion(string $version, string $apiName = null) * @method static string getVersion() + * @method static string getLatestVersion() * @method static bool isLatest(string $c = null) * @method static string resolveClassname(string $classname, bool $forceLatest = null) Returns formatted classname using current version * @method static \Juampi92\APIResources\APIResource resolve(string $classname) @@ -14,6 +15,8 @@ * @method static \Illuminate\Http\Resources\Json\Resource collection(string $classname, ...$args) Resolves the classname and instantiates the resource as a collection * @method static string getRoute(string $name, array $parameters, bool $absolute) * @method static string getRouteName(string $name) + * @method static string getBasePath() + * @method static string getResourcesPath() * * @see \Juampi92\APIResources\APIResourceManager */ diff --git a/src/Resolvers/CacheResolver.php b/src/Resolvers/CacheResolver.php new file mode 100644 index 0000000..b9f2de6 --- /dev/null +++ b/src/Resolvers/CacheResolver.php @@ -0,0 +1,28 @@ + $classPath */ + $classPath = $routingManifest[$this->classname][$this->version] ?? null; + + if ($classPath && ! class_exists($classPath)) { + return null; + } + + return $classPath; + } +} diff --git a/src/Resolvers/PathResolver.php b/src/Resolvers/PathResolver.php new file mode 100644 index 0000000..43e9f36 --- /dev/null +++ b/src/Resolvers/PathResolver.php @@ -0,0 +1,43 @@ +guessResourcePath(); + + /** @var class-string $fullPath */ + $fullPath = "\\{$basePath}\\{$resourcePath}"; + + if (! class_exists($fullPath)) { + return null; + } + + return $fullPath; + } + + private function guessResourcePath(): string + { + $resourcesPath = APIResource::getResourcesPath(); + + if (empty($resourcesPath)) { + return "{$this->version}\\{$this->classname}"; + } + + return sprintf( + "%s\\%s\\%s", + $resourcesPath, + $this->version, + Str::after($this->classname, $resourcesPath . "\\") + ); + } +} diff --git a/src/Resolvers/Resolver.php b/src/Resolvers/Resolver.php new file mode 100644 index 0000000..3a3bfc4 --- /dev/null +++ b/src/Resolvers/Resolver.php @@ -0,0 +1,67 @@ +version = $version; + $this->classname = $classname; + } + + public function setFallback(Resolver $resolveStrategy): Resolver + { + $this->fallback = $resolveStrategy; + + return $resolveStrategy; + } + + /** + * @throws ResourceNotFoundException + */ + public function run(): APIResource + { + if ($path = $this->resolve()) { + return new APIResource($path); + } + + if ($this->fallback) { + return $this->fallback->run(); + } + + throw new ResourceNotFoundException($this->classname, $path ?? '--unresolved--'); + } + + /** + * @throws ResourceNotFoundException + */ + public function dryRun(): string + { + if ($path = $this->resolve()) { + return $path; + } + + if ($this->fallback) { + return $this->fallback->dryRun(); + } + + throw new ResourceNotFoundException($this->classname, $path ?? '--unresolved--'); + } + + /** + * @return class-string + */ + abstract protected function resolve(): ?string; +} diff --git a/src/Resolvers/ResolverFactory.php b/src/Resolvers/ResolverFactory.php new file mode 100644 index 0000000..50016a1 --- /dev/null +++ b/src/Resolvers/ResolverFactory.php @@ -0,0 +1,32 @@ +setFallback( + new PathResolver($classname, $latestVersion) + ); + } + + $cacheResolver->setFallback( + $pathResolver, + ); + + return $cacheResolver; + } +} From 2a7697ee24557a9742c8cf877a8348b0e21fb2bb Mon Sep 17 00:00:00 2001 From: Mazen Touati Date: Wed, 3 Aug 2022 16:40:33 +0200 Subject: [PATCH 10/14] Update routing cache path and format --- publishable/config/api.php | 2 +- src/APIResourceManager.php | 8 +++++++- src/Resolvers/CacheResolver.php | 6 +++++- tests/APIResourceTest.php | 10 ++++++++-- tests/Fixtures/config/multi.php | 2 +- tests/Fixtures/config/simple.php | 2 +- 6 files changed, 23 insertions(+), 7 deletions(-) diff --git a/publishable/config/api.php b/publishable/config/api.php index 606d048..1f86a11 100644 --- a/publishable/config/api.php +++ b/publishable/config/api.php @@ -58,6 +58,6 @@ | strategy for each Resource in every version. */ - 'routing_manifest_path' => app()->bootstrapPath('api-resources-manifest.json'), + 'routing_cache_path' => app()->bootstrapPath('cache/api-resources-cache.php'), ]; diff --git a/src/APIResourceManager.php b/src/APIResourceManager.php index 74071d7..09ad5b2 100644 --- a/src/APIResourceManager.php +++ b/src/APIResourceManager.php @@ -193,9 +193,12 @@ protected function parseClassname(string $classname, bool $forceLatest = false): ?? $this->resolveClassnameFromFilesystem($classname, $version); } + /** + * @deprecated + */ protected function resolveClassnameFromRoutingManifest(string $classname, string $version): ?string { - $routingManifest = json_decode(Storage::get(config('api.routing_manifest_path')), true); + $routingManifest = json_decode(Storage::get(config('api.routing_cache_path')), true); if (! $routingManifest) { return null; @@ -204,6 +207,9 @@ protected function resolveClassnameFromRoutingManifest(string $classname, string return $routingManifest[$classname][$version] ?? null; } + /** + * @deprecated + */ protected function resolveClassnameFromFilesystem(string $classname, string $version): ?string { $path = ! empty($this->resources) diff --git a/src/Resolvers/CacheResolver.php b/src/Resolvers/CacheResolver.php index b9f2de6..82607e0 100644 --- a/src/Resolvers/CacheResolver.php +++ b/src/Resolvers/CacheResolver.php @@ -10,7 +10,11 @@ class CacheResolver extends Resolver { protected function resolve(): ?string { - $routingManifest = json_decode(Storage::get(config('api.routing_manifest_path')), true); + if (Storage::missing(config('api.routing_cache_path'))) { + return null; + } + + $routingManifest = include Storage::path(config('api.routing_cache_path')); if (! $routingManifest) { return null; diff --git a/tests/APIResourceTest.php b/tests/APIResourceTest.php index 26ba9ef..caba41f 100644 --- a/tests/APIResourceTest.php +++ b/tests/APIResourceTest.php @@ -169,7 +169,13 @@ public function test_it_prioritize_resolving_from_route_manifest(): void ], ]; - Storage::put(config('api.routing_manifest_path'), json_encode($manifest)); + $content = sprintf( + " app()->bootstrapPath('api-resources-manifest.json'), + 'routing_cache_path' => app()->bootstrapPath('cache/api-resources-cache.php'), ]; diff --git a/tests/Fixtures/config/simple.php b/tests/Fixtures/config/simple.php index 8af94dd..725fdb7 100644 --- a/tests/Fixtures/config/simple.php +++ b/tests/Fixtures/config/simple.php @@ -44,5 +44,5 @@ | strategy for each Resource in every version. */ - 'routing_manifest_path' => app()->bootstrapPath('api-resources-manifest.json'), + 'routing_cache_path' => app()->bootstrapPath('cache/api-resources-cache.php'), ]; From 7fbe8321149c54d410257a1d8f575b6d4ebed4ed Mon Sep 17 00:00:00 2001 From: Mazen Touati Date: Wed, 3 Aug 2022 17:43:45 +0200 Subject: [PATCH 11/14] Fix return type and remove deprecated code --- src/APIResourceManager.php | 48 +------------------------------------ src/Facades/APIResource.php | 2 +- 2 files changed, 2 insertions(+), 48 deletions(-) diff --git a/src/APIResourceManager.php b/src/APIResourceManager.php index 09ad5b2..f06828a 100644 --- a/src/APIResourceManager.php +++ b/src/APIResourceManager.php @@ -3,8 +3,6 @@ namespace Juampi92\APIResources; use Exception; -use Illuminate\Http\Resources\Json\JsonResource; -use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Juampi92\APIResources\Exceptions\ResourceNotFoundException; use Juampi92\APIResources\Exceptions\WrongConfigurationException; @@ -170,55 +168,11 @@ public function getBasePath(): string return $this->path; } - public function getResourcesPath(): string + public function getResourcesPath(): ?string { return $this->resources; } - /** - * Returns the classname with the version considering. - * - * @param bool $forceLatest Set to true if last version is required - * - * @return class-string - * @deprecated - */ - protected function parseClassname(string $classname, bool $forceLatest = false): string - { - $version = $forceLatest ? $this->latest : $this->current; - - $version = Str::start($version, 'v'); - - return $this->resolveClassnameFromRoutingManifest($classname, $version) - ?? $this->resolveClassnameFromFilesystem($classname, $version); - } - - /** - * @deprecated - */ - protected function resolveClassnameFromRoutingManifest(string $classname, string $version): ?string - { - $routingManifest = json_decode(Storage::get(config('api.routing_cache_path')), true); - - if (! $routingManifest) { - return null; - } - - return $routingManifest[$classname][$version] ?? null; - } - - /** - * @deprecated - */ - protected function resolveClassnameFromFilesystem(string $classname, string $version): ?string - { - $path = ! empty($this->resources) - ? $this->resources . "\\{$version}\\" . Str::after($classname, $this->resources . "\\") - : "{$version}\\" . $classname; - - return "\\{$this->path}\\{$path}"; - } - /** * Smart builds the classname using the correct version. * If it fails with the current version, it falls back to diff --git a/src/Facades/APIResource.php b/src/Facades/APIResource.php index 7d429a6..200cf71 100644 --- a/src/Facades/APIResource.php +++ b/src/Facades/APIResource.php @@ -16,7 +16,7 @@ * @method static string getRoute(string $name, array $parameters, bool $absolute) * @method static string getRouteName(string $name) * @method static string getBasePath() - * @method static string getResourcesPath() + * @method static ?string getResourcesPath() * * @see \Juampi92\APIResources\APIResourceManager */ From be003bb7dc151841bdd2ce72693fd54908bb6745 Mon Sep 17 00:00:00 2001 From: Mazen Touati Date: Wed, 3 Aug 2022 17:44:04 +0200 Subject: [PATCH 12/14] Assert resolution strategy --- tests/Fixtures/Resources/App/v1/File.php | 17 +++ tests/Fixtures/Resources/App/v2/File.php | 17 +++ tests/Fixtures/Resources/App/v3/File.php | 17 +++ tests/Resolvers/ResolutionStrategyTest.php | 125 +++++++++++++++++++++ tests/ResourcePathResolveTest.php | 77 ------------- 5 files changed, 176 insertions(+), 77 deletions(-) create mode 100644 tests/Fixtures/Resources/App/v1/File.php create mode 100644 tests/Fixtures/Resources/App/v2/File.php create mode 100644 tests/Fixtures/Resources/App/v3/File.php create mode 100644 tests/Resolvers/ResolutionStrategyTest.php delete mode 100644 tests/ResourcePathResolveTest.php diff --git a/tests/Fixtures/Resources/App/v1/File.php b/tests/Fixtures/Resources/App/v1/File.php new file mode 100644 index 0000000..0d6037e --- /dev/null +++ b/tests/Fixtures/Resources/App/v1/File.php @@ -0,0 +1,17 @@ + $this->id, + 'path' => $this->name, + 'v' => 1, + ]; + } +} diff --git a/tests/Fixtures/Resources/App/v2/File.php b/tests/Fixtures/Resources/App/v2/File.php new file mode 100644 index 0000000..feae3ea --- /dev/null +++ b/tests/Fixtures/Resources/App/v2/File.php @@ -0,0 +1,17 @@ + $this->id, + 'path' => $this->name, + 'v' => 2, + ]; + } +} diff --git a/tests/Fixtures/Resources/App/v3/File.php b/tests/Fixtures/Resources/App/v3/File.php new file mode 100644 index 0000000..a51ea2c --- /dev/null +++ b/tests/Fixtures/Resources/App/v3/File.php @@ -0,0 +1,17 @@ + $this->id, + 'path' => $this->name, + 'v' => 3, + ]; + } +} diff --git a/tests/Resolvers/ResolutionStrategyTest.php b/tests/Resolvers/ResolutionStrategyTest.php new file mode 100644 index 0000000..d37eaa9 --- /dev/null +++ b/tests/Resolvers/ResolutionStrategyTest.php @@ -0,0 +1,125 @@ + require __DIR__.'/../Fixtures/config/simple.php']); + } + + public function test_it_can_resolve_api_changes(): void + { + APIResource::setVersion('1'); + + $this->assertEquals( + '\\' . \Juampi92\APIResources\Tests\Fixtures\Resources\App\v1\File::class, + ResolverFactory::make('App\File')->dryRun(), + ); + + APIResource::setVersion('2'); + + $this->assertEquals( + '\\' . \Juampi92\APIResources\Tests\Fixtures\Resources\App\v2\File::class, + ResolverFactory::make('App\File')->dryRun(), + ); + + APIResource::setVersion('3'); + + $this->assertEquals( + '\\' . \Juampi92\APIResources\Tests\Fixtures\Resources\App\v3\File::class, + ResolverFactory::make('App\File')->dryRun(), + ); + } + + /** + * @dataProvider resourcePathDataProvider + */ + public function test_it_can_resolve_resource_path_changes(string $path, string $version, string $classname, string $expected): void + { + config(['api.resources_path' => $path]); + config(['api.resources' => '']); + + APIResource::setVersion($version); + + $this->assertEquals( + $expected, + ResolverFactory::make($classname)->dryRun(), + ); + } + + public function resourcePathDataProvider(): array + { + return [ + [ + 'Juampi92\\APIResources\\Tests\\Fixtures\Resources\Cached', + '2', + 'User', + '\\' . \Juampi92\APIResources\Tests\Fixtures\Resources\Cached\v2\User::class, + ], + + [ + 'Juampi92\\APIResources\\Tests\\Fixtures\Resources\Api', + '1', + 'User', + '\\' . \Juampi92\APIResources\Tests\Fixtures\Resources\Api\v1\User::class, + ], + ]; + } + + /** + * @dataProvider resourcePrefixDataProvider + */ + public function test_it_can_resolve_resources_prefix_changes(?string $prefix, string $version, string $classname, string $expected): void + { + config(['api.resources' => $prefix]); + + APIResource::setVersion($version); + + $this->assertEquals( + $expected, + ResolverFactory::make($classname)->dryRun(), + ); + } + + public function resourcePrefixDataProvider(): array + { + return [ + [ + 'Cached', + '2', + 'User', + '\\' . \Juampi92\APIResources\Tests\Fixtures\Resources\Cached\v2\User::class, + ], + + [ + 'Api', + '1', + 'User', + '\\' . \Juampi92\APIResources\Tests\Fixtures\Resources\Api\v1\User::class, + ], + + [ + '', + '1', + 'User', + '\\' . \Juampi92\APIResources\Tests\Fixtures\Resources\v1\User::class, + ], + + [ + null, + '1', + 'User', + '\\' . \Juampi92\APIResources\Tests\Fixtures\Resources\v1\User::class, + ], + ]; + } +} diff --git a/tests/ResourcePathResolveTest.php b/tests/ResourcePathResolveTest.php deleted file mode 100644 index bdc929d..0000000 --- a/tests/ResourcePathResolveTest.php +++ /dev/null @@ -1,77 +0,0 @@ - require __DIR__ . '/../publishable/config/api.php']); - - $this->apiResourceManager = new APIResourceManager(); - } - - public function test_it_can_resolve_api_changes() - { - $this->apiResourceManager->setVersion('1'); - $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['App\User']); - $this->assertEquals('\\App\\Http\\Resources\\App\\v1\\User', $classname); - - $this->apiResourceManager->setVersion('2'); - $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['App\Users']); - $this->assertEquals('\\App\\Http\\Resources\\App\\v2\\Users', $classname); - - $this->apiResourceManager->setVersion('3'); - $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['App\User\Single']); - $this->assertEquals('\\App\\Http\\Resources\\App\\v3\\User\\Single', $classname); - } - - public function test_it_can_resolve_resource_path_changes() - { - config(['api.resources_path' => 'App\Resources']); - $this->apiResourceManager->setVersion('3'); - $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['App\User']); - $this->assertEquals('\\App\\Resources\\App\\v3\\User', $classname); - - config(['api.resources_path' => 'App\Resources2']); - $this->apiResourceManager->setVersion('4'); - $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['App\User']); - $this->assertEquals('\\App\\Resources2\\App\\v4\\User', $classname); - } - - public function test_it_can_resolve_resources_prefix_changes() - { - config(['api.resources' => 'Api']); - $this->apiResourceManager->setVersion('1'); - $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['Api\User']); - $this->assertEquals('\\App\\Http\\Resources\\Api\\v1\\User', $classname); - - config(['api.resources' => 'Api\App']); - $this->apiResourceManager->setVersion('1'); - $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['Api\App\User']); - $this->assertEquals('\\App\\Http\\Resources\\Api\\App\\v1\\User', $classname); - } - - public function test_it_can_resolve_resources_prefix_empty() - { - config(['api.resources' => '']); - $this->apiResourceManager->setVersion('1'); - $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['User']); - $this->assertEquals('\\App\\Http\\Resources\\v1\\User', $classname); - - config(['api.resources' => null]); - $this->apiResourceManager->setVersion('1'); - $classname = $this->callMethod($this->apiResourceManager, 'parseClassname', ['User']); - $this->assertEquals('\\App\\Http\\Resources\\v1\\User', $classname); - } -} From a03895f58eb30e70a23f39d5a64216a6d1855410 Mon Sep 17 00:00:00 2001 From: Mazen Touati Date: Wed, 3 Aug 2022 17:47:28 +0200 Subject: [PATCH 13/14] Drop dryRun from resolver --- src/APIResourceManager.php | 4 ++-- src/Resolvers/Resolver.php | 21 ++------------------- tests/Resolvers/ResolutionStrategyTest.php | 10 +++++----- 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/src/APIResourceManager.php b/src/APIResourceManager.php index f06828a..cb18730 100644 --- a/src/APIResourceManager.php +++ b/src/APIResourceManager.php @@ -185,7 +185,7 @@ public function getResourcesPath(): ?string */ public function resolve(string $classname): APIResource { - return ResolverFactory::make($classname)->run(); + return new APIResource(ResolverFactory::make($classname)->run()); } /** @@ -193,7 +193,7 @@ public function resolve(string $classname): APIResource */ public function resolveClassname(string $classname): string { - return ResolverFactory::make($classname)->dryRun(); + return ResolverFactory::make($classname)->run(); } /** diff --git a/src/Resolvers/Resolver.php b/src/Resolvers/Resolver.php index 3a3bfc4..9458ce8 100644 --- a/src/Resolvers/Resolver.php +++ b/src/Resolvers/Resolver.php @@ -4,7 +4,6 @@ use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\ResourceCollection; -use Juampi92\APIResources\APIResource; use Juampi92\APIResources\Exceptions\ResourceNotFoundException; abstract class Resolver @@ -31,30 +30,14 @@ public function setFallback(Resolver $resolveStrategy): Resolver /** * @throws ResourceNotFoundException */ - public function run(): APIResource - { - if ($path = $this->resolve()) { - return new APIResource($path); - } - - if ($this->fallback) { - return $this->fallback->run(); - } - - throw new ResourceNotFoundException($this->classname, $path ?? '--unresolved--'); - } - - /** - * @throws ResourceNotFoundException - */ - public function dryRun(): string + public function run(): string { if ($path = $this->resolve()) { return $path; } if ($this->fallback) { - return $this->fallback->dryRun(); + return $this->fallback->run(); } throw new ResourceNotFoundException($this->classname, $path ?? '--unresolved--'); diff --git a/tests/Resolvers/ResolutionStrategyTest.php b/tests/Resolvers/ResolutionStrategyTest.php index d37eaa9..d08fce5 100644 --- a/tests/Resolvers/ResolutionStrategyTest.php +++ b/tests/Resolvers/ResolutionStrategyTest.php @@ -22,21 +22,21 @@ public function test_it_can_resolve_api_changes(): void $this->assertEquals( '\\' . \Juampi92\APIResources\Tests\Fixtures\Resources\App\v1\File::class, - ResolverFactory::make('App\File')->dryRun(), + ResolverFactory::make('App\File')->run(), ); APIResource::setVersion('2'); $this->assertEquals( '\\' . \Juampi92\APIResources\Tests\Fixtures\Resources\App\v2\File::class, - ResolverFactory::make('App\File')->dryRun(), + ResolverFactory::make('App\File')->run(), ); APIResource::setVersion('3'); $this->assertEquals( '\\' . \Juampi92\APIResources\Tests\Fixtures\Resources\App\v3\File::class, - ResolverFactory::make('App\File')->dryRun(), + ResolverFactory::make('App\File')->run(), ); } @@ -52,7 +52,7 @@ public function test_it_can_resolve_resource_path_changes(string $path, string $ $this->assertEquals( $expected, - ResolverFactory::make($classname)->dryRun(), + ResolverFactory::make($classname)->run(), ); } @@ -86,7 +86,7 @@ public function test_it_can_resolve_resources_prefix_changes(?string $prefix, st $this->assertEquals( $expected, - ResolverFactory::make($classname)->dryRun(), + ResolverFactory::make($classname)->run(), ); } From 02bee87c5cc9e5c597470ddbc96c5fa8d6e07c3c Mon Sep 17 00:00:00 2001 From: Mazen Touati Date: Wed, 3 Aug 2022 18:28:07 +0200 Subject: [PATCH 14/14] Adapt for all versions in resolutions strategies --- src/APIResourceManager.php | 9 +++++++- src/Facades/APIResource.php | 1 + src/Resolvers/CacheResolver.php | 29 +++++++++++++----------- src/Resolvers/PathResolver.php | 37 +++++++++++++++++++++---------- src/Resolvers/Resolver.php | 33 +++------------------------ src/Resolvers/ResolverFactory.php | 21 ++++-------------- tests/APIResourceTest.php | 6 ----- 7 files changed, 57 insertions(+), 79 deletions(-) diff --git a/src/APIResourceManager.php b/src/APIResourceManager.php index 38519f9..ac53e49 100644 --- a/src/APIResourceManager.php +++ b/src/APIResourceManager.php @@ -5,7 +5,6 @@ use Exception; use Illuminate\Support\Str; use Juampi92\APIResources\Exceptions\ResourceNotFoundException; -use Juampi92\APIResources\Exceptions\WrongConfigurationException; use Juampi92\APIResources\Resolvers\ResolverFactory; use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\ResourceCollection; @@ -214,6 +213,14 @@ public function collection($classname, ...$args) return $resource->collection(...$args); } + /** + * @return array + */ + public function getVersionsBetweenCurrentAndLatest(): array + { + return $this->getVersionsBetween($this->current, $this->latest); + } + /** * @return array */ diff --git a/src/Facades/APIResource.php b/src/Facades/APIResource.php index 200cf71..8810706 100644 --- a/src/Facades/APIResource.php +++ b/src/Facades/APIResource.php @@ -17,6 +17,7 @@ * @method static string getRouteName(string $name) * @method static string getBasePath() * @method static ?string getResourcesPath() + * @method static array getVersionsBetweenCurrentAndLatest() * * @see \Juampi92\APIResources\APIResourceManager */ diff --git a/src/Resolvers/CacheResolver.php b/src/Resolvers/CacheResolver.php index 82607e0..8e61c74 100644 --- a/src/Resolvers/CacheResolver.php +++ b/src/Resolvers/CacheResolver.php @@ -5,28 +5,31 @@ use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\ResourceCollection; use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Str; +use Juampi92\APIResources\Exceptions\ResourceNotFoundException; +use Juampi92\APIResources\Facades\APIResource; class CacheResolver extends Resolver { - protected function resolve(): ?string + public function run(): string { - if (Storage::missing(config('api.routing_cache_path'))) { - return null; - } - $routingManifest = include Storage::path(config('api.routing_cache_path')); - if (! $routingManifest) { - return null; - } + foreach (APIResource::getVersionsBetweenCurrentAndLatest() as $version) { + $version = Str::start($version, 'v'); + + /** @var ?class-string $path */ + $path = $routingManifest[$this->classname][$version] ?? null; - /** @var ?class-string $classPath */ - $classPath = $routingManifest[$this->classname][$this->version] ?? null; + if (! $path) { + continue; + } - if ($classPath && ! class_exists($classPath)) { - return null; + if (class_exists($path)) { + return $path; + } } - return $classPath; + throw new ResourceNotFoundException($this->classname, APIResource::getLatestVersion()); } } diff --git a/src/Resolvers/PathResolver.php b/src/Resolvers/PathResolver.php index 43e9f36..9d84f5b 100644 --- a/src/Resolvers/PathResolver.php +++ b/src/Resolvers/PathResolver.php @@ -5,39 +5,52 @@ use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\ResourceCollection; use Illuminate\Support\Str; +use Juampi92\APIResources\Exceptions\ResourceNotFoundException; use Juampi92\APIResources\Facades\APIResource; class PathResolver extends Resolver { - protected function resolve(): ?string + public function run(): string { - $basePath = APIResource::getBasePath(); - - $resourcePath = $this->guessResourcePath(); + foreach (APIResource::getVersionsBetweenCurrentAndLatest() as $version) { + $version = Str::start($version, 'v'); - /** @var class-string $fullPath */ - $fullPath = "\\{$basePath}\\{$resourcePath}"; + $path = $this->getPath($version); - if (! class_exists($fullPath)) { - return null; + // Check if the resource was found + if (class_exists($path)) { + return $path; + } } - return $fullPath; + throw new ResourceNotFoundException($this->classname, APIResource::getLatestVersion()); } - private function guessResourcePath(): string + private function guessResourcePath(string $version): string { $resourcesPath = APIResource::getResourcesPath(); if (empty($resourcesPath)) { - return "{$this->version}\\{$this->classname}"; + return "{$version}\\{$this->classname}"; } return sprintf( "%s\\%s\\%s", $resourcesPath, - $this->version, + $version, Str::after($this->classname, $resourcesPath . "\\") ); } + + /** + * @return class-string + */ + protected function getPath($version): string + { + $basePath = APIResource::getBasePath(); + + $resourcePath = $this->guessResourcePath($version); + + return "\\{$basePath}\\{$resourcePath}"; + } } diff --git a/src/Resolvers/Resolver.php b/src/Resolvers/Resolver.php index 9458ce8..18022f7 100644 --- a/src/Resolvers/Resolver.php +++ b/src/Resolvers/Resolver.php @@ -10,41 +10,14 @@ abstract class Resolver { protected string $classname; - protected string $version; - - protected ?Resolver $fallback = null; - - public function __construct(string $classname, string $version) + public function __construct(string $classname) { - $this->version = $version; $this->classname = $classname; } - public function setFallback(Resolver $resolveStrategy): Resolver - { - $this->fallback = $resolveStrategy; - - return $resolveStrategy; - } - - /** - * @throws ResourceNotFoundException - */ - public function run(): string - { - if ($path = $this->resolve()) { - return $path; - } - - if ($this->fallback) { - return $this->fallback->run(); - } - - throw new ResourceNotFoundException($this->classname, $path ?? '--unresolved--'); - } - /** * @return class-string + * @throws ResourceNotFoundException */ - abstract protected function resolve(): ?string; + abstract public function run(): string; } diff --git a/src/Resolvers/ResolverFactory.php b/src/Resolvers/ResolverFactory.php index 50016a1..b033c8c 100644 --- a/src/Resolvers/ResolverFactory.php +++ b/src/Resolvers/ResolverFactory.php @@ -2,6 +2,7 @@ namespace Juampi92\APIResources\Resolvers; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Juampi92\APIResources\Facades\APIResource; @@ -9,24 +10,10 @@ class ResolverFactory { public static function make(string $classname): Resolver { - $version = Str::start(APIResource::getVersion(), 'v'); - - $cacheResolver = new CacheResolver($classname, $version); - - $pathResolver = new PathResolver($classname, $version); - - if (! APIResource::isLatest()) { - $latestVersion = Str::start(APIResource::getLatestVersion(), 'v'); - - $pathResolver->setFallback( - new PathResolver($classname, $latestVersion) - ); + if (Storage::exists(config('api.routing_cache_path'))) { + return new CacheResolver($classname); } - $cacheResolver->setFallback( - $pathResolver, - ); - - return $cacheResolver; + return new PathResolver($classname); } } diff --git a/tests/APIResourceTest.php b/tests/APIResourceTest.php index c5664b5..682a457 100644 --- a/tests/APIResourceTest.php +++ b/tests/APIResourceTest.php @@ -159,7 +159,6 @@ public function test_without_resource_folder() public function test_it_prioritize_resolving_from_route_manifest(): void { // Arrange - $manifest = [ 'App\User' => [ 'v1' => UserV2Cached::class, @@ -205,11 +204,6 @@ public function test_it_prioritize_resolving_from_route_manifest(): void APIResourceFacade::resolve('App\User')->make($user) ); - $this->assertInstanceOf( - PostV3AppResource::class, - APIResourceFacade::resolve('App\Post')->make(new Fixtures\Models\Post()) - ); - // Cleanup Storage::delete(config('api.routing_cache_path'));