diff --git a/ModelDescriber/JMSModelDescriber.php b/ModelDescriber/JMSModelDescriber.php index 991228fd2..5de1cf5a2 100644 --- a/ModelDescriber/JMSModelDescriber.php +++ b/ModelDescriber/JMSModelDescriber.php @@ -126,6 +126,7 @@ public function describe(Model $model, OA\Schema $schema) } catch (\ReflectionException $ignored) { } } + $this->checkRequiredFields($reflections, $schema, $name); if (null !== $item->setter) { try { $reflections[] = new \ReflectionMethod($item->class, $item->setter); @@ -346,4 +347,36 @@ private function propertyTypeUsesGroups(array $type) return null; } } + + /** + * Mark property as required if it is not nullable. + * + * @param array $reflections + * @param \OpenApi\Annotations\Schema $schema + * @param string $name + */ + public function checkRequiredFields(array $reflections, OA\Schema $schema, string $name): void + { + foreach ($reflections as $reflection) { + $nullable = false; + if ($reflection instanceof \ReflectionProperty) { + $type = PHP_VERSION_ID >= 70400 ? $reflection->getType() : null; + if (null !== $type && !$type->allowsNull()) { + $nullable = true; + } + } elseif ($reflection instanceof \ReflectionMethod) { + $returnType = $reflection->getReturnType(); + if (null !== $returnType && !$returnType->allowsNull()) { + $nullable = true; + } + } + if ($nullable) { + $required = Generator::UNDEFINED !== $schema->required ? $schema->required : []; + $required[] = $name; + + $schema->required = $required; + break; + } + } + } } diff --git a/Tests/Functional/Controller/JMSController.php b/Tests/Functional/Controller/JMSController.php index 392eace10..8257c3aef 100644 --- a/Tests/Functional/Controller/JMSController.php +++ b/Tests/Functional/Controller/JMSController.php @@ -15,6 +15,7 @@ use Nelmio\ApiDocBundle\Tests\Functional\Entity\JMSComplex; use Nelmio\ApiDocBundle\Tests\Functional\Entity\JMSDualComplex; use Nelmio\ApiDocBundle\Tests\Functional\Entity\JMSNamingStrategyConstraints; +use Nelmio\ApiDocBundle\Tests\Functional\Entity\JMSTyped; use Nelmio\ApiDocBundle\Tests\Functional\Entity\JMSUser; use Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSChat; use Nelmio\ApiDocBundle\Tests\Functional\Entity\NestedGroup\JMSChatRoomUser; @@ -136,4 +137,16 @@ public function minUserAction() public function minUserNestedAction() { } + + /** + * @Route("/api/jms_typed", methods={"GET"}) + * @OA\Response( + * response=200, + * description="Success", + * @Model(type=JMSTyped::class) + * ) + */ + public function typedAction() + { + } } diff --git a/Tests/Functional/Entity/JMSTyped.php b/Tests/Functional/Entity/JMSTyped.php new file mode 100644 index 000000000..873841c02 --- /dev/null +++ b/Tests/Functional/Entity/JMSTyped.php @@ -0,0 +1,43 @@ +markTestSkipped('PHP 7.4 required.'); + } + $this->assertEquals([ + 'type' => 'object', + 'properties' => [ + 'id' => ['type' => 'integer'], + 'user' => ['$ref' => '#/components/schemas/JMSUser'], + 'name' => ['type' => 'string'], + 'virtual_friend' => ['$ref' => '#/components/schemas/JMSUser'], + ], + 'required' => [ + 'virtual_friend', + 'id', + 'user', + ], + 'schema' => 'JMSTyped', + ], json_decode($this->getModel('JMSTyped')->toJson(), true)); + } }