welcome back to dyb-tech

This commit is contained in:
Daniel Guzman
2024-05-18 02:28:01 +02:00
parent 9513cdba09
commit 9f30bc98c7
6149 changed files with 668407 additions and 0 deletions
@@ -0,0 +1,74 @@
<?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\Model\ModelRegistry;
use OpenApi\Annotations as OA;
use OpenApi\Generator;
/**
* @internal
*/
class AnnotationsReader
{
private $phpDocReader;
private $openApiAnnotationsReader;
private $symfonyConstraintAnnotationReader;
public function __construct(
?Reader $annotationsReader,
ModelRegistry $modelRegistry,
array $mediaTypes,
bool $useValidationGroups = false
) {
$this->phpDocReader = new PropertyPhpDocReader();
$this->openApiAnnotationsReader = new OpenApiAnnotationsReader($annotationsReader, $modelRegistry, $mediaTypes);
$this->symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader(
$annotationsReader,
$useValidationGroups
);
}
public function updateDefinition(\ReflectionClass $reflectionClass, OA\Schema $schema): UpdateClassDefinitionResult
{
$this->openApiAnnotationsReader->updateSchema($reflectionClass, $schema);
$this->symfonyConstraintAnnotationReader->setSchema($schema);
return new UpdateClassDefinitionResult(
$this->shouldDescribeModelProperties($schema)
);
}
public function getPropertyName($reflection, string $default): string
{
return $this->openApiAnnotationsReader->getPropertyName($reflection, $default);
}
public function updateProperty($reflection, OA\Property $property, array $serializationGroups = null): void
{
$this->openApiAnnotationsReader->updateProperty($reflection, $property, $serializationGroups);
$this->phpDocReader->updateProperty($reflection, $property);
$this->symfonyConstraintAnnotationReader->updateProperty($reflection, $property, $serializationGroups);
}
/**
* if an objects schema type and ref are undefined OR the object was manually
* defined as an object, then we're good to do the normal describe flow of
* class properties.
*/
private function shouldDescribeModelProperties(OA\Schema $schema): bool
{
return (Generator::UNDEFINED === $schema->type || 'object' === $schema->type)
&& Generator::UNDEFINED === $schema->ref;
}
}
@@ -0,0 +1,118 @@
<?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\Model\ModelRegistry;
use Nelmio\ApiDocBundle\OpenApiPhp\ModelRegister;
use Nelmio\ApiDocBundle\OpenApiPhp\Util;
use Nelmio\ApiDocBundle\Util\SetsContextTrait;
use OpenApi\Analysis;
use OpenApi\Annotations as OA;
use OpenApi\Context;
use OpenApi\Generator;
/**
* @internal
*/
class OpenApiAnnotationsReader
{
use SetsContextTrait;
/**
* @var Reader|null
*/
private $annotationsReader;
private $modelRegister;
public function __construct(?Reader $annotationsReader, ModelRegistry $modelRegistry, array $mediaTypes)
{
$this->annotationsReader = $annotationsReader;
$this->modelRegister = new ModelRegister($modelRegistry, $mediaTypes);
}
public function updateSchema(\ReflectionClass $reflectionClass, OA\Schema $schema): void
{
/** @var OA\Schema|null $oaSchema */
if (!$oaSchema = $this->getAnnotation($schema->_context, $reflectionClass, OA\Schema::class)) {
return;
}
// Read @Model annotations
$this->modelRegister->__invoke(new Analysis([$oaSchema], Util::createContext()));
if (!$oaSchema->validate()) {
return;
}
$schema->mergeProperties($oaSchema);
}
public function getPropertyName($reflection, string $default): string
{
/** @var OA\Property|null $oaProperty */
if (!$oaProperty = $this->getAnnotation(new Context(), $reflection, OA\Property::class)) {
return $default;
}
return Generator::UNDEFINED !== $oaProperty->property ? $oaProperty->property : $default;
}
public function updateProperty($reflection, OA\Property $property, array $serializationGroups = null): void
{
/** @var OA\Property|null $oaProperty */
if (!$oaProperty = $this->getAnnotation($property->_context, $reflection, OA\Property::class)) {
return;
}
// Read @Model annotations
$this->modelRegister->__invoke(new Analysis([$oaProperty], Util::createContext()), $serializationGroups);
if (!$oaProperty->validate()) {
return;
}
$property->mergeProperties($oaProperty);
}
/**
* @param \ReflectionClass|\ReflectionProperty|\ReflectionMethod $reflection
*
* @return mixed
*/
private function getAnnotation(Context $parentContext, $reflection, string $className)
{
$this->setContextFromReflection($parentContext, $reflection);
try {
if (\PHP_VERSION_ID >= 80100) {
if (null !== $attribute = $reflection->getAttributes($className, \ReflectionAttribute::IS_INSTANCEOF)[0] ?? null) {
return $attribute->newInstance();
}
}
if (null !== $this->annotationsReader) {
if ($reflection instanceof \ReflectionClass) {
return $this->annotationsReader->getClassAnnotation($reflection, $className);
} elseif ($reflection instanceof \ReflectionProperty) {
return $this->annotationsReader->getPropertyAnnotation($reflection, $className);
} elseif ($reflection instanceof \ReflectionMethod) {
return $this->annotationsReader->getMethodAnnotation($reflection, $className);
}
}
} finally {
$this->setContext(null);
}
return null;
}
}
@@ -0,0 +1,64 @@
<?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 OpenApi\Annotations as OA;
use OpenApi\Generator;
use phpDocumentor\Reflection\DocBlock\Tags\Var_;
use phpDocumentor\Reflection\DocBlockFactory;
/**
* Extract information about properties of a model from the DocBlock comment.
*
* @internal
*/
class PropertyPhpDocReader
{
private $docBlockFactory;
public function __construct()
{
$this->docBlockFactory = DocBlockFactory::createInstance();
}
/**
* Update the Swagger information with information from the DocBlock comment.
*/
public function updateProperty($reflection, OA\Property $property): void
{
try {
$docBlock = $this->docBlockFactory->create($reflection);
} catch (\Exception $e) {
// ignore
return;
}
if (!$title = $docBlock->getSummary()) {
/** @var Var_ $var */
foreach ($docBlock->getTagsByName('var') as $var) {
if (!method_exists($var, 'getDescription') || !$description = $var->getDescription()) {
continue;
}
$title = $description->render();
if ($title) {
break;
}
}
}
if (Generator::UNDEFINED === $property->title && $title) {
$property->title = $title;
}
if (Generator::UNDEFINED === $property->description && $docBlock->getDescription() && $docBlock->getDescription()->render()) {
$property->description = $docBlock->getDescription()->render();
}
}
}
@@ -0,0 +1,233 @@
<?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;
/**
* @internal
*/
class SymfonyConstraintAnnotationReader
{
use SetsContextTrait;
/**
* @var Reader|null
*/
private $annotationsReader;
/**
* @var OA\Schema
*/
private $schema;
/**
* @var bool
*/
private $useValidationGroups;
public function __construct(?Reader $annotationsReader, bool $useValidationGroups = false)
{
$this->annotationsReader = $annotationsReader;
$this->useValidationGroups = $useValidationGroups;
}
/**
* Update the given property and schema with defined Symfony constraints.
*
* @param \ReflectionProperty|\ReflectionMethod $reflection
*/
public function updateProperty($reflection, OA\Property $property, ?array $validationGroups = null): void
{
foreach ($this->getAnnotations($property->_context, $reflection, $validationGroups) as $outerAnnotation) {
$innerAnnotations = $outerAnnotation instanceof Assert\Compound || $outerAnnotation instanceof Assert\Sequentially
? $outerAnnotation->constraints
: [$outerAnnotation];
$this->processPropertyAnnotations($reflection, $property, $innerAnnotations);
}
}
private function processPropertyAnnotations($reflection, OA\Property $property, $annotations)
{
foreach ($annotations as $annotation) {
if ($annotation instanceof Assert\NotBlank || $annotation instanceof Assert\NotNull) {
// To support symfony/validator < 4.3
if ($annotation instanceof Assert\NotBlank && \property_exists($annotation, 'allowNull') && $annotation->allowNull) {
// The field is optional
return;
}
// The field is required
if (null === $this->schema) {
return;
}
$propertyName = Util::getSchemaPropertyName($this->schema, $property);
if (null === $propertyName) {
return;
}
$existingRequiredFields = Generator::UNDEFINED !== $this->schema->required ? $this->schema->required : [];
$existingRequiredFields[] = $propertyName;
$this->schema->required = array_values(array_unique($existingRequiredFields));
} elseif ($annotation instanceof Assert\Length) {
if (isset($annotation->min)) {
$property->minLength = (int) $annotation->min;
}
if (isset($annotation->max)) {
$property->maxLength = (int) $annotation->max;
}
} elseif ($annotation instanceof Assert\Regex) {
$this->appendPattern($property, $annotation->getHtmlPattern());
} elseif ($annotation instanceof Assert\Count) {
if (isset($annotation->min)) {
$property->minItems = (int) $annotation->min;
}
if (isset($annotation->max)) {
$property->maxItems = (int) $annotation->max;
}
} elseif ($annotation instanceof Assert\Choice) {
$this->applyEnumFromChoiceConstraint($property, $annotation, $reflection);
} elseif ($annotation instanceof Assert\Range) {
if (\is_int($annotation->min)) {
$property->minimum = $annotation->min;
}
if (\is_int($annotation->max)) {
$property->maximum = $annotation->max;
}
} elseif ($annotation instanceof Assert\LessThan) {
if (\is_int($annotation->value)) {
$property->exclusiveMaximum = true;
$property->maximum = $annotation->value;
}
} elseif ($annotation instanceof Assert\LessThanOrEqual) {
if (\is_int($annotation->value)) {
$property->maximum = $annotation->value;
}
} elseif ($annotation instanceof Assert\GreaterThan) {
if (\is_int($annotation->value)) {
$property->exclusiveMinimum = true;
$property->minimum = $annotation->value;
}
} elseif ($annotation instanceof Assert\GreaterThanOrEqual) {
if (\is_int($annotation->value)) {
$property->minimum = $annotation->value;
}
}
}
}
public function setSchema($schema): void
{
$this->schema = $schema;
}
/**
* Append the pattern from the constraint to the existing pattern.
*/
private function appendPattern(OA\Schema $property, $newPattern): void
{
if (null === $newPattern) {
return;
}
if (Generator::UNDEFINED !== $property->pattern) {
$property->pattern = sprintf('%s, %s', $property->pattern, $newPattern);
} else {
$property->pattern = $newPattern;
}
}
/**
* @param \ReflectionProperty|\ReflectionMethod $reflection
*/
private function applyEnumFromChoiceConstraint(OA\Schema $property, Assert\Choice $choice, $reflection): void
{
if ($choice->callback) {
$enumValues = call_user_func(is_array($choice->callback) ? $choice->callback : [$reflection->class, $choice->callback]);
} else {
$enumValues = $choice->choices;
}
$setEnumOnThis = $property;
if ($choice->multiple) {
$setEnumOnThis = Util::getChild($property, OA\Items::class);
}
$setEnumOnThis->enum = array_values($enumValues);
}
/**
* @param \ReflectionProperty|\ReflectionMethod $reflection
*/
private function getAnnotations(Context $parentContext, $reflection, ?array $validationGroups): iterable
{
// To correctly load OA annotations
$this->setContextFromReflection($parentContext, $reflection);
foreach ($this->locateAnnotations($reflection) as $annotation) {
if (!$annotation instanceof Constraint) {
continue;
}
if (!$this->useValidationGroups || $this->isConstraintInGroup($annotation, $validationGroups)) {
yield $annotation;
}
}
$this->setContext(null);
}
/**
* @param \ReflectionProperty|\ReflectionMethod $reflection
*/
private function locateAnnotations($reflection): \Traversable
{
if (\PHP_VERSION_ID >= 80000 && class_exists(Constraint::class)) {
foreach ($reflection->getAttributes(Constraint::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
yield $attribute->newInstance();
}
}
if (null !== $this->annotationsReader) {
if ($reflection instanceof \ReflectionProperty) {
yield from $this->annotationsReader->getPropertyAnnotations($reflection);
} elseif ($reflection instanceof \ReflectionMethod) {
yield from $this->annotationsReader->getMethodAnnotations($reflection);
}
}
}
/**
* Check to see if the given constraint is in the provided serialization groups.
*
* If no groups are provided the validator would run in the Constraint::DEFAULT_GROUP,
* and constraints without any `groups` passed to them would be in that same
* default group. So even with a null $validationGroups passed here there still
* has to be a check on the default group.
*/
private function isConstraintInGroup(Constraint $annotation, ?array $validationGroups): bool
{
return count(array_intersect(
$validationGroups ?: [Constraint::DEFAULT_GROUP],
(array) $annotation->groups
)) > 0;
}
}
@@ -0,0 +1,41 @@
<?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;
/**
* result object returned from `AnnotationReader::updateDefinition` as a way
* to pass back information about manually defined schema elements.
*
* @internal
*/
final class UpdateClassDefinitionResult
{
/**
* Whether or not the model describer shoudl continue reading class properties
* after updating the open api schema from an `OA\Schema` definition.
*
* Users may maually define a `type` or `ref` on a schema, and if that's the case
* model describers should _probably_ not describe any additional properties or try
* to merge in properties.
*/
private $shouldDescribeModelProperties;
public function __construct(bool $shouldDescribeModelProperties)
{
$this->shouldDescribeModelProperties = $shouldDescribeModelProperties;
}
public function shouldDescribeModelProperties(): bool
{
return $this->shouldDescribeModelProperties;
}
}