Skip to content

Commit

Permalink
Merge branch 'master' into symfony-map-request-data
Browse files Browse the repository at this point in the history
# Conflicts:
#	Tests/Functional/Controller/ApiController81.php
#	phpunit-baseline.json
  • Loading branch information
DjordyKoert committed Jan 15, 2024
2 parents a2f44ea + e1405ef commit da56d50
Show file tree
Hide file tree
Showing 48 changed files with 1,018 additions and 296 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ jobs:
- php-version: 7.3
symfony-require: "5.4.*"
doctrine-annotations: true
- php-version: 7.4
symfony-require: "5.4.*"
doctrine-annotations: true
- php-version: 8.0
symfony-require: "5.4.*"
doctrine-annotations: true
Expand Down
9 changes: 0 additions & 9 deletions DependencyInjection/NelmioApiDocExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -279,15 +279,6 @@ public function load(array $configs, ContainerBuilder $container): void
if (null !== $controllerNameConverter) {
$container->getDefinition('nelmio_api_doc.controller_reflector')->setArgument(1, $controllerNameConverter);
}

// New parameter self-exclude has to be set to false for service nelmio_api_doc.object_model.property_describers.array
// BC compatibility with Symfony <6.3
$container->getDefinition('nelmio_api_doc.object_model.property_describers.array')
->setArguments([
!method_exists(TaggedIteratorArgument::class, 'excludeSelf')
? new TaggedIteratorArgument('nelmio_api_doc.object_model.property_describer')
: new TaggedIteratorArgument('nelmio_api_doc.object_model.property_describer', null, null, false, null, [], false),
]);
}

private function findNameAliases(array $names, string $area): array
Expand Down
3 changes: 3 additions & 0 deletions Exception/UndocumentedArrayItemsException.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

namespace Nelmio\ApiDocBundle\Exception;

/**
* @deprecated since 4.17, this exception is not used anymore
*/
class UndocumentedArrayItemsException extends \LogicException
{
private $class;
Expand Down
2 changes: 1 addition & 1 deletion Model/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function getSerializationContext(): array

public function getHash(): string
{
return md5(serialize([$this->type, $this->getGroups()]));
return md5(serialize([$this->type, $this->getSerializationContext()]));
}

/**
Expand Down
1 change: 1 addition & 0 deletions Model/ModelRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ private function modelToArray(Model $model): array
'type' => $getType($model->getType()),
'options' => $model->getOptions(),
'groups' => $model->getGroups(),
'serialization_context' => $model->getSerializationContext(),
];
}

Expand Down
3 changes: 2 additions & 1 deletion ModelDescriber/FormModelDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ public function __construct(

if (null === $mediaTypes) {
$mediaTypes = ['json'];
@trigger_error(sprintf('Not passing media types to the constructor of %s is deprecated since version 4.1 and won\'t be allowed in version 5.', self::class), E_USER_DEPRECATED);

trigger_deprecation('nelmio/api-doc-bundle', '4.1', 'Not passing media types to the constructor of %s is deprecated and won\'t be allowed in version 5.', self::class);
}
$this->mediaTypes = $mediaTypes;
$this->useValidationGroups = $useValidationGroups;
Expand Down
16 changes: 15 additions & 1 deletion ModelDescriber/JMSModelDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@ public function describe(Model $model, OA\Schema $schema)

continue;
}

if (Generator::UNDEFINED === $property->default && $item->hasDefault) {
$property->default = $item->defaultValue;
}

if (null === $item->type) {
$key = Util::searchIndexedCollectionItem($schema->properties, 'property', $name);
unset($schema->properties[$key]);
Expand Down Expand Up @@ -248,7 +253,7 @@ public function describeItem(array $type, OA\Schema $property, Context $context)
{
$nestedTypeInfo = $this->getNestedTypeInArray($type);
if (null !== $nestedTypeInfo) {
list($nestedType, $isHash) = $nestedTypeInfo;
[$nestedType, $isHash] = $nestedTypeInfo;
if ($isHash) {
$property->type = 'object';
$property->additionalProperties = Util::createChild($property, OA\Property::class);
Expand Down Expand Up @@ -285,6 +290,15 @@ public function describeItem(array $type, OA\Schema $property, Context $context)
$property->type = 'string';
$property->format = 'date-time';
} else {
// See https://github.com/schmittjoh/serializer/blob/5a5a03a/src/Metadata/Driver/EnumPropertiesDriver.php#L51
if ('enum' === $type['name']
&& isset($type['params'][0])
&& function_exists('enum_exists')
&& enum_exists($type['params'][0])
) {
$type = ['name' => $type['params'][0]];
}

$groups = $this->computeGroups($context, $type);

$model = new Model(new Type(Type::BUILTIN_TYPE_OBJECT, false, $type['name']), $groups);
Expand Down
42 changes: 27 additions & 15 deletions ModelDescriber/ObjectModelDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
use Doctrine\Common\Annotations\Reader;
use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface;
use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait;
use Nelmio\ApiDocBundle\Exception\UndocumentedArrayItemsException;
use Nelmio\ApiDocBundle\Model\Model;
use Nelmio\ApiDocBundle\ModelDescriber\Annotations\AnnotationsReader;
use Nelmio\ApiDocBundle\OpenApiPhp\Util;
Expand All @@ -37,27 +36,38 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar
private $classMetadataFactory;
/** @var Reader|null */
private $doctrineReader;
/** @var PropertyDescriberInterface[] */
private $propertyDescribers;
/** @var PropertyDescriberInterface|PropertyDescriberInterface[] */
private $propertyDescriber;
/** @var string[] */
private $mediaTypes;
/** @var NameConverterInterface|null */
private $nameConverter;
/** @var bool */
private $useValidationGroups;

/**
* @param PropertyDescriberInterface|PropertyDescriberInterface[] $propertyDescribers
*/
public function __construct(
PropertyInfoExtractorInterface $propertyInfo,
?Reader $reader,
iterable $propertyDescribers,
$propertyDescribers,
array $mediaTypes,
NameConverterInterface $nameConverter = null,
bool $useValidationGroups = false,
ClassMetadataFactoryInterface $classMetadataFactory = null
) {
if (is_array($propertyDescribers)) {
trigger_deprecation('nelmio/api-doc-bundle', '4.17', 'Passing an array of PropertyDescriberInterface to %s() is deprecated. Pass a single PropertyDescriberInterface instead.', __METHOD__);
} else {
if (!$propertyDescribers instanceof PropertyDescriberInterface) {
throw new \InvalidArgumentException(sprintf('Argument 3 passed to %s() must be an array of %s or a single %s.', __METHOD__, PropertyDescriberInterface::class, PropertyDescriberInterface::class));
}
}

$this->propertyInfo = $propertyInfo;
$this->doctrineReader = $reader;
$this->propertyDescribers = $propertyDescribers;
$this->propertyDescriber = $propertyDescribers;
$this->mediaTypes = $mediaTypes;
$this->nameConverter = $nameConverter;
$this->useValidationGroups = $useValidationGroups;
Expand Down Expand Up @@ -116,6 +126,10 @@ public function describe(Model $model, OA\Schema $schema)
// The SerializerExtractor does expose private/protected properties for some reason, so we eliminate them here
$propertyInfoProperties = array_intersect($propertyInfoProperties, $this->propertyInfo->getProperties($class, []) ?? []);

$defaultValues = array_filter($reflClass->getDefaultProperties(), static function ($value) {
return null !== $value;
});

foreach ($propertyInfoProperties as $propertyName) {
$serializedName = null !== $this->nameConverter ? $this->nameConverter->normalize($propertyName, $class, null, $model->getSerializationContext()) : $propertyName;

Expand All @@ -142,6 +156,10 @@ public function describe(Model $model, OA\Schema $schema)
continue;
}

if (Generator::UNDEFINED === $property->default && array_key_exists($propertyName, $defaultValues)) {
$property->default = $defaultValues[$propertyName];
}

$types = $this->propertyInfo->getTypes($class, $propertyName);
if (null === $types || 0 === count($types)) {
throw new \LogicException(sprintf('The PropertyInfo component was not able to guess the type of %s::$%s. You may need to add a `@var` annotation or use `@OA\Property(type="")` to make its type explicit.', $class, $propertyName));
Expand Down Expand Up @@ -184,20 +202,14 @@ private function camelize(string $string): string
*/
private function describeProperty(array $types, Model $model, OA\Schema $property, string $propertyName, OA\Schema $schema)
{
foreach ($this->propertyDescribers as $propertyDescriber) {
$propertyDescribers = is_array($this->propertyDescriber) ? $this->propertyDescriber : [$this->propertyDescriber];

foreach ($propertyDescribers as $propertyDescriber) {
if ($propertyDescriber instanceof ModelRegistryAwareInterface) {
$propertyDescriber->setModelRegistry($this->modelRegistry);
}
if ($propertyDescriber->supports($types)) {
try {
$propertyDescriber->describe($types, $property, $model->getGroups(), $schema);
} catch (UndocumentedArrayItemsException $e) {
if (null !== $e->getClass()) {
throw $e; // This exception is already complete
}

throw new UndocumentedArrayItemsException($model->getType()->getClassName(), sprintf('%s%s', $propertyName, $e->getPath()));
}
$propertyDescriber->describe($types, $property, $model->getGroups(), $schema, $model->getSerializationContext());

return;
}
Expand Down
2 changes: 1 addition & 1 deletion OpenApiPhp/Util.php
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ private static function mergeFromArray(OA\AbstractAnnotation $annotation, array
if ('$ref' === $propertyName) {
$propertyName = 'ref';
}
if (!\in_array($propertyName, $done, true)) {
if (array_key_exists($propertyName, $defaults) && !\in_array($propertyName, $done, true)) {
self::mergeProperty($annotation, $propertyName, $value, $defaults[$propertyName], $overwrite);
}
}
Expand Down
41 changes: 41 additions & 0 deletions Processor/NullablePropertyProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace Nelmio\ApiDocBundle\Processor;

use OpenApi\Analysis;
use OpenApi\Annotations as OA;
use OpenApi\Generator;
use OpenApi\Processors\ProcessorInterface;

/**
* Processor to clean up the generated OpenAPI documentation for nullable properties.
*/
final class NullablePropertyProcessor implements ProcessorInterface
{
public function __invoke(Analysis $analysis): void
{
if (Generator::isDefault($analysis->openapi->components) || Generator::isDefault($analysis->openapi->components->schemas)) {
return;
}

/** @var OA\Schema[] $schemas */
$schemas = $analysis->openapi->components->schemas;

foreach ($schemas as $schema) {
if (Generator::UNDEFINED === $schema->properties) {
continue;
}

foreach ($schema->properties as $property) {
if (Generator::UNDEFINED !== $property->nullable) {
if (!$property->nullable) {
// if already false mark it as undefined (so it does not show up as `nullable: false`)
$property->nullable = Generator::UNDEFINED;
}
}
}
}
}
}
41 changes: 7 additions & 34 deletions PropertyDescriber/ArrayPropertyDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,55 +13,28 @@

use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface;
use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait;
use Nelmio\ApiDocBundle\Exception\UndocumentedArrayItemsException;
use Nelmio\ApiDocBundle\OpenApiPhp\Util;
use OpenApi\Annotations as OA;

class ArrayPropertyDescriber implements PropertyDescriberInterface, ModelRegistryAwareInterface
class ArrayPropertyDescriber implements PropertyDescriberInterface, ModelRegistryAwareInterface, PropertyDescriberAwareInterface
{
use ModelRegistryAwareTrait;
use NullablePropertyTrait;
use PropertyDescriberAwareTrait;

/** @var PropertyDescriberInterface[] */
private $propertyDescribers;

public function __construct(iterable $propertyDescribers = [])
public function describe(array $types, OA\Schema $property, array $groups = null, ?OA\Schema $schema = null, array $context = [])
{
$this->propertyDescribers = $propertyDescribers;
}
$property->type = 'array';
$property = Util::getChild($property, OA\Items::class);

public function describe(array $types, OA\Schema $property, array $groups = null, ?OA\Schema $schema = null)
{
// BC layer for symfony < 5.3
$type = method_exists($types[0], 'getCollectionValueTypes') ?
($types[0]->getCollectionValueTypes()[0] ?? null) :
$types[0]->getCollectionValueType();
if (null === $type) {
throw new UndocumentedArrayItemsException();
return;
}

$property->type = 'array';
$this->setNullableProperty($types[0], $property, $schema);
$property = Util::getChild($property, OA\Items::class);

foreach ($this->propertyDescribers as $propertyDescriber) {
if ($propertyDescriber instanceof ModelRegistryAwareInterface) {
$propertyDescriber->setModelRegistry($this->modelRegistry);
}
if ($propertyDescriber->supports([$type])) {
try {
$propertyDescriber->describe([$type], $property, $groups, $schema);
} catch (UndocumentedArrayItemsException $e) {
if (null !== $e->getClass()) {
throw $e; // This exception is already complete
}

throw new UndocumentedArrayItemsException(null, sprintf('%s[]', $e->getPath()));
}

break;
}
}
$this->propertyDescriber->describe([$type], $property, $groups, $schema, $context);
}

public function supports(array $types): bool
Expand Down
5 changes: 1 addition & 4 deletions PropertyDescriber/BooleanPropertyDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,9 @@

class BooleanPropertyDescriber implements PropertyDescriberInterface
{
use NullablePropertyTrait;

public function describe(array $types, OA\Schema $property, array $groups = null, ?OA\Schema $schema = null)
public function describe(array $types, OA\Schema $property, array $groups = null, ?OA\Schema $schema = null, array $context = [])
{
$property->type = 'boolean';
$this->setNullableProperty($types[0], $property, $schema);
}

public function supports(array $types): bool
Expand Down
25 changes: 5 additions & 20 deletions PropertyDescriber/CompoundPropertyDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,19 @@
use OpenApi\Annotations as OA;
use OpenApi\Generator;

class CompoundPropertyDescriber implements PropertyDescriberInterface, ModelRegistryAwareInterface
class CompoundPropertyDescriber implements PropertyDescriberInterface, ModelRegistryAwareInterface, PropertyDescriberAwareInterface
{
use ModelRegistryAwareTrait;
use PropertyDescriberAwareTrait;

/** @var PropertyDescriberInterface[] */
private $propertyDescribers;

public function __construct(iterable $propertyDescribers)
{
$this->propertyDescribers = $propertyDescribers;
}

public function describe(array $types, OA\Schema $property, array $groups = null, ?OA\Schema $schema = null)
public function describe(array $types, OA\Schema $property, array $groups = null, ?OA\Schema $schema = null, array $context = [])
{
$property->oneOf = Generator::UNDEFINED !== $property->oneOf ? $property->oneOf : [];

foreach ($types as $type) {
$property->oneOf[] = $schema = Util::createChild($property, OA\Schema::class, []);
foreach ($this->propertyDescribers as $propertyDescriber) {
if ($propertyDescriber instanceof ModelRegistryAwareInterface) {
$propertyDescriber->setModelRegistry($this->modelRegistry);
}
if ($propertyDescriber->supports([$type])) {
$propertyDescriber->describe([$type], $schema, $groups, $schema);

break;
}
}

$this->propertyDescriber->describe([$type], $schema, $groups, $schema, $context);
}
}

Expand Down
5 changes: 1 addition & 4 deletions PropertyDescriber/DateTimePropertyDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,10 @@

class DateTimePropertyDescriber implements PropertyDescriberInterface
{
use NullablePropertyTrait;

public function describe(array $types, OA\Schema $property, array $groups = null, ?OA\Schema $schema = null)
public function describe(array $types, OA\Schema $property, array $groups = null, ?OA\Schema $schema = null, array $context = [])
{
$property->type = 'string';
$property->format = 'date-time';
$this->setNullableProperty($types[0], $property, $schema);
}

public function supports(array $types): bool
Expand Down
5 changes: 1 addition & 4 deletions PropertyDescriber/FloatPropertyDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,10 @@

class FloatPropertyDescriber implements PropertyDescriberInterface
{
use NullablePropertyTrait;

public function describe(array $types, OA\Schema $property, array $groups = null, ?OA\Schema $schema = null)
public function describe(array $types, OA\Schema $property, array $groups = null, ?OA\Schema $schema = null, array $context = [])
{
$property->type = 'number';
$property->format = 'float';
$this->setNullableProperty($types[0], $property, $schema);
}

public function supports(array $types): bool
Expand Down
Loading

0 comments on commit da56d50

Please sign in to comment.