From 318ae2b83bf3d8c0ce1df8e94fc4a58df33e162a Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Fri, 31 Jan 2025 12:02:57 +0200 Subject: [PATCH] added operation transformers api --- src/Configuration/OperationTransformers.php | 6 +- src/Contexts/OperationTransformerContext.php | 20 --- src/Contracts/OperationTransformer.php | 4 +- src/Extensions/OperationExtension.php | 3 +- src/Generator.php | 9 +- .../ExtensionWrapperTransformer.php | 32 ----- src/Reflections/ReflectionRoute.php | 122 ------------------ src/ScrambleServiceProvider.php | 44 ++----- src/Support/ContainerUtils.php | 38 ++++++ src/Support/OperationBuilder.php | 48 ++----- .../RequestBodyExtension.php | 30 +---- .../OperationExtensions/ResponseExtension.php | 1 - 12 files changed, 74 insertions(+), 283 deletions(-) delete mode 100644 src/Contexts/OperationTransformerContext.php delete mode 100644 src/OperationTransformers/ExtensionWrapperTransformer.php delete mode 100644 src/Reflections/ReflectionRoute.php create mode 100644 src/Support/ContainerUtils.php diff --git a/src/Configuration/OperationTransformers.php b/src/Configuration/OperationTransformers.php index 3afcc5e9..5b54c9f4 100644 --- a/src/Configuration/OperationTransformers.php +++ b/src/Configuration/OperationTransformers.php @@ -2,6 +2,8 @@ namespace Dedoc\Scramble\Configuration; +use Dedoc\Scramble\Support\OperationExtensions\DeprecationExtension; +use Dedoc\Scramble\Support\OperationExtensions\ErrorResponsesExtension; use Dedoc\Scramble\Support\OperationExtensions\RequestBodyExtension; use Dedoc\Scramble\Support\OperationExtensions\RequestEssentialsExtension; use Dedoc\Scramble\Support\OperationExtensions\ResponseExtension; @@ -47,13 +49,15 @@ public function all(): array $base = $this->transformers ?: [ RequestEssentialsExtension::class, RequestBodyExtension::class, + ErrorResponsesExtension::class, ResponseExtension::class, + DeprecationExtension::class, ]; return array_values(array_unique([ ...$this->prepends, ...$base, ...$this->appends, - ])); + ], SORT_REGULAR)); } } diff --git a/src/Contexts/OperationTransformerContext.php b/src/Contexts/OperationTransformerContext.php deleted file mode 100644 index 61ba2219..00000000 --- a/src/Contexts/OperationTransformerContext.php +++ /dev/null @@ -1,20 +0,0 @@ -infer, $typeTransformer); + $routeInfo = new RouteInfo($route, $this->infer, $typeTransformer); - if (! $reflectionRoute->isControllerAction()) { + if (! $routeInfo->isClassBased()) { return null; } - $operation = $this->operationBuilder->build($reflectionRoute, $openApi, $config, $typeTransformer); + $operation = $this->operationBuilder->build($routeInfo, $openApi, $config, $typeTransformer); $this->ensureSchemaTypes($route, $operation); diff --git a/src/OperationTransformers/ExtensionWrapperTransformer.php b/src/OperationTransformers/ExtensionWrapperTransformer.php deleted file mode 100644 index fdfec82b..00000000 --- a/src/OperationTransformers/ExtensionWrapperTransformer.php +++ /dev/null @@ -1,32 +0,0 @@ -route, $context->reflectionRoute); - - $extensionInstance = new $this->extension( - - ) - } - - - public function handle(Operation $operation, OperationTransformerContext $context) - { - $routeInfo = new RouteInfo($context->route, $this->infer, $this->typeTransformer); - } -} diff --git a/src/Reflections/ReflectionRoute.php b/src/Reflections/ReflectionRoute.php deleted file mode 100644 index 788ebdd7..00000000 --- a/src/Reflections/ReflectionRoute.php +++ /dev/null @@ -1,122 +0,0 @@ -requestParametersFromCalls = new Bag; - $this->indexBuildingBroker = app(Infer\Extensions\IndexBuildingBroker::class); - } - - public function isControllerAction(): bool - { - return is_string($this->route->getAction('uses')); - } - - public function getControllerClass(): ?string - { - return $this->isControllerAction() - ? explode('@', $this->route->getAction('uses'))[0] - : null; - } - - public function getControllerMethod(): ?string - { - return $this->isControllerAction() - ? explode('@', $this->route->getAction('uses'))[1] - : null; - } - - public function parsePhpDoc(): PhpDocNode - { - if ($this->phpDoc) { - return $this->phpDoc; - } - - if (! $this->getControllerMethodAstNode()) { - return new PhpDocNode([]); - } - - $this->phpDoc = $this->getControllerMethodAstNode()->getAttribute('parsedPhpDoc') ?: new PhpDocNode([]); - - return $this->phpDoc; - } - - public function getControllerMethodAstNode(): ?ClassMethod - { - if ($this->methodNode || ! $this->isControllerAction() || ! $this->getReflectionMethod()) { - return $this->methodNode; - } - - return $this->methodNode = MethodReflector::make(...explode('@', $this->route->getAction('uses'))) - ->getAstNode(); - } - - public function getReflectionMethod(): ?ReflectionMethod - { - if (! $this->isControllerAction()) { - return null; - } - - if (! method_exists($this->getControllerClass(), $this->getControllerMethod())) { - return null; - } - - return (new ReflectionClass($this->getControllerClass())) - ->getMethod($this->getControllerMethod()); - } - - public function getReturnType() - { - return (new RouteResponseTypeRetriever($this))->getResponseType(); - } - - /** - * @todo Maybe better name is needed as this method performs method analysis, indexes building, etc. - */ - public function getMethodType(): ?FunctionType - { - if (! $this->isControllerAction() || ! $this->getReflectionMethod()) { - return null; - } - - if (! $this->methodType) { - $def = $this->infer->analyzeClass($this->getReflectionMethod()->getDeclaringClass()->getName()); - - /* - * Here the final resolution of the method types may happen. - */ - $this->methodType = $def->getMethodDefinition($this->getControllerMethod(), indexBuilders: [ - new RequestParametersBuilder($this->requestParametersFromCalls, $this->typeTransformer), - ...$this->indexBuildingBroker->indexBuilders, - ])->type; - } - - return $this->methodType; - } -} diff --git a/src/ScrambleServiceProvider.php b/src/ScrambleServiceProvider.php index f92285da..f9fe809e 100644 --- a/src/ScrambleServiceProvider.php +++ b/src/ScrambleServiceProvider.php @@ -22,6 +22,7 @@ use Dedoc\Scramble\Support\ExceptionToResponseExtensions\HttpExceptionToResponseExtension; use Dedoc\Scramble\Support\ExceptionToResponseExtensions\NotFoundExceptionToResponseExtension; use Dedoc\Scramble\Support\ExceptionToResponseExtensions\ValidationExceptionToResponseExtension; +use Dedoc\Scramble\Support\Generator\Operation; use Dedoc\Scramble\Support\Generator\TypeTransformer; use Dedoc\Scramble\Support\IndexBuilders\IndexBuilder; use Dedoc\Scramble\Support\InferExtensions\AbortHelpersExceptionInfer; @@ -38,7 +39,6 @@ use Dedoc\Scramble\Support\InferExtensions\ResponseMethodReturnTypeExtension; use Dedoc\Scramble\Support\InferExtensions\TypeTraceInfer; use Dedoc\Scramble\Support\InferExtensions\ValidatorTypeInfer; -use Dedoc\Scramble\Support\OperationBuilder; use Dedoc\Scramble\Support\OperationExtensions\DeprecationExtension; use Dedoc\Scramble\Support\OperationExtensions\ErrorResponsesExtension; use Dedoc\Scramble\Support\OperationExtensions\ParameterExtractor\AttributesParametersExtractor; @@ -46,6 +46,7 @@ use Dedoc\Scramble\Support\OperationExtensions\RequestBodyExtension; use Dedoc\Scramble\Support\OperationExtensions\RequestEssentialsExtension; use Dedoc\Scramble\Support\OperationExtensions\ResponseExtension; +use Dedoc\Scramble\Support\RouteInfo; use Dedoc\Scramble\Support\ServerFactory; use Dedoc\Scramble\Support\TypeToSchemaExtensions\AnonymousResourceCollectionTypeToSchema; use Dedoc\Scramble\Support\TypeToSchemaExtensions\CollectionToSchema; @@ -139,25 +140,6 @@ public function configurePackage(Package $package): void ); }); - $this->app->when(OperationBuilder::class) - ->needs('$extensionsClasses') - ->give(function () { - $extensions = array_merge(config('scramble.extensions', []), Scramble::$extensions); - - $operationExtensions = array_values(array_filter( - $extensions, - fn ($e) => is_a($e, OperationExtension::class, true), - )); - - return array_merge([ - RequestEssentialsExtension::class, - RequestBodyExtension::class, - ErrorResponsesExtension::class, - ResponseExtension::class, - DeprecationExtension::class, - ], $operationExtensions); - }); - $this->app->when(IndexBuildingBroker::class) ->needs('$indexBuilders') ->give(function () { @@ -214,12 +196,8 @@ public function configurePackage(Package $package): void ], $exceptionToResponseExtensions), ); }); - } - public function bootingPackage() - { Scramble::configure() - ->useConfig(config('scramble')) ->withOperationTransformers(function (OperationTransformers $transformers) { $extensions = array_merge(config('scramble.extensions', []), Scramble::$extensions); @@ -228,18 +206,14 @@ public function bootingPackage() fn ($e) => is_a($e, OperationExtension::class, true), )); - $operationExtensions = array_merge([ - RequestEssentialsExtension::class, - RequestBodyExtension::class, - ErrorResponsesExtension::class, - ResponseExtension::class, - DeprecationExtension::class, - ], $operationExtensions); - - $transformers->append(array_map(function ($extension) { - return new ExtensionWrapperTransformer($extension); - }, $operationExtensions)); + $transformers->append($operationExtensions); }); + } + + public function bootingPackage() + { + Scramble::configure() + ->useConfig(config('scramble')); $this->app->booted(function (Application $app) { Scramble::configure() diff --git a/src/Support/ContainerUtils.php b/src/Support/ContainerUtils.php new file mode 100644 index 00000000..bca8d6a6 --- /dev/null +++ b/src/Support/ContainerUtils.php @@ -0,0 +1,38 @@ + $class + * @return T + */ + public static function makeContextable(string $class, array $contextfulBindings = []) + { + $reflectionClass = new ReflectionClass($class); + + $parameters = $reflectionClass->getConstructor()?->getParameters() ?? []; + + $contextfulArguments = collect($parameters) + ->mapWithKeys(function (ReflectionParameter $p) use ($contextfulBindings) { + $parameterClass = $p->getType() instanceof ReflectionNamedType + ? $p->getType()->getName() + : null; + + return $parameterClass && isset($contextfulBindings[$parameterClass]) ? [ + $p->name => $contextfulBindings[$parameterClass], + ] : []; + }) + ->all(); + + return app()->make($class, $contextfulArguments); + } +} diff --git a/src/Support/OperationBuilder.php b/src/Support/OperationBuilder.php index f5b17cb4..f22ecc71 100644 --- a/src/Support/OperationBuilder.php +++ b/src/Support/OperationBuilder.php @@ -2,10 +2,8 @@ namespace Dedoc\Scramble\Support; -use Dedoc\Scramble\Contexts\OperationTransformerContext; -use Dedoc\Scramble\Extensions\OperationExtension; +use Dedoc\Scramble\Contracts\OperationTransformer; use Dedoc\Scramble\GeneratorConfig; -use Dedoc\Scramble\Reflections\ReflectionRoute; use Dedoc\Scramble\Support\Generator\OpenApi; use Dedoc\Scramble\Support\Generator\Operation; use Dedoc\Scramble\Support\Generator\TypeTransformer; @@ -13,54 +11,32 @@ /** @internal */ class OperationBuilder { - /** @var class-string */ - private array $extensionsClasses; - - public function __construct(array $extensionsClasses = []) - { - $this->extensionsClasses = $extensionsClasses; - } - - public function build(ReflectionRoute $reflectionRoute, OpenApi $openApi, GeneratorConfig $config, TypeTransformer $typeTransformer) + public function build(RouteInfo $routeInfo, OpenApi $openApi, GeneratorConfig $config, TypeTransformer $typeTransformer) { $operation = new Operation('get'); - $operationTransformerContext = new OperationTransformerContext( - $reflectionRoute->route, - $reflectionRoute, - $openApi, - $config, - ); - - $routeInfo = new RouteInfo($route, $this->infer, $typeTransformer); - - foreach ($config->operationTransformers->all() as $operationTransformer) { - $instance = is_callable($operationTransformer) - ? $operationTransformer - : ContainerUtils::makeContextable($operationTransformer, [ + foreach ($config->operationTransformers->all() as $operationTransformerClass) { + $instance = is_callable($operationTransformerClass) + ? $operationTransformerClass + : ContainerUtils::makeContextable($operationTransformerClass, [ OpenApi::class => $openApi, GeneratorConfig::class => $config, TypeTransformer::class => $typeTransformer, ]); if (is_callable($instance)) { - $instance($operation, $context); + $instance($operation, $routeInfo); continue; } - $instance->handle($operation, $context); - } - + if ($instance instanceof OperationTransformer) { + $instance->handle($operation, $routeInfo); - foreach ($this->extensionsClasses as $extensionClass) { - $extension = app()->make($extensionClass, [ - 'openApi' => $openApi, - 'config' => $config, - 'openApiTransformer' => $typeTransformer, - ]); + continue; + } - $extension->handle($operation, $routeInfo); + // throw here. } return $operation; diff --git a/src/Support/OperationExtensions/RequestBodyExtension.php b/src/Support/OperationExtensions/RequestBodyExtension.php index 136de839..75a96270 100644 --- a/src/Support/OperationExtensions/RequestBodyExtension.php +++ b/src/Support/OperationExtensions/RequestBodyExtension.php @@ -4,6 +4,7 @@ use Dedoc\Scramble\Extensions\OperationExtension; use Dedoc\Scramble\Scramble; +use Dedoc\Scramble\Support\ContainerUtils; use Dedoc\Scramble\Support\Generator\Combined\AllOf; use Dedoc\Scramble\Support\Generator\Operation; use Dedoc\Scramble\Support\Generator\Parameter; @@ -199,7 +200,7 @@ private function extractParameters(Operation $operation, RouteInfo $routeInfo) { $result = []; foreach ($this->config->parametersExtractors->all() as $extractorClass) { - $extractor = $this->buildContextfulExtractor($extractorClass, [ + $extractor = ContainerUtils::makeContextable($extractorClass, [ TypeTransformer::class => $this->openApiTransformer, Operation::class => $operation, ]); @@ -209,31 +210,4 @@ private function extractParameters(Operation $operation, RouteInfo $routeInfo) return $result; } - - /** - * @template T of ParameterExtractor - * - * @param class-string $class - * @return T - */ - private function buildContextfulExtractor(string $class, array $contextfulBindings): ParameterExtractor - { - $reflectionClass = new ReflectionClass($class); - - $parameters = $reflectionClass->getConstructor()?->getParameters() ?? []; - - $contextfulArguments = collect($parameters) - ->mapWithKeys(function (ReflectionParameter $p) use ($contextfulBindings) { - $parameterClass = $p->getType() instanceof ReflectionNamedType - ? $p->getType()->getName() - : null; - - return $parameterClass && isset($contextfulBindings[$parameterClass]) ? [ - $p->name => $contextfulBindings[$parameterClass], - ] : []; - }) - ->all(); - - return app()->make($class, $contextfulArguments); - } } diff --git a/src/Support/OperationExtensions/ResponseExtension.php b/src/Support/OperationExtensions/ResponseExtension.php index bdfc91e3..9933edc0 100644 --- a/src/Support/OperationExtensions/ResponseExtension.php +++ b/src/Support/OperationExtensions/ResponseExtension.php @@ -28,7 +28,6 @@ public function handle(Operation $operation, RouteInfo $routeInfo) $responses = collect($returnTypes) ->merge(optional($routeInfo->getMethodType())->exceptions ?: []) -// ->dd() ->map($this->openApiTransformer->toResponse(...)) ->filter() ->unique(fn ($response) => ($response instanceof Response ? $response->code : 'ref').':'.json_encode($response->toArray()))