Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple requests #468 #473

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Support/OperationExtensions/RequestBodyExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ protected function extractRouteRequestValidationRules(Route $route, $methodNode)
if (($formRequestRulesExtractor = new FormRequestRulesExtractor($methodNode))->shouldHandle()) {
if (count($formRequestRules = $formRequestRulesExtractor->extract($route))) {
$rules = array_merge($rules, $formRequestRules);
$nodesResults[] = $formRequestRulesExtractor->node();
array_push($nodesResults, ...$formRequestRulesExtractor->nodes());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
use PhpParser\Node\Param;
Expand All @@ -23,7 +24,7 @@ public function __construct(?FunctionLike $handler)
$this->handler = $handler;
}

public function shouldHandle()
public function shouldHandle(): bool
{
if (! $this->handler) {
return false;
Expand All @@ -33,84 +34,87 @@ public function shouldHandle()
return false;
}

$className = $this->getFormRequestClassName();

if (is_a($className, BaseData::class, true)) {
return false;
}

return true;
}

public function node()
/**
* @return ValidationNodesResult[]
*/
public function nodes(): array
{
$requestClassName = $this->getFormRequestClassName();

$classReflector = Infer\Reflector\ClassReflector::make($requestClassName);

$phpDocReflector = SchemaClassDocReflector::createFromDocString($classReflector->getReflection()->getDocComment() ?: '');

$schemaName = ($phpDocReflector->getTagValue('@ignoreSchema')->value ?? null) !== null
? null
: $phpDocReflector->getSchemaName($requestClassName);

return new ValidationNodesResult(
(new NodeFinder)->find(
Arr::wrap($classReflector->getMethod('rules')->getAstNode()->stmts),
fn (Node $node) => $node instanceof Node\Expr\ArrayItem
&& $node->key instanceof Node\Scalar\String_
&& $node->getAttribute('parsedPhpDoc'),
),
schemaName: $schemaName,
description: $phpDocReflector->getDescription(),
);
return $this->getFormRequestClassNames()->map(function (string $requestClassName) {
$classReflector = Infer\Reflector\ClassReflector::make($requestClassName);

$phpDocReflector = SchemaClassDocReflector::createFromDocString($classReflector->getReflection()->getDocComment() ?: '');

$schemaName = ($phpDocReflector->getTagValue('@ignoreSchema')->value ?? null) !== null
? null
: $phpDocReflector->getSchemaName($requestClassName);

return new ValidationNodesResult(
(new NodeFinder)->find(
Arr::wrap($classReflector->getMethod('rules')->getAstNode()->stmts),
fn (Node $node) => $node instanceof Node\Expr\ArrayItem
&& $node->key instanceof Node\Scalar\String_
&& $node->getAttribute('parsedPhpDoc'),
),
schemaName: $schemaName,
description: $phpDocReflector->getDescription(),
);
})->all();
}

public function extract(Route $route)
public function extract(Route $route): array
{
$requestClassName = $this->getFormRequestClassName();

/** @var Request $request */
$request = (new $requestClassName);

$rules = [];

if (method_exists($request, 'setMethod')) {
$request->setMethod($route->methods()[0]);
}
$this->getFormRequestClassNames()->each(function (string $requestClassName) use ($route, &$rules) {
/** @var Request $request */
$request = (new $requestClassName);

if (method_exists($request, 'rules')) {
$rules = $request->rules();
}
if (method_exists($request, 'setMethod')) {
$request->setMethod($route->methods()[0]);
}

if (method_exists($request, 'rules')) {
$rules = array_merge($rules, $request->rules());
}
});

return $rules;
}

private function findCustomRequestParam(Param $param)
private function findCustomRequestParam(Param $param): bool
{
if (! $param->type || ! method_exists($param->type, '__toString')) {
return false;
}

$className = (string) $param->type;

if (is_a($className, BaseData::class, true)) {
return false;
}

return method_exists($className, 'rules');
}

private function getFormRequestClassName()
private function getFormRequestClassNames(): Collection
{
$requestParam = collect($this->handler->getParams())->first($this->findCustomRequestParam(...));
return collect($this->handler->getParams())
->filter($this->findCustomRequestParam(...))
->map(function($requestParam){
$requestClassName = (string) $requestParam->type;

$requestClassName = (string) $requestParam->type;
$reflectionClass = new ReflectionClass($requestClassName);

$reflectionClass = new ReflectionClass($requestClassName);

// If the classname is actually an interface, it might be bound to the container.
if (! $reflectionClass->isInstantiable() && app()->bound($requestClassName)) {
$classInstance = app()->getBindings()[$requestClassName]['concrete'](app());
$requestClassName = $classInstance::class;
}
// If the classname is actually an interface, it might be bound to the container.
if (! $reflectionClass->isInstantiable() && app()->bound($requestClassName)) {
$classInstance = app()->getBindings()[$requestClassName]['concrete'](app());
$requestClassName = $classInstance::class;
}

return $requestClassName;
return $requestClassName;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ protected function registerInterfaceBasedRequest(Application $app)
{
$app->bind(DataRequestContract::class, ConcreteDataRequest::class);
}

public function test_request_multiple(): void
{
$openApi = $this->generateForRoute(function () {
return Route::post('/test', FormRequestMultipleRulesExtractorTestController::class);
});

expect($openApi['components']['schemas']['FirstRequest']['properties'])
->toHaveKey('first')
->toHaveKey('second');
}
}

interface DataRequestContract
Expand All @@ -45,3 +56,24 @@ class FormRequestRulesExtractorTestController
{
public function __invoke(DataRequestContract $request) {}
}

class FirstRequest extends FormRequest implements DataRequestContract
{
public function rules()
{
return ['first' => 'required'];
}
}

class SecondRequest extends FormRequest implements DataRequestContract
{
public function rules()
{
return ['second' => 'required'];
}
}

class FormRequestMultipleRulesExtractorTestController
{
public function __invoke(FirstRequest $firstRequest, SecondRequest $secondRequest) {}
}