From 3cb0e0c2430d9a4364e4c118d56b77439742568e Mon Sep 17 00:00:00 2001 From: Andreas Heigl Date: Mon, 11 Nov 2024 09:56:48 +0100 Subject: [PATCH] Set nullable true when default value is null When reflection shows that the default parameter of a a property is null, then the `nullable` parameter is set to `true` for that property. Currently that needs to be set via the attribute but that is a bit redundant as the information is already available from reflection This change only does that for properties and for setter methods. Right now getter methods that allow a null value to be returned are not taken into account for setting the `nullable` property. --- .../Annotations/ReflectionReader.php | 73 ++++++++++++++++--- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/src/ModelDescriber/Annotations/ReflectionReader.php b/src/ModelDescriber/Annotations/ReflectionReader.php index 4ea3f26f9..51a25a0ca 100644 --- a/src/ModelDescriber/Annotations/ReflectionReader.php +++ b/src/ModelDescriber/Annotations/ReflectionReader.php @@ -14,6 +14,7 @@ use Nelmio\ApiDocBundle\Util\SetsContextTrait; use OpenApi\Annotations as OA; use OpenApi\Generator; +use ReflectionProperty; /** * Read default values of a property from the function or property signature. @@ -54,6 +55,11 @@ public function updateProperty( if ($reflection instanceof \ReflectionMethod) { $methodDefault = $this->getDefaultFromMethodReflection($reflection); if (Generator::UNDEFINED !== $methodDefault) { + if (null === $methodDefault) { + $property->nullable = true; + + return; + } $property->default = $methodDefault; return; @@ -61,9 +67,14 @@ public function updateProperty( } if ($reflection instanceof \ReflectionProperty) { - $methodDefault = $this->getDefaultFromPropertyReflection($reflection); - if (Generator::UNDEFINED !== $methodDefault) { - $property->default = $methodDefault; + $propertyDefault = $this->getDefaultFromPropertyReflection($reflection); + if (Generator::UNDEFINED !== $propertyDefault) { + if (Generator::UNDEFINED === $property->nullable && null === $propertyDefault) { + $property->nullable = true; + + return; + } + $property->default = $propertyDefault; return; } @@ -77,6 +88,7 @@ public function updateProperty( if ($parameter->name !== $serializedName) { continue; } + if (!$parameter->isDefaultValueAvailable()) { continue; } @@ -89,7 +101,12 @@ public function updateProperty( continue; } - $property->default = $parameter->getDefaultValue(); + $default = $parameter->getDefaultValue(); + if (Generator::UNDEFINED === $property->nullable && null === $default) { + $property->nullable = true; + } + + $property->default = $default; } } @@ -117,10 +134,6 @@ private function getDefaultFromMethodReflection(\ReflectionMethod $reflection) return Generator::UNDEFINED; } - if (null === $param->getDefaultValue()) { - return Generator::UNDEFINED; - } - return $param->getDefaultValue(); } @@ -129,17 +142,55 @@ private function getDefaultFromMethodReflection(\ReflectionMethod $reflection) */ public function getDefaultFromPropertyReflection(\ReflectionProperty $reflection) { + $propertyName = $reflection->name; if (!$reflection->getDeclaringClass()->hasProperty($propertyName)) { return Generator::UNDEFINED; } - $defaultValue = $reflection->getDeclaringClass()->getDefaultProperties()[$propertyName] ?? null; + if (PHP_VERSION_ID < 80000) { + return $reflection->getDeclaringClass()->getDefaultProperties()[$propertyName] ?? Generator::UNDEFINED; + } + + if (! $reflection->hasDefaultValue()) { + return Generator::UNDEFINED; + } - if (null === $defaultValue) { + if ($this->hasImplicitNullDefaultValue($reflection)) { return Generator::UNDEFINED; } - return $defaultValue; + return $reflection->getDefaultValue(); + } + + /** + * Check whether the default value is an implicit null + * + * An implicit null would be any null value that is not explicitly set and + * contradicts a set DocBlock @ var annotation + * + * So a property without an explicit value but an `@var int` docblock + * would be considered as not having an implicit null default value as + * that contradicts the annotation. + * + * A property without a default value and no docblock would be considered + * to have an explicit NULL default value to be set though. + */ + private function hasImplicitNullDefaultValue(ReflectionProperty $reflection): bool + { + if (null !== $reflection->getDefaultValue()) { + return false; + } + + if (false === $reflection->getDocComment()) { + return true; + } + + $docComment = $reflection->getDocComment(); + if (! preg_match('/@var\s+([\s]+)/', $docComment, $matches)){ + return true; + } + + return false !== strpos(strtolower($matches[1]), 'null'); } }