Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
romalytvynenko committed Jan 31, 2025
1 parent 23e15a7 commit 85858b9
Show file tree
Hide file tree
Showing 10 changed files with 344 additions and 9 deletions.
9 changes: 6 additions & 3 deletions src/Configuration/GeneratorConfigCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

class GeneratorConfigCollection
{
/**
* @var array<string, GeneratorConfig>
*/
private array $apis = [];

public function __construct()
Expand All @@ -20,6 +23,7 @@ private function buildDefaultApiConfiguration(): GeneratorConfig
{
return (new GeneratorConfig(
parametersExtractors: new ParametersExtractors,
operationTransformers: new OperationTransformers,
))->expose(
ui: fn (Router $router, $action) => $router->get('docs/api', $action)->name('scramble.docs.ui'),
document: fn (Router $router, $action) => $router->get('docs/api.json', $action)->name('scramble.docs.document'),
Expand All @@ -39,9 +43,8 @@ public function register(string $name, array $config): GeneratorConfig
{
$this->apis[$name] = $generatorConfig = new GeneratorConfig(
config: array_merge(config('scramble') ?: [], $config),
parametersExtractors: isset($this->apis[Scramble::DEFAULT_API])
? $this->apis[Scramble::DEFAULT_API]->parametersExtractors
: new ParametersExtractors,
parametersExtractors: $this->apis[Scramble::DEFAULT_API]->parametersExtractors,
operationTransformers: $this->apis[Scramble::DEFAULT_API]->operationTransformers,
);

return $generatorConfig;
Expand Down
59 changes: 59 additions & 0 deletions src/Configuration/OperationTransformers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace Dedoc\Scramble\Configuration;

use Dedoc\Scramble\Support\OperationExtensions\RequestBodyExtension;
use Dedoc\Scramble\Support\OperationExtensions\RequestEssentialsExtension;
use Dedoc\Scramble\Support\OperationExtensions\ResponseExtension;
use Illuminate\Support\Arr;

class OperationTransformers
{
protected array $transformers = [];

protected array $appends = [];

protected array $prepends = [];

public function append(array|callable|string $transformers)
{
$this->appends = array_merge(
$this->appends,
Arr::wrap($transformers)
);

return $this;
}

public function prepend(array|callable|string $transformers)
{
$this->prepends = array_merge(
$this->prepends,
Arr::wrap($transformers)
);

return $this;
}

public function use(array $transformers)
{
$this->transformers = $transformers;

return $this;
}

public function all(): array
{
$base = $this->transformers ?: [
RequestEssentialsExtension::class,
RequestBodyExtension::class,
ResponseExtension::class,
];

return array_values(array_unique([
...$this->prepends,
...$base,
...$this->appends,
]));
}
}
20 changes: 20 additions & 0 deletions src/Contexts/OperationTransformerContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Dedoc\Scramble\Contexts;

use Dedoc\Scramble\GeneratorConfig;
use Dedoc\Scramble\Reflections\ReflectionRoute;
use Dedoc\Scramble\Support\Generator\OpenApi;
use Illuminate\Routing\Route;

class OperationTransformerContext
{
public function __construct(
public readonly Route $route,
public readonly ReflectionRoute $reflectionRoute,
public readonly OpenApi $document,
public readonly GeneratorConfig $config,
)
{
}
}
11 changes: 11 additions & 0 deletions src/Contracts/OperationTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Dedoc\Scramble\Contracts;

use Dedoc\Scramble\Contexts\OperationTransformerContext;
use Dedoc\Scramble\Support\Generator\Operation;

interface OperationTransformer
{
public function handle(Operation $operation, OperationTransformerContext $context);
}
9 changes: 5 additions & 4 deletions src/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

use Dedoc\Scramble\Attributes\ExcludeAllRoutesFromDocs;
use Dedoc\Scramble\Attributes\ExcludeRouteFromDocs;
use Dedoc\Scramble\Contexts\OperationTransformerContext;
use Dedoc\Scramble\Exceptions\RouteAware;
use Dedoc\Scramble\Infer\Services\FileParser;
use Dedoc\Scramble\OpenApiVisitor\SchemaEnforceVisitor;
use Dedoc\Scramble\Reflections\ReflectionRoute;
use Dedoc\Scramble\Support\Generator\Components;
use Dedoc\Scramble\Support\Generator\InfoObject;
use Dedoc\Scramble\Support\Generator\OpenApi;
Expand Down Expand Up @@ -34,7 +36,6 @@ class Generator
public function __construct(
private OperationBuilder $operationBuilder,
private ServerFactory $serverFactory,
private FileParser $fileParser,
private Infer $infer
) {}

Expand Down Expand Up @@ -202,13 +203,13 @@ private function buildTypeTransformer(OpenApiContext $context): TypeTransformer

private function routeToOperation(OpenApi $openApi, Route $route, GeneratorConfig $config, TypeTransformer $typeTransformer)
{
$routeInfo = new RouteInfo($route, $this->infer, $typeTransformer);
$reflectionRoute = new ReflectionRoute($route, $this->infer, $typeTransformer);

if (! $routeInfo->isClassBased()) {
if (! $reflectionRoute->isControllerAction()) {
return null;
}

$operation = $this->operationBuilder->build($routeInfo, $openApi, $config, $typeTransformer);
$operation = $this->operationBuilder->build($reflectionRoute, $openApi, $config, $typeTransformer);

$this->ensureSchemaTypes($route, $operation);

Expand Down
34 changes: 34 additions & 0 deletions src/GeneratorConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
namespace Dedoc\Scramble;

use Closure;
use Dedoc\Scramble\Configuration\OperationTransformers;
use Dedoc\Scramble\Configuration\ParametersExtractors;
use Illuminate\Routing\Route;
use Illuminate\Routing\Router;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use ReflectionFunction;
use ReflectionNamedType;

class GeneratorConfig
{
Expand All @@ -26,6 +29,7 @@ public function __construct(
private ?Closure $routeResolver = null,
private array $afterOpenApiGenerated = [],
public readonly ParametersExtractors $parametersExtractors = new ParametersExtractors,
public readonly OperationTransformers $operationTransformers = new OperationTransformers,
) {}

public function config(array $config)
Expand Down Expand Up @@ -102,6 +106,36 @@ public function withParametersExtractors(callable $callback): static
return $this;
}

public function withOperationTransformers(array|string|callable $cb): static
{
if ($this->isOperationTransformerMapper($cb)) {
$cb($this->operationTransformers);

return $this;
}

$cb = function (OperationTransformers $transformers) use ($cb) {
$transformers->append($cb);
};

$cb($this->operationTransformers);

return $this;
}

private function isOperationTransformerMapper($cb): bool
{
if (! $cb instanceof Closure) {
return false;
}

$reflection = new ReflectionFunction($cb);

return count($reflection->getParameters()) === 1
&& $reflection->getParameters()[0]->getType() instanceof ReflectionNamedType
&& is_a($reflection->getParameters()[0]->getType()->getName(), OperationTransformers::class, true);
}

public function get(string $key, mixed $default = null)
{
return Arr::get($this->config, $key, $default);
Expand Down
32 changes: 32 additions & 0 deletions src/OperationTransformers/ExtensionWrapperTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Dedoc\Scramble\OperationTransformers;

use Dedoc\Scramble\Contexts\OperationTransformerContext;
use Dedoc\Scramble\Contracts\OperationTransformer;
use Dedoc\Scramble\Infer;
use Dedoc\Scramble\Support\Generator\Operation;
use Dedoc\Scramble\Support\Generator\TypeTransformer;
use Dedoc\Scramble\Support\RouteInfo;

class ExtensionWrapperTransformer
{
public function __construct(private string $extension)
{
}

public function __invoke(Operation $operation, OperationTransformerContext $context)
{
$routeInfo = new RouteInfo($context->route, $context->reflectionRoute);

$extensionInstance = new $this->extension(

)
}


public function handle(Operation $operation, OperationTransformerContext $context)
{
$routeInfo = new RouteInfo($context->route, $this->infer, $this->typeTransformer);
}
}
122 changes: 122 additions & 0 deletions src/Reflections/ReflectionRoute.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?php

namespace Dedoc\Scramble\Reflections;

use Dedoc\Scramble\Infer;
use Dedoc\Scramble\Infer\Reflector\MethodReflector;
use Dedoc\Scramble\Support\IndexBuilders\Bag;
use Dedoc\Scramble\Support\IndexBuilders\RequestParametersBuilder;
use Dedoc\Scramble\Support\RouteResponseTypeRetriever;
use Dedoc\Scramble\Support\Type\FunctionType;
use Illuminate\Routing\Route;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use ReflectionClass;
use ReflectionMethod;

class ReflectionRoute
{
public ?FunctionType $methodType = null;

private ?PhpDocNode $phpDoc = null;

private ?ClassMethod $methodNode = null;

public readonly Bag $requestParametersFromCalls;

public readonly Infer\Extensions\IndexBuildingBroker $indexBuildingBroker;

public function __construct(
public readonly Route $route,
) {
$this->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;
}
}
Loading

0 comments on commit 85858b9

Please sign in to comment.