forked from nelmio/NelmioApiDocBundle
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move Reflection handling into own Reader
This allows to fetch default values via reflection should they so far not have been set via other means. As that happens before the SymfonyConstraint handling, the required fields should be set as expected but should also take into account the default values set via Attributes or Annotations.
- Loading branch information
1 parent
0a81850
commit 7a499cb
Showing
3 changed files
with
162 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the NelmioApiDocBundle package. | ||
* | ||
* (c) Nelmio | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Nelmio\ApiDocBundle\ModelDescriber\Annotations; | ||
|
||
use Doctrine\Common\Annotations\Reader; | ||
use Nelmio\ApiDocBundle\OpenApiPhp\Util; | ||
use Nelmio\ApiDocBundle\Util\SetsContextTrait; | ||
use OpenApi\Annotations as OA; | ||
use OpenApi\Context; | ||
use OpenApi\Generator; | ||
use Symfony\Component\Validator\Constraint; | ||
use Symfony\Component\Validator\Constraints as Assert; | ||
|
||
/** | ||
* Read default values of a property from the function or property signature | ||
* | ||
* This needs to be called before the SymfonyConstraintAnnotationReader, | ||
* otherwise required properties might be considered wrongly. | ||
* | ||
* @internal | ||
*/ | ||
class ReflectionReader | ||
{ | ||
use SetsContextTrait; | ||
|
||
private ?Reader $annotationsReader; | ||
|
||
/** | ||
* @var OA\Schema | ||
*/ | ||
private $schema; | ||
|
||
public function __construct(?Reader $annotationsReader) | ||
{ | ||
$this->annotationsReader = $annotationsReader; | ||
} | ||
|
||
/** | ||
* Update the given property and schema with defined Symfony constraints. | ||
* | ||
* @param \ReflectionProperty|\ReflectionMethod $reflection | ||
* @param string[]|null $validationGroups | ||
*/ | ||
public function updateProperty( | ||
$reflection, | ||
OA\Property $property, | ||
?array $validationGroups = null | ||
): void | ||
{ | ||
// The default has been set by an Annotation or Attribute | ||
// We leave that as it is! | ||
if ($property->default !== Generator::UNDEFINED) { | ||
return; | ||
} | ||
|
||
$serializedName = $reflection->getName(); | ||
foreach (['', 'get', 'is', 'has', 'can', 'add', 'remove', 'set'] as $prefix) { | ||
if (str_starts_with($serializedName, $prefix)) { | ||
$serializedName = substr($serializedName, strlen($prefix)); | ||
} | ||
} | ||
|
||
if ($reflection instanceof \ReflectionMethod) { | ||
$methodDefault = $this->getDefaultFromMethodReflection($reflection); | ||
if (Generator::UNDEFINED !== $methodDefault) { | ||
$property->default = $methodDefault; | ||
return; | ||
} | ||
} | ||
|
||
if ($reflection instanceof \ReflectionProperty) { | ||
$methodDefault = $this->getDefaultFromPropertyReflection($reflection); | ||
if (Generator::UNDEFINED !== $methodDefault) { | ||
$property->default = $methodDefault; | ||
return; | ||
} | ||
} | ||
// Fix for https://github.com/nelmio/NelmioApiDocBundle/issues/2222 | ||
// Promoted properties with a value initialized by the constructor are not considered to have a default value | ||
// and are therefore not returned by ReflectionClass::getDefaultProperties(); see https://bugs.php.net/bug.php?id=81386 | ||
$reflClassConstructor = $reflection->getDeclaringClass()->getConstructor(); | ||
$reflClassConstructorParameters = null !== $reflClassConstructor ? $reflClassConstructor->getParameters() : []; | ||
foreach ($reflClassConstructorParameters as $parameter) { | ||
if ($parameter->name !== $serializedName) { | ||
continue; | ||
} | ||
if (!$parameter->isDefaultValueAvailable()) { | ||
continue; | ||
} | ||
|
||
if (null === $this->schema) { | ||
continue; | ||
} | ||
|
||
if (!Generator::isDefault($property->default)) { | ||
continue; | ||
} | ||
|
||
$property->default = $parameter->getDefaultValue(); | ||
} | ||
} | ||
|
||
public function setSchema($schema): void | ||
{ | ||
$this->schema = $schema; | ||
} | ||
|
||
private function getDefaultFromMethodReflection(\ReflectionMethod $reflection): mixed | ||
{ | ||
if (! str_starts_with('set', $reflection->name)) { | ||
return Generator::UNDEFINED; | ||
} | ||
|
||
if (1 !== $reflection->getNumberOfParameters()) { | ||
return Generator::UNDEFINED; | ||
} | ||
|
||
$param = $reflection->getParameters()[0]; | ||
|
||
if (! $param->isDefaultValueAvailable()) { | ||
return Generator::UNDEFINED; | ||
} | ||
|
||
if (null === $param->getDefaultValue()) { | ||
return Generator::UNDEFINED; | ||
} | ||
|
||
return $param->getDefaultValue(); | ||
} | ||
|
||
public function getDefaultFromPropertyReflection(\ReflectionProperty $reflection): mixed | ||
{ | ||
$propertyName = $reflection->name; | ||
if (! $reflection->getDeclaringClass()->hasProperty($propertyName)) { | ||
return Generator::UNDEFINED; | ||
} | ||
|
||
$reflectionProp = $reflection->getDeclaringClass()->getProperty($propertyName); | ||
if (! $reflectionProp->hasDefaultValue()) { | ||
return Generator::UNDEFINED; | ||
} | ||
|
||
if (null === $reflectionProp->getDefaultValue()) { | ||
return Generator::UNDEFINED; | ||
} | ||
|
||
return $reflectionProp->getDefaultValue(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters