Repository: nelmio/NelmioApiDocBundle (PHP)

Issue:
[Bug]: Using #[NotNull] / #[NotBlank] doesn't influence nullable
### Version

4.26.1

### Description

It is possible (/feasible?) to apply `#[NotNull]` / `#{NotBlank]` constraints on nullable properties in some scenarios (i.e.: not let Symfony serializer error when value is not provided by allowing it to assign `null` instead). But while these annotations are handled by this bundle, they don't seem to affect `nullable`, but only `required` of the object type?

So IMO the `SymfonyConstraintAnnotationReader` should not just only update the list of required fields, but also do `$property->nullable = false;` (or at least if it's unset / default)

### Additional context

_No response_


Discussion:
Also interested in this.
EDIT: PR is here #2329 



Patch:
diff --git a/src/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php b/src/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php
index 39db78e5e..116958d07 100644
--- a/src/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php
+++ b/src/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php
@@ -90,6 +90,7 @@ private function processPropertyAnnotations($reflection, OA\Property $property,
                 $existingRequiredFields[] = $propertyName;
 
                 $this->schema->required = array_values(array_unique($existingRequiredFields));
+                $property->nullable = false;
             } elseif ($annotation instanceof Assert\Length) {
                 if (isset($annotation->min)) {
                     $property->minLength = $annotation->min;


Test patch:
diff --git a/tests/Functional/Entity/SymfonyConstraintsWithValidationGroups80.php b/tests/Functional/Entity/SymfonyConstraintsWithValidationGroups80.php
index c03794602..b08f0d256 100644
--- a/tests/Functional/Entity/SymfonyConstraintsWithValidationGroups80.php
+++ b/tests/Functional/Entity/SymfonyConstraintsWithValidationGroups80.php
@@ -43,4 +43,13 @@ class SymfonyConstraintsWithValidationGroups80
      * @Assert\Valid
      */
     public $propertyArray;
+
+    /**
+     * @var ?string
+     *
+     * @Groups({"test"})
+     *
+     * @Assert\NotNull(groups={"test"})
+     */
+    public $propertyNotNullOnSpecificGroup;
 }
diff --git a/tests/Functional/Entity/SymfonyConstraintsWithValidationGroups81.php b/tests/Functional/Entity/SymfonyConstraintsWithValidationGroups81.php
index 609950221..cab5853a4 100644
--- a/tests/Functional/Entity/SymfonyConstraintsWithValidationGroups81.php
+++ b/tests/Functional/Entity/SymfonyConstraintsWithValidationGroups81.php
@@ -37,4 +37,11 @@ class SymfonyConstraintsWithValidationGroups81
     #[OA\Property(type: 'array', items: new OA\Items(type: 'string'))]
     #[Assert\Valid]
     public $propertyArray;
+
+    /**
+     * @var ?string
+     */
+    #[Groups(['test'])]
+    #[Assert\NotNull(groups: ['test'])]
+    public $propertyNotNullOnSpecificGroup;
 }
diff --git a/tests/Functional/Fixtures/MapQueryStringCleanupComponents.json b/tests/Functional/Fixtures/MapQueryStringCleanupComponents.json
index dd8152e8d..999263bcf 100644
--- a/tests/Functional/Fixtures/MapQueryStringCleanupComponents.json
+++ b/tests/Functional/Fixtures/MapQueryStringCleanupComponents.json
@@ -155,6 +155,14 @@
                                 "type": "string"
                             }
                         }
+                    },
+                    {
+                        "name": "propertyNotNullOnSpecificGroup",
+                        "in": "query",
+                        "required": true,
+                        "schema": {
+                            "type": "string"
+                        }
                     }
                 ],
                 "responses": {
diff --git a/tests/Functional/Fixtures/MapQueryStringController.json b/tests/Functional/Fixtures/MapQueryStringController.json
index 2dae21ab6..d29e83361 100644
--- a/tests/Functional/Fixtures/MapQueryStringController.json
+++ b/tests/Functional/Fixtures/MapQueryStringController.json
@@ -155,6 +155,14 @@
                                 "type": "string"
                             }
                         }
+                    },
+                    {
+                        "name": "propertyNotNullOnSpecificGroup",
+                        "in": "query",
+                        "required": true,
+                        "schema": {
+                            "type": "string"
+                        }
                     }
                 ],
                 "responses": {
@@ -1095,7 +1103,8 @@
             "SymfonyConstraintsWithValidationGroups": {
                 "required": [
                     "property",
-                    "propertyInDefaultGroup"
+                    "propertyInDefaultGroup",
+                    "propertyNotNullOnSpecificGroup"
                 ],
                 "properties": {
                     "property": {
@@ -1113,6 +1122,9 @@
                         "items": {
                             "type": "string"
                         }
+                    },
+                    "propertyNotNullOnSpecificGroup": {
+                        "type": "string"
                     }
                 },
                 "type": "object"
diff --git a/tests/Functional/Fixtures/MapRequestPayloadController.json b/tests/Functional/Fixtures/MapRequestPayloadController.json
index 139309ced..7fff78337 100644
--- a/tests/Functional/Fixtures/MapRequestPayloadController.json
+++ b/tests/Functional/Fixtures/MapRequestPayloadController.json
@@ -147,7 +147,8 @@
             "SymfonyConstraintsWithValidationGroups": {
                 "required": [
                     "property",
-                    "propertyInDefaultGroup"
+                    "propertyInDefaultGroup",
+                    "propertyNotNullOnSpecificGroup"
                 ],
                 "properties": {
                     "property": {
@@ -165,6 +166,9 @@
                         "items": {
                             "type": "string"
                         }
+                    },
+                    "propertyNotNullOnSpecificGroup": {
+                        "type": "string"
                     }
                 },
                 "type": "object"
diff --git a/tests/Functional/ValidationGroupsFunctionalTest.php b/tests/Functional/ValidationGroupsFunctionalTest.php
index ad522968b..3e43eee2b 100644
--- a/tests/Functional/ValidationGroupsFunctionalTest.php
+++ b/tests/Functional/ValidationGroupsFunctionalTest.php
@@ -35,6 +35,7 @@ public function testConstraintGroupsAreRespectedWhenDescribingModels(): void
         $expected = [
             'required' => [
                 'property',
+                'propertyNotNullOnSpecificGroup',
             ],
             'properties' => [
                 'property' => [
@@ -42,6 +43,9 @@ public function testConstraintGroupsAreRespectedWhenDescribingModels(): void
                     // the min/max constraint is in the default group only and shouldn't
                     // be read here with validation groups turned on
                 ],
+                'propertyNotNullOnSpecificGroup' => [
+                    'type' => 'string',
+                ],
             ],
             'type' => 'object',
             'schema' => 'SymfonyConstraintsTestGroup',
@@ -75,6 +79,10 @@ public function testConstraintDefaultGroupsAreRespectedWhenReadingAnnotations():
                         'type' => 'string',
                     ],
                 ],
+                'propertyNotNullOnSpecificGroup' => [
+                    'type' => 'string',
+                    'nullable' => true,
+                ],
             ],
             'type' => 'object',
             'schema' => 'SymfonyConstraintsDefaultGroup',