From 4948f8199512af873b6c3c86382b26c85accb7ca Mon Sep 17 00:00:00 2001 From: Juan Pablo Barreto Date: Wed, 3 Aug 2022 17:25:08 +0200 Subject: [PATCH 1/2] initial idea --- src/APIResourceManager.php | 96 ++++++++--------------------- src/APIResourceVersion.php | 83 +++++++++++++++++++++++++ src/APIResourcesServiceProvider.php | 2 +- src/Config.php | 38 ++++++++++++ 4 files changed, 148 insertions(+), 71 deletions(-) create mode 100644 src/APIResourceVersion.php create mode 100644 src/Config.php diff --git a/src/APIResourceManager.php b/src/APIResourceManager.php index 8ed06cc..b83d391 100644 --- a/src/APIResourceManager.php +++ b/src/APIResourceManager.php @@ -3,6 +3,7 @@ namespace Juampi92\APIResources; use Exception; +use Illuminate\Contracts\Config\Repository; use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\ResourceCollection; use Illuminate\Support\Str; @@ -35,8 +36,22 @@ class APIResourceManager */ protected $routePath; - public function __construct() + private Repository $config; + + private ?APIResourceVersion $version = null; + + public function __construct(Repository $config) { + $this->config = $config; + } + + private function getResourceVersion(): APIResourceVersion + { + if (! $this->version) { + throw new Exception('APIResource not initialised. Please use APIResource::setVersion($version) to specify the current version'); + } + + return $this->version; } /** @@ -45,17 +60,9 @@ public function __construct() * @param string $route * @return string */ - public function getRouteName($route) + public function getRouteName(string $route) { - if (! $this->routePath) { - // Grab route_prefix config first. If it's not set, - // grab the resources, and replace `\` with `.`, and - // transform it all to lowercase. - $this->routePath = $this->getConfig('route_prefix') - ?: str_replace('\\', '.', strtolower($this->getConfig('resources'))); - } - - return "{$this->routePath}.v{$this->current}" . Str::after($route, $this->routePath); + return $this->getResourceVersion()->getRouteName($route); } /** @@ -69,25 +76,6 @@ public function getRoute($name, $parameters = [], bool $absolute = true): string return route($this->getRouteName((string) $name), $parameters, $absolute); } - /** - * Get config considering the API name if present. - * - * @param string $cfg Config path - * @param string $name Name of api if present - * - * @return mixed The result of the config - */ - protected function getConfig($cfg, $name = null) - { - if (is_null($name)) { - $name = $this->apiName; - } - - $name = $name ? ".$name" : ''; - - return config("api.$cfg{$name}"); - } - /** * Sets the current API version. * @@ -96,28 +84,11 @@ protected function getConfig($cfg, $name = null) * * @return $this */ - public function setVersion(string $current, $apiName = null) + public function setVersion(string $current, ?string $apiName = null) { - $this->apiName = $apiName; - - $latestVersion = $this->getConfig('version'); - - if (! $latestVersion) { - throw new Exception('You must define a config(\'api\') with a latest version. Do: php artisan vendor:publish --provider="Juampi92/APIResources/APIResourcesServiceProvider"'); - } + $config = new Config($this->config, $apiName); - // Reset pre-cached properties - $this->current = $current; - $this->routePath = null; - $this->latest = $this->getConfig('version'); - - // Path can be only one or one for each api - $this->path = config('api.resources_path'); - if (is_array($this->path)) { - $this->path = $this->getConfig('resources_path'); - } - - $this->resources = $this->getConfig('resources'); + $this->version = new APIResourceVersion($config, $current); return $this; } @@ -129,12 +100,12 @@ public function setVersion(string $current, $apiName = null) */ public function getVersion() { - return $this->current; + return $this->getResourceVersion()->getVersion(); } public function getLatestVersion(): string { - return $this->latest; + return $this->getResourceVersion()->getLatestVersion(); } /** @@ -146,16 +117,11 @@ public function getLatestVersion(): string */ public function isLatest(string $current = null): bool { - if (! isset($current)) { - $current = $this->current; + if (is_null($current)) { + $current = $this->getResourceVersion()->getVersion(); } - return $this->latest === $current; - } - - public function getBasePath(): string - { - return $this->path; + return $this->getResourceVersion()->getLatestVersion() === $current; } /** @@ -246,14 +212,4 @@ public function collection($classname, ...$args) return $resource->collection(...$args); } - - /** - * @return array - */ - private function getVersionsBetween(string $current, string $latest): array - { - $versions = $this->getConfig('versions') ?: [$current, $latest]; - - return Version::fromRange($versions, $current, $latest); - } } diff --git a/src/APIResourceVersion.php b/src/APIResourceVersion.php new file mode 100644 index 0000000..edf7ca0 --- /dev/null +++ b/src/APIResourceVersion.php @@ -0,0 +1,83 @@ +config = $config; + $this->version = $version; + + $latest = $this->config->get('version'); + + if (! $latest) { + throw new Exception('You must define a config(\'api\') with a latest version. Do: php artisan vendor:publish --provider="Juampi92/APIResources/APIResourcesServiceProvider"'); + } + + $this->latest = $latest; + $this->path = $this->config->get('resources_path'); + $this->resourcesPath = $this->config->get('resources'); + } + + /** + * Returns the name of the versioned route. + * + * @param string $route + * @return string + */ + public function getRouteName(string $route): string + { + if (! isset($this->routePath)) { + // Grab route_prefix config first. If it's not set, + // grab the resources, and replace `\` with `.`, and + // transform it all to lowercase. + $this->routePath = $this->config->get('route_prefix') + ?: str_replace('\\', '.', strtolower($this->resourcesPath)); + } + + return "{$this->routePath}.v{$this->version}" . Str::after($route, $this->routePath); + } + + public function getVersion(): string + { + return $this->version; + } + + public function getLatestVersion(): string + { + return $this->latest; + } + + /** + * @return array + */ + private function getVersionsArray(): array + { + $current = $this->getVersion(); + $latest = $this->getLatestVersion(); + + $versions = $this->config->get('versions') ?: [$current, $latest]; + + return Version::fromRange($versions, $current, $latest); + } +} diff --git a/src/APIResourcesServiceProvider.php b/src/APIResourcesServiceProvider.php index d2cebed..349b574 100644 --- a/src/APIResourcesServiceProvider.php +++ b/src/APIResourcesServiceProvider.php @@ -22,7 +22,7 @@ public function boot(): void public function register(): void { $this->app->singleton('apiresource', function () { - return new APIResourceManager(); + return resolve(APIResourceManager::class); }); } diff --git a/src/Config.php b/src/Config.php new file mode 100644 index 0000000..6912750 --- /dev/null +++ b/src/Config.php @@ -0,0 +1,38 @@ +config = $config; + $this->name = $name; + } + + /** + * @param string $key + * @param mixed $default + * @return mixed + */ + public function get(string $key, $default = null) + { + $fullKey = $this->wrapInNamespace($key); + + return $this->config->get("api.{$fullKey}", $default); + } + + private function wrapInNamespace(string $key): string + { + if (! $this->name) { + return $key; + } + + return "{$this->name}.$key"; + } +} From 3d58fd909036c964370458e2441e7a72e64aed73 Mon Sep 17 00:00:00 2001 From: Juan Pablo Barreto Date: Wed, 3 Aug 2022 23:36:52 +0200 Subject: [PATCH 2/2] ClassnameResolver idea --- src/APIResourceManager.php | 81 ++++------------------ src/APIResourceVersion.php | 39 ++++++----- src/Config.php | 5 ++ src/Resolvers/CacheClassnameResolver.php | 40 +++++++++++ src/Resolvers/CacheResolver.php | 35 ---------- src/Resolvers/ClassnameResolver.php | 14 ++++ src/Resolvers/ClassnameResolverFactory.php | 18 +++++ src/Resolvers/PathClassnameResolver.php | 50 +++++++++++++ src/Resolvers/PathResolver.php | 56 --------------- src/Resolvers/Resolver.php | 23 ------ src/Resolvers/ResolverFactory.php | 19 ----- tests/Fixtures/config/multi.php | 72 +++++++++---------- 12 files changed, 196 insertions(+), 256 deletions(-) create mode 100644 src/Resolvers/CacheClassnameResolver.php delete mode 100644 src/Resolvers/CacheResolver.php create mode 100644 src/Resolvers/ClassnameResolver.php create mode 100644 src/Resolvers/ClassnameResolverFactory.php create mode 100644 src/Resolvers/PathClassnameResolver.php delete mode 100644 src/Resolvers/PathResolver.php delete mode 100644 src/Resolvers/Resolver.php delete mode 100644 src/Resolvers/ResolverFactory.php diff --git a/src/APIResourceManager.php b/src/APIResourceManager.php index 7e32e09..a48f64d 100644 --- a/src/APIResourceManager.php +++ b/src/APIResourceManager.php @@ -5,54 +5,22 @@ use Exception; use Illuminate\Contracts\Config\Repository; 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; -use Juampi92\APIResources\Resolvers\ResolverFactory; -use Illuminate\Http\Resources\Json\ResourceCollection; -use Illuminate\Support\Str; -use Juampi92\APIResources\Exceptions\ResourceNotFoundException; -use Illuminate\Support\Str; -use Juampi92\APIResources\Exceptions\ResourceNotFoundException; -use Juampi92\APIResources\Resolvers\ResolverFactory; -use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\ResourceCollection; +use Juampi92\APIResources\Resolvers\ClassnameResolverFactory; use Juampi92\APIResources\Support\Version; class APIResourceManager { - protected string $current; - - protected string $latest; - - /** - * @var string - */ - protected $path; - - /** - * @var string - */ - protected $apiName; - - /** - * @var string - */ - protected $resources; - - /** - * @var string|null - */ - protected $routePath; - private Repository $config; + private ClassnameResolverFactory $classnameResolverFactory; + private ?APIResourceVersion $version = null; - public function __construct(Repository $config) + public function __construct(Repository $config, ClassnameResolverFactory $classnameResolverFactory) { $this->config = $config; + $this->classnameResolverFactory = $classnameResolverFactory; } private function getResourceVersion(): APIResourceVersion @@ -97,8 +65,9 @@ public function getRoute($name, $parameters = [], bool $absolute = true): string public function setVersion(string $current, ?string $apiName = null) { $config = new Config($this->config, $apiName); + $resolver = $this->classnameResolverFactory->make($config); - $this->version = new APIResourceVersion($config, $current); + $this->version = new APIResourceVersion($config, $current, $resolver); return $this; } @@ -134,16 +103,6 @@ public function isLatest(string $current = null): bool return $this->getResourceVersion()->getLatestVersion() === $current; } - public function getResourcesPath(): string - { - return $this->path; - } - - public function getResourcesPath(): ?string - { - return $this->resources; - } - /** * Smart builds the classname using the correct version. * If it fails with the current version, it falls back to @@ -156,15 +115,17 @@ public function getResourcesPath(): ?string */ public function resolve(string $classname): APIResource { - return new APIResource(ResolverFactory::make($classname)->run()); + return new APIResource( + $this->resolveClassname($classname) + ); } /** - * @throws ResourceNotFoundException + * @returns class-string */ public function resolveClassname(string $classname): string { - return ResolverFactory::make($classname)->run(); + return $this->getResourceVersion()->resolveClassname($classname); } /** @@ -192,22 +153,4 @@ public function collection($classname, ...$args) return $resource->collection(...$args); } - - /** - * @return array - */ - public function getVersionsBetweenCurrentAndLatest(): array - { - return $this->getVersionsBetween($this->current, $this->latest); - } - - /** - * @return array - */ - private function getVersionsBetween(string $current, string $latest): array - { - $versions = $this->getConfig('versions') ?: [$current, $latest]; - - return Version::fromRange($versions, $current, $latest); - } } diff --git a/src/APIResourceVersion.php b/src/APIResourceVersion.php index edf7ca0..95fba80 100644 --- a/src/APIResourceVersion.php +++ b/src/APIResourceVersion.php @@ -3,7 +3,9 @@ namespace Juampi92\APIResources; use Exception; +use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Support\Str; +use Juampi92\APIResources\Resolvers\ClassnameResolver; use Juampi92\APIResources\Support\Version; class APIResourceVersion @@ -12,31 +14,20 @@ class APIResourceVersion private string $version; - private string $latest; + private ClassnameResolver $resolver; - private ?string $path; - - private ?string $resourcesPath; - - /** - * Cache of the Route Path. - */ + /** Cache of the Route Path */ private ?string $routePath = null; - public function __construct(Config $config, string $version) + public function __construct(Config $config, string $version, ClassnameResolver $resolver) { $this->config = $config; $this->version = $version; + $this->resolver = $resolver; - $latest = $this->config->get('version'); - - if (! $latest) { + if (! $this->getLatestVersion()) { throw new Exception('You must define a config(\'api\') with a latest version. Do: php artisan vendor:publish --provider="Juampi92/APIResources/APIResourcesServiceProvider"'); } - - $this->latest = $latest; - $this->path = $this->config->get('resources_path'); - $this->resourcesPath = $this->config->get('resources'); } /** @@ -52,7 +43,7 @@ public function getRouteName(string $route): string // grab the resources, and replace `\` with `.`, and // transform it all to lowercase. $this->routePath = $this->config->get('route_prefix') - ?: str_replace('\\', '.', strtolower($this->resourcesPath)); + ?: str_replace('\\', '.', strtolower($this->config->get('resources'))); } return "{$this->routePath}.v{$this->version}" . Str::after($route, $this->routePath); @@ -65,7 +56,7 @@ public function getVersion(): string public function getLatestVersion(): string { - return $this->latest; + return $this->config->get('version'); } /** @@ -80,4 +71,16 @@ private function getVersionsArray(): array return Version::fromRange($versions, $current, $latest); } + + /** + * @param string $classname + * @return class-string + */ + public function resolveClassname(string $classname): string + { + return $this->resolver->resolve( + $classname, + $this->getVersionsArray() + ); + } } diff --git a/src/Config.php b/src/Config.php index 6912750..27636a6 100644 --- a/src/Config.php +++ b/src/Config.php @@ -33,6 +33,11 @@ private function wrapInNamespace(string $key): string return $key; } + // When the key itself is not an array, we don't need the namespace. + if (! is_array($this->config->get($key))) { + return $key; + } + return "{$this->name}.$key"; } } diff --git a/src/Resolvers/CacheClassnameResolver.php b/src/Resolvers/CacheClassnameResolver.php new file mode 100644 index 0000000..e23102a --- /dev/null +++ b/src/Resolvers/CacheClassnameResolver.php @@ -0,0 +1,40 @@ +storage = $storage; + + $this->routingManifest = include_once $this->storage->path( + $config->get('routing_cache_path') + ); + } + + public function resolve(string $classname, array $versions): string + { + $currentVersion = Arr::first($versions); + + /** @var class-string|null $path */ + $path = $this->routingManifest[$classname][$currentVersion] ?? null; + + if (! class_exists($path)) { + throw new ResourceNotFoundException($classname, last($versions)); + } + + return $path; + } +} diff --git a/src/Resolvers/CacheResolver.php b/src/Resolvers/CacheResolver.php deleted file mode 100644 index 8e61c74..0000000 --- a/src/Resolvers/CacheResolver.php +++ /dev/null @@ -1,35 +0,0 @@ - $path */ - $path = $routingManifest[$this->classname][$version] ?? null; - - if (! $path) { - continue; - } - - if (class_exists($path)) { - return $path; - } - } - - throw new ResourceNotFoundException($this->classname, APIResource::getLatestVersion()); - } -} diff --git a/src/Resolvers/ClassnameResolver.php b/src/Resolvers/ClassnameResolver.php new file mode 100644 index 0000000..3e8c8e7 --- /dev/null +++ b/src/Resolvers/ClassnameResolver.php @@ -0,0 +1,14 @@ + $versions + * @return class-string + */ + public function resolve(string $classname, array $versions): string; +} diff --git a/src/Resolvers/ClassnameResolverFactory.php b/src/Resolvers/ClassnameResolverFactory.php new file mode 100644 index 0000000..2399e35 --- /dev/null +++ b/src/Resolvers/ClassnameResolverFactory.php @@ -0,0 +1,18 @@ + $config]); + } + + return resolve(PathClassnameResolver::class, ['config' => $config]); + } +} diff --git a/src/Resolvers/PathClassnameResolver.php b/src/Resolvers/PathClassnameResolver.php new file mode 100644 index 0000000..96e2610 --- /dev/null +++ b/src/Resolvers/PathClassnameResolver.php @@ -0,0 +1,50 @@ +config = $config; + } + + public function resolve(string $classname, array $versions): string + { + $basePath = $this->config->get('base_path'); + + foreach ($versions as $version) { + $version = Str::start($version, 'v'); + + $path = "\\{$basePath}\\" . $this->guessResourcePath($classname, $version); + + if (class_exists($path)) { + return $path; + } + } + + throw new ResourceNotFoundException($classname, last($versions)); + } + + private function guessResourcePath(string $classname, string $version): string + { + $resourcesPath = $this->config->get('resources_path'); + + if (empty($resourcesPath)) { + return "{$version}\\{$classname}"; + } + + return sprintf( + "%s\\%s\\%s", + $resourcesPath, + $version, + Str::after($classname, $resourcesPath . "\\") + ); + } +} diff --git a/src/Resolvers/PathResolver.php b/src/Resolvers/PathResolver.php deleted file mode 100644 index 9d84f5b..0000000 --- a/src/Resolvers/PathResolver.php +++ /dev/null @@ -1,56 +0,0 @@ -getPath($version); - - // Check if the resource was found - if (class_exists($path)) { - return $path; - } - } - - throw new ResourceNotFoundException($this->classname, APIResource::getLatestVersion()); - } - - private function guessResourcePath(string $version): string - { - $resourcesPath = APIResource::getResourcesPath(); - - if (empty($resourcesPath)) { - return "{$version}\\{$this->classname}"; - } - - return sprintf( - "%s\\%s\\%s", - $resourcesPath, - $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 deleted file mode 100644 index 18022f7..0000000 --- a/src/Resolvers/Resolver.php +++ /dev/null @@ -1,23 +0,0 @@ -classname = $classname; - } - - /** - * @return class-string - * @throws ResourceNotFoundException - */ - abstract public function run(): string; -} diff --git a/src/Resolvers/ResolverFactory.php b/src/Resolvers/ResolverFactory.php deleted file mode 100644 index b033c8c..0000000 --- a/src/Resolvers/ResolverFactory.php +++ /dev/null @@ -1,19 +0,0 @@ - [ @@ -18,12 +18,12 @@ ], /* - |-------------------------------------------------------------------------- - | API Versions - |-------------------------------------------------------------------------- - | - | Here you have a list of versions ordered from oldest to latest. - */ + |-------------------------------------------------------------------------- + | API Versions + |-------------------------------------------------------------------------- + | + | Here you have a list of versions ordered from oldest to latest. + */ 'versions' => [ 'app' => [ @@ -40,33 +40,33 @@ ], /* - |-------------------------------------------------------------------------- - | API Default - |-------------------------------------------------------------------------- - | - | + |-------------------------------------------------------------------------- + | API Default + |-------------------------------------------------------------------------- + | + | */ 'default' => 'app', /* - |-------------------------------------------------------------------------- - | Resorces homepath - |-------------------------------------------------------------------------- - | - | This value is the base folder where your resources are stored. - | + |-------------------------------------------------------------------------- + | Resorces homepath + |-------------------------------------------------------------------------- + | + | This value is the base folder where your resources are stored. + | */ 'resources_path' => 'Juampi92\APIResources\Tests\Fixtures\Resources', /* - |-------------------------------------------------------------------------- - | Resorces - |-------------------------------------------------------------------------- - | - | Here is the folder that has versionated resources. If you store them - | in the root, leave this empty '' + |-------------------------------------------------------------------------- + | Resorces + |-------------------------------------------------------------------------- + | + | Here is the folder that has versionated resources. If you store them + | in the root, leave this empty '' */ 'resources' => [ @@ -76,12 +76,12 @@ ], /* - |-------------------------------------------------------------------------- - | Routing manifest file - |-------------------------------------------------------------------------- - | - | Here is the path for the routing manifest that will cache the resolution - | strategy for each Resource in every version. + |-------------------------------------------------------------------------- + | Resolution cache path + |-------------------------------------------------------------------------- + | + | Here is the path for the resolution manifest that will cache the resolution + | strategy for each Resource in every version. */ 'routing_cache_path' => app()->bootstrapPath('cache/api-resources-cache.php'),