From 1434c8715121fdb63f19e49abb65c96ea2fc2d50 Mon Sep 17 00:00:00 2001 From: Joseph Martell Date: Fri, 4 Mar 2022 20:59:23 +0100 Subject: [PATCH 1/2] A request extending the Symfony request object can have a schema. The schema is automatically put in the request object path if it's passed as a method argument --- src/Attributes/Schema.php | 2 +- src/ComponentBuilder.php | 13 +++++--- src/GeneratorHttp.php | 41 +++++++++++++++++++++++- tests/Examples/Dummy/DummyController.php | 9 +++--- tests/Examples/Dummy/DummyRequest.php | 21 ++++++++++++ 5 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 tests/Examples/Dummy/DummyRequest.php diff --git a/src/Attributes/Schema.php b/src/Attributes/Schema.php index f612bed..f2a6122 100644 --- a/src/Attributes/Schema.php +++ b/src/Attributes/Schema.php @@ -59,7 +59,7 @@ public function jsonSerialize(): array $firstProperty = reset($this->properties); if ($firstProperty instanceof RefProperty || $firstProperty instanceof MediaProperty) { - $schema = $firstProperty; + $schema = $firstProperty->jsonSerialize(); } else { $array = []; diff --git a/src/ComponentBuilder.php b/src/ComponentBuilder.php index cb8ab3e..0afa458 100644 --- a/src/ComponentBuilder.php +++ b/src/ComponentBuilder.php @@ -4,19 +4,21 @@ namespace OpenApiGenerator; -use OpenApiGenerator\Attributes\Property; +use OpenApiGenerator\Attributes\PropertyInterface; use OpenApiGenerator\Attributes\PropertyItems; use OpenApiGenerator\Attributes\Schema; class ComponentBuilder { private ?Schema $currentSchema = null; - private ?Property $currentProperty = null; + private ?PropertyInterface $currentProperty = null; - public function addSchema(Schema $schema, string $className): bool + public function __construct(private $noMedia = true) { - $schema->setNoMedia(true); + } + public function addSchema(Schema $schema, string $className): bool + { if (!$schema->getName()) { $explodedNamespace = explode('\\', $className); $className = end($explodedNamespace); @@ -28,7 +30,7 @@ public function addSchema(Schema $schema, string $className): bool return true; } - public function addProperty(Property $property): bool + public function addProperty(PropertyInterface $property): bool { $this->saveProperty(); $this->currentProperty = $property; @@ -57,6 +59,7 @@ public function addPropertyItems(PropertyItems $items): bool public function getComponent(): ?Schema { $this->saveProperty(); + $this->currentSchema->setNoMedia($this->noMedia); return $this->currentSchema; } diff --git a/src/GeneratorHttp.php b/src/GeneratorHttp.php index db76ac1..4fd13b6 100644 --- a/src/GeneratorHttp.php +++ b/src/GeneratorHttp.php @@ -14,11 +14,15 @@ use OpenApiGenerator\Attributes\Property; use OpenApiGenerator\Attributes\PropertyItems; use OpenApiGenerator\Attributes\PUT; +use OpenApiGenerator\Attributes\RefProperty; use OpenApiGenerator\Attributes\RequestBody; use OpenApiGenerator\Attributes\Response; use OpenApiGenerator\Attributes\Route; +use OpenApiGenerator\Attributes\Schema; +use OpenApiGenerator\Tests\Examples\Dummy\DummyRequest; use ReflectionAttribute; use ReflectionClass; +use ReflectionMethod; use ReflectionParameter; use Symfony\Component\HttpFoundation\Request; @@ -50,8 +54,10 @@ public function append(ReflectionClass $reflectionClass) } } + $requestBody = $this->getRequestBody($method); + $pathBuilder = new PathMethodBuilder(); - $pathBuilder->setRequestBody(new RequestBody()); + $pathBuilder->setRequestBody($requestBody); // Add method Attributes to the builder foreach ($methodAttributes as $attribute) { @@ -82,6 +88,39 @@ public function append(ReflectionClass $reflectionClass) } } + private function getRequestBody(ReflectionMethod $method): RequestBody + { + $requestBody = new RequestBody(); + + $requestClass = array_filter( + $method->getParameters(), + static fn(ReflectionParameter $parameter): bool => is_subclass_of( + $parameter->getType()->getName(), + Request::class + ) + ); + + if (count($requestClass) > 0) { + $requestReflection = new ReflectionClass(DummyRequest::class); + $schemaAttributes = $requestReflection->getAttributes(Schema::class); + /** @var ReflectionAttribute|false $schema */ + $schema = reset($schemaAttributes); + + if ($schema) { + /** @var Schema $requestSchema */ + $requestSchema = $schema->newInstance(); + + $builder = new ComponentBuilder(false); + $builder->addSchema($requestSchema, DummyRequest::class); + $builder->addProperty(new RefProperty($requestSchema->getName())); + + $requestBody->setSchema($builder->getComponent()); + } + } + + return $requestBody; + } + /** * @param ReflectionParameter[] $methodParameters * @return Parameter[] diff --git a/tests/Examples/Dummy/DummyController.php b/tests/Examples/Dummy/DummyController.php index 4f12b4f..29f408e 100644 --- a/tests/Examples/Dummy/DummyController.php +++ b/tests/Examples/Dummy/DummyController.php @@ -27,7 +27,9 @@ class DummyController GET("/path", ["Dummy"], "Dummy get path"), Property(Type::STRING, "prop1", "Prop 1", "val1"), Property(Type::STRING, "prop2", "Prop 2", "val2"), - Response + Response, + Property(Type::STRING, "Response prop 1", "Prop response 1", "1"), + Property(Type::INT, "Response prop 2", "Prop response 2", 4) ] public function get(): void { @@ -76,12 +78,9 @@ public function post(): void #[ PUT("/path/{id}", ["Dummy"], "Dummy put"), - Property(Type::STRING, "prop1"), - Property(Type::ID, "prop2"), - Property(Type::BOOLEAN, "prop3"), Response(204) ] - public function put(#[IDParam] int $id): void + public function put(#[IDParam] int $id, DummyRequest $dummyRequest): void { // } diff --git a/tests/Examples/Dummy/DummyRequest.php b/tests/Examples/Dummy/DummyRequest.php new file mode 100644 index 0000000..82ad1b7 --- /dev/null +++ b/tests/Examples/Dummy/DummyRequest.php @@ -0,0 +1,21 @@ + Date: Tue, 8 Mar 2022 01:20:26 +0300 Subject: [PATCH 2/2] Added the ability to specify a ref to a schema in the Property attribute --- README.md | 15 +++++++++++++++ src/Attributes/Property.php | 11 ++++++++++- tests/Examples/Dummy/DummyRequest.php | 3 ++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5a5f94d..80c6dcb 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,24 @@ class Controller { Property(PropertyType::STRING, "prop1", description: "Property description", enum: ["val1", "val2"]), Property(PropertyType::INT, "prop2", example: 1), Property(PropertyType::BOOLEAN, "prop3"), + Property(PropertyType::REF, "prop4", ref: RefSchema::class) Response(ref: SchemaName::class, description: "Response description") ] public function get(#[Parameter("Parameter description")] int $id): JsonResponse { // ... } } + +#[ + Schema, + Property(Type::STRING, "Property 1"), + Property(Type::INT, "Property 2"), +] +class RefSchema +{ + public string $property1; + public int $property2; +} ``` Will generate @@ -75,6 +87,9 @@ Will generate "prop3": { "type": "boolean", "description": "" + }, + "prop4": { + "$ref": "#/components/schemas/RefSchema" } } } diff --git a/src/Attributes/Property.php b/src/Attributes/Property.php index 39080db..583b96a 100644 --- a/src/Attributes/Property.php +++ b/src/Attributes/Property.php @@ -24,8 +24,13 @@ public function __construct( private string $description = '', private mixed $example = null, private ?string $format = null, - private ?array $enum = null + private ?array $enum = null, + private ?string $ref = null ) { + if ($this->ref) { + $ref = explode('\\', $this->ref); + $this->ref = end($ref); + } } public function setPropertyItems(PropertyItems $propertyItems): void @@ -55,6 +60,10 @@ public function jsonSerialize(): array } } + if ($this->type === PropertyType::REF) { + return ['$ref' => "#/components/schemas/$this->ref"]; + } + if ($this->type === PropertyType::ID) { $type = 'integer'; $minimum = 1; diff --git a/tests/Examples/Dummy/DummyRequest.php b/tests/Examples/Dummy/DummyRequest.php index 82ad1b7..8035737 100644 --- a/tests/Examples/Dummy/DummyRequest.php +++ b/tests/Examples/Dummy/DummyRequest.php @@ -13,7 +13,8 @@ Property(Type::STRING, "Property 1"), Property(Type::INT, "Property 2"), Property(Type::ARRAY, "Property 3"), - PropertyItems(Type::STRING) + PropertyItems(Type::STRING), + Property(Type::REF, "Property 4", ref: DummyRefComponent::class), ] class DummyRequest extends Request {