diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 44573a0ee..dae21d11e 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,5 +1,20 @@ parameters: ignoreErrors: + - + message: "#^Property Nelmio\\\\ApiDocBundle\\\\Annotation\\\\Model\\:\\:\\$_required type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Annotation/Model.php + + - + message: "#^Property Nelmio\\\\ApiDocBundle\\\\Annotation\\\\Security\\:\\:\\$_required type has no value type specified in iterable type array\\.$#" + count: 1 + path: src/Annotation/Security.php + + - + message: "#^Only booleans are allowed in an if condition, mixed given\\.$#" + count: 1 + path: src/Describer/ExternalDocDescriber.php + - message: "#^Method Nelmio\\\\ApiDocBundle\\\\PropertyDescriber\\\\PropertyDescriberInterface\\:\\:describe\\(\\) invoked with 5 parameters, 2\\-3 required\\.$#" count: 1 @@ -50,6 +65,11 @@ parameters: count: 1 path: src/Render/Html/HtmlOpenApiRenderer.php + - + message: "#^Parameter \\$twig of method Nelmio\\\\ApiDocBundle\\\\Render\\\\Html\\\\HtmlOpenApiRenderer\\:\\:__construct\\(\\) has invalid type Twig_Environment\\.$#" + count: 1 + path: src/Render/Html/HtmlOpenApiRenderer.php + - message: "#^Property Nelmio\\\\ApiDocBundle\\\\Render\\\\Html\\\\HtmlOpenApiRenderer\\:\\:\\$twig has unknown class Twig_Environment as its type\\.$#" count: 1 diff --git a/phpstan.dist.neon b/phpstan.dist.neon index 4d4d69b79..fd35821f1 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -7,7 +7,7 @@ includes: - vendor/phpstan/phpstan-strict-rules/rules.neon parameters: - level: 5 + level: 6 paths: - src - tests @@ -15,6 +15,7 @@ parameters: - tests/Functional/Entity/* - tests/Functional/EntityExcluded/* - tests/Functional/Controller/* + - tests/ModelDescriber/Annotations/Fixture/* dynamicConstantNames: - Symfony\Component\HttpKernel\Kernel::VERSION - Symfony\Component\HttpKernel\Kernel::VERSION_ID @@ -28,3 +29,6 @@ parameters: universalObjectCratesClasses: - OpenApi\Context treatPhpDocTypesAsCertain: false + checkGenericClassInNonGenericObjectType: false + ignoreErrors: + - '#^Property class@anonymous/tests/.* has no type specified.$#' diff --git a/src/Annotation/Areas.php b/src/Annotation/Areas.php index 2ac703b4d..50670a044 100644 --- a/src/Annotation/Areas.php +++ b/src/Annotation/Areas.php @@ -18,7 +18,7 @@ final class Areas { /** @var string[] */ - private $areas; + private array $areas; /** * @param string[]|array{value: string[]} $properties diff --git a/src/Annotation/Model.php b/src/Annotation/Model.php index dc0e59502..7f472d238 100644 --- a/src/Annotation/Model.php +++ b/src/Annotation/Model.php @@ -33,25 +33,22 @@ final class Model extends Attachable Parameter::class, ]; - /** - * @var string - */ - public $type; + public string $type; /** - * @var string[] + * @var string[]|null */ - public $groups; + public ?array $groups; /** - * @var mixed[] + * @var mixed[]|null */ - public $options; + public ?array $options; /** * @var array */ - public $serializationContext; + public array $serializationContext; /** * @param mixed[] $properties diff --git a/src/Annotation/Security.php b/src/Annotation/Security.php index 9182f3ecd..ffbf2b6a7 100644 --- a/src/Annotation/Security.php +++ b/src/Annotation/Security.php @@ -26,16 +26,17 @@ class Security extends AbstractAnnotation public static $_required = ['name']; - /** - * @var string - */ - public $name; + public ?string $name; /** * @var string[] */ - public $scopes = []; + public array $scopes = []; + /** + * @param array $properties + * @param string[] $scopes + */ public function __construct( array $properties = [], ?string $name = null, diff --git a/src/ApiDocGenerator.php b/src/ApiDocGenerator.php index 4a0da2075..61d805cf3 100644 --- a/src/ApiDocGenerator.php +++ b/src/ApiDocGenerator.php @@ -69,17 +69,23 @@ public function __construct($describers, $modelDescribers, ?CacheItemPoolInterfa $this->generator = $generator ?? new Generator($this->logger); } - public function setAlternativeNames(array $alternativeNames) + /** + * @param string[] $alternativeNames + */ + public function setAlternativeNames(array $alternativeNames): void { $this->alternativeNames = $alternativeNames; } - public function setMediaTypes(array $mediaTypes) + /** + * @param string[] $mediaTypes + */ + public function setMediaTypes(array $mediaTypes): void { $this->mediaTypes = $mediaTypes; } - public function setOpenApiVersion(?string $openApiVersion) + public function setOpenApiVersion(?string $openApiVersion): void { $this->openApiVersion = $openApiVersion; } diff --git a/src/Command/DumpCommand.php b/src/Command/DumpCommand.php index a74821576..900626025 100644 --- a/src/Command/DumpCommand.php +++ b/src/Command/DumpCommand.php @@ -20,10 +20,7 @@ class DumpCommand extends Command { - /** - * @var RenderOpenApi - */ - private $renderOpenApi; + private RenderOpenApi $renderOpenApi; /** * @var mixed[] diff --git a/src/Controller/DocumentationController.php b/src/Controller/DocumentationController.php index a09c6a89b..da47e4ea6 100644 --- a/src/Controller/DocumentationController.php +++ b/src/Controller/DocumentationController.php @@ -19,17 +19,14 @@ final class DocumentationController { - /** - * @var RenderOpenApi - */ - private $renderOpenApi; + private RenderOpenApi $renderOpenApi; public function __construct(RenderOpenApi $renderOpenApi) { $this->renderOpenApi = $renderOpenApi; } - public function __invoke(Request $request, $area = 'default') + public function __invoke(Request $request, string $area = 'default'): JsonResponse { try { return JsonResponse::fromJsonString( diff --git a/src/Controller/SwaggerUiController.php b/src/Controller/SwaggerUiController.php index 0678b808e..dac206ac4 100644 --- a/src/Controller/SwaggerUiController.php +++ b/src/Controller/SwaggerUiController.php @@ -20,15 +20,9 @@ final class SwaggerUiController { - /** - * @var RenderOpenApi - */ - private $renderOpenApi; + private RenderOpenApi $renderOpenApi; - /** - * @var string - */ - private $uiRenderer; + private string $uiRenderer; public function __construct(RenderOpenApi $renderOpenApi, string $uiRenderer) { @@ -36,7 +30,7 @@ public function __construct(RenderOpenApi $renderOpenApi, string $uiRenderer) $this->uiRenderer = $uiRenderer; } - public function __invoke(Request $request, $area = 'default') + public function __invoke(Request $request, string $area = 'default'): Response { try { $response = new Response( diff --git a/src/Controller/YamlDocumentationController.php b/src/Controller/YamlDocumentationController.php index 9a320e13a..400eab4ed 100644 --- a/src/Controller/YamlDocumentationController.php +++ b/src/Controller/YamlDocumentationController.php @@ -18,17 +18,14 @@ final class YamlDocumentationController { - /** - * @var RenderOpenApi - */ - private $renderOpenApi; + private RenderOpenApi $renderOpenApi; public function __construct(RenderOpenApi $renderOpenApi) { $this->renderOpenApi = $renderOpenApi; } - public function __invoke(Request $request, $area = 'default') + public function __invoke(Request $request, string $area = 'default'): Response { try { $response = new Response( diff --git a/src/DependencyInjection/NelmioApiDocExtension.php b/src/DependencyInjection/NelmioApiDocExtension.php index 99555b26a..0a70868a3 100644 --- a/src/DependencyInjection/NelmioApiDocExtension.php +++ b/src/DependencyInjection/NelmioApiDocExtension.php @@ -281,6 +281,11 @@ public function load(array $configs, ContainerBuilder $container): void } } + /** + * @param array $names + * + * @return array + */ private function findNameAliases(array $names, string $area): array { $nameAliases = array_filter($names, function (array $aliasInfo) use ($area) { diff --git a/src/Describer/DefaultDescriber.php b/src/Describer/DefaultDescriber.php index 57a31df04..b69120cf1 100644 --- a/src/Describer/DefaultDescriber.php +++ b/src/Describer/DefaultDescriber.php @@ -22,7 +22,7 @@ */ final class DefaultDescriber implements DescriberInterface { - public function describe(OA\OpenApi $api) + public function describe(OA\OpenApi $api): void { // Info /** @var OA\Info $info */ diff --git a/src/Describer/DescriberInterface.php b/src/Describer/DescriberInterface.php index ab92a5c97..bcd7c73ea 100644 --- a/src/Describer/DescriberInterface.php +++ b/src/Describer/DescriberInterface.php @@ -15,5 +15,8 @@ interface DescriberInterface { + /** + * @return void + */ public function describe(OpenApi $api); } diff --git a/src/Describer/ExternalDocDescriber.php b/src/Describer/ExternalDocDescriber.php index 2bfa4f94b..cf827187a 100644 --- a/src/Describer/ExternalDocDescriber.php +++ b/src/Describer/ExternalDocDescriber.php @@ -16,12 +16,15 @@ class ExternalDocDescriber implements DescriberInterface { + /** + * @var array|callable + */ private $externalDoc; - private $overwrite; + private bool $overwrite; /** - * @param array|callable $externalDoc + * @param array|callable $externalDoc */ public function __construct($externalDoc, bool $overwrite = false) { @@ -29,6 +32,9 @@ public function __construct($externalDoc, bool $overwrite = false) $this->overwrite = $overwrite; } + /** + * @return void + */ public function describe(OA\OpenApi $api) { $externalDoc = $this->getExternalDoc(); @@ -38,6 +44,9 @@ public function describe(OA\OpenApi $api) } } + /** + * @return mixed The external doc + */ private function getExternalDoc() { if (is_callable($this->externalDoc)) { diff --git a/src/Describer/ModelRegistryAwareInterface.php b/src/Describer/ModelRegistryAwareInterface.php index 36bdc9e4a..d4dd050af 100644 --- a/src/Describer/ModelRegistryAwareInterface.php +++ b/src/Describer/ModelRegistryAwareInterface.php @@ -15,5 +15,8 @@ interface ModelRegistryAwareInterface { + /** + * @return void + */ public function setModelRegistry(ModelRegistry $modelRegistry); } diff --git a/src/Describer/ModelRegistryAwareTrait.php b/src/Describer/ModelRegistryAwareTrait.php index 3bcdecac3..e88f18a0c 100644 --- a/src/Describer/ModelRegistryAwareTrait.php +++ b/src/Describer/ModelRegistryAwareTrait.php @@ -15,11 +15,11 @@ trait ModelRegistryAwareTrait { + private ModelRegistry $modelRegistry; + /** - * @var ModelRegistry + * @return void */ - private $modelRegistry; - public function setModelRegistry(ModelRegistry $modelRegistry) { $this->modelRegistry = $modelRegistry; diff --git a/src/Describer/OpenApiPhpDescriber.php b/src/Describer/OpenApiPhpDescriber.php index 66399fa5d..c5da854e8 100644 --- a/src/Describer/OpenApiPhpDescriber.php +++ b/src/Describer/OpenApiPhpDescriber.php @@ -31,14 +31,11 @@ final class OpenApiPhpDescriber { use SetsContextTrait; - private $routeCollection; - private $controllerReflector; + private RouteCollection $routeCollection; + private ControllerReflector $controllerReflector; - /** - * @var Reader|null - */ - private $annotationReader; - private $logger; + private ?Reader $annotationReader; + private LoggerInterface $logger; public function __construct(RouteCollection $routeCollection, ControllerReflector $controllerReflector, ?Reader $annotationReader, LoggerInterface $logger, bool $overwrite = false) { @@ -52,7 +49,7 @@ public function __construct(RouteCollection $routeCollection, ControllerReflecto $this->logger = $logger; } - public function describe(OA\OpenApi $api) + public function describe(OA\OpenApi $api): void { $classAnnotations = []; @@ -200,6 +197,9 @@ private function getMethodsToParse(): \Generator } } + /** + * @return string[] + */ private function getSupportedHttpMethods(Route $route): array { $allMethods = Util::OPERATIONS; @@ -223,7 +223,7 @@ private function normalizePath(string $path): string } /** - * @param \ReflectionClass|\ReflectionMethod $reflection + * @param \ReflectionClass|\ReflectionMethod $reflection * * @return OA\AbstractAnnotation[] */ diff --git a/src/Describer/RouteDescriber.php b/src/Describer/RouteDescriber.php index 00b7ba729..d2a26ac65 100644 --- a/src/Describer/RouteDescriber.php +++ b/src/Describer/RouteDescriber.php @@ -20,11 +20,14 @@ final class RouteDescriber implements DescriberInterface, ModelRegistryAwareInte { use ModelRegistryAwareTrait; - private $routeCollection; + private RouteCollection $routeCollection; - private $controllerReflector; + private ControllerReflector $controllerReflector; - private $routeDescribers; + /** + * @var iterable + */ + private iterable $routeDescribers; /** * @param RouteDescriberInterface[]|iterable $routeDescribers @@ -36,7 +39,7 @@ public function __construct(RouteCollection $routeCollection, ControllerReflecto $this->routeDescribers = $routeDescribers; } - public function describe(OA\OpenApi $api) + public function describe(OA\OpenApi $api): void { if (0 === count($this->routeDescribers)) { return; @@ -49,7 +52,7 @@ public function describe(OA\OpenApi $api) // if able to resolve the controller $controller = $route->getDefault('_controller'); - if ($method = $this->controllerReflector->getReflectionMethod($controller)) { + if (null !== $method = $this->controllerReflector->getReflectionMethod($controller)) { // Extract as many information as possible about this route foreach ($this->routeDescribers as $describer) { if ($describer instanceof ModelRegistryAwareInterface) { diff --git a/src/Exception/UndocumentedArrayItemsException.php b/src/Exception/UndocumentedArrayItemsException.php index 2ce418741..7e1ee84c2 100644 --- a/src/Exception/UndocumentedArrayItemsException.php +++ b/src/Exception/UndocumentedArrayItemsException.php @@ -16,8 +16,8 @@ */ class UndocumentedArrayItemsException extends \LogicException { - private $class; - private $path; + private ?string $class; + private string $path; public function __construct(?string $class = null, string $path = '') { @@ -33,11 +33,17 @@ public function __construct(?string $class = null, string $path = '') parent::__construct(sprintf('Property "%s" is an array, but its items type isn\'t specified. You can specify that by using the type `string[]` for instance or `@OA\Property(type="array", @OA\Items(type="string"))`.', $propertyName)); } + /** + * @return string|null + */ public function getClass() { return $this->class; } + /** + * @return string + */ public function getPath() { return $this->path; diff --git a/src/Form/Extension/DocumentationExtension.php b/src/Form/Extension/DocumentationExtension.php index ec38b0953..fa6098745 100644 --- a/src/Form/Extension/DocumentationExtension.php +++ b/src/Form/Extension/DocumentationExtension.php @@ -32,6 +32,11 @@ public function configureOptions(OptionsResolver $resolver): void ->setAllowedTypes('documentation', ['array', 'bool']); } + /** + * @deprecated since Symfony 4.2, use getExtendedTypes() instead. + * + * @return string + */ public function getExtendedType() { return self::getExtendedTypes()[0]; diff --git a/src/Model/Model.php b/src/Model/Model.php index b7c796b68..59504e5d5 100644 --- a/src/Model/Model.php +++ b/src/Model/Model.php @@ -15,13 +15,22 @@ final class Model { - private $type; + private Type $type; - private $options; - private $serializationContext; + /** + * @var mixed[]|null + */ + private ?array $options; + + /** + * @var mixed[] + */ + private array $serializationContext; /** * @param string[]|null $groups + * @param mixed[]|null $options + * @param mixed[] $serializationContext */ public function __construct(Type $type, ?array $groups = null, ?array $options = null, array $serializationContext = []) { @@ -33,10 +42,7 @@ public function __construct(Type $type, ?array $groups = null, ?array $options = } } - /** - * @return Type - */ - public function getType() + public function getType(): Type { return $this->type; } @@ -44,7 +50,7 @@ public function getType() /** * @return string[]|null */ - public function getGroups() + public function getGroups(): ?array { return $this->serializationContext['groups'] ?? null; } @@ -65,7 +71,7 @@ public function getHash(): string /** * @return mixed[]|null */ - public function getOptions() + public function getOptions(): ?array { return $this->options; } diff --git a/src/Model/ModelRegistry.php b/src/Model/ModelRegistry.php index 1fadbcaba..3f0defe08 100644 --- a/src/Model/ModelRegistry.php +++ b/src/Model/ModelRegistry.php @@ -23,22 +23,41 @@ final class ModelRegistry { use LoggerAwareTrait; - private $registeredModelNames = []; + /** + * @var array List of model names to models + */ + private array $registeredModelNames = []; - private $alternativeNames = []; + /** + * @var Model[] + */ + private array $alternativeNames = []; - private $unregistered = []; + /** + * @var string[] List of hashes of models that have not been registered yet + */ + private array $unregistered = []; - private $models = []; + /** + * @var array List of model hashes to models + */ + private array $models = []; - private $names = []; + /** + * @var array List of model hashes to model names + */ + private array $names = []; - private $modelDescribers = []; + /** + * @var iterable + */ + private iterable $modelDescribers; - private $api; + private OA\OpenApi $api; /** * @param ModelDescriberInterface[]|iterable $modelDescribers + * @param array $alternativeNames * * @internal */ @@ -109,7 +128,7 @@ public function registerSchemas(): void } } - if (!$this->unregistered && $this->alternativeNames) { + if ([] === $this->unregistered && [] !== $this->alternativeNames) { foreach ($this->alternativeNames as $model) { $this->register($model); } @@ -140,6 +159,9 @@ private function generateModelName(Model $model): string return $name; } + /** + * @return array + */ private function modelToArray(Model $model): array { $getType = function (Type $type) use (&$getType) { @@ -148,8 +170,8 @@ private function modelToArray(Model $model): array 'built_in_type' => $type->getBuiltinType(), 'nullable' => $type->isNullable(), 'collection' => $type->isCollection(), - 'collection_key_types' => $type->isCollection() ? array_map($getType, $this->getCollectionKeyTypes($type)) : null, - 'collection_value_types' => $type->isCollection() ? array_map($getType, $this->getCollectionValueTypes($type)) : null, + 'collection_key_types' => $type->isCollection() ? array_map($getType, $type->getCollectionKeyTypes()) : null, + 'collection_value_types' => $type->isCollection() ? array_map($getType, $type->getCollectionValueTypes()) : null, ]; }; @@ -191,16 +213,6 @@ private function typeToString(Type $type): string } } - private function getCollectionKeyTypes(Type $type): array - { - return $type->getCollectionKeyTypes(); - } - - private function getCollectionValueTypes(Type $type): array - { - return $type->getCollectionValueTypes(); - } - private function getCollectionValueType(Type $type): ?Type { return $type->getCollectionValueTypes()[0] ?? null; diff --git a/src/ModelDescriber/Annotations/AnnotationsReader.php b/src/ModelDescriber/Annotations/AnnotationsReader.php index d8a299268..df1597263 100644 --- a/src/ModelDescriber/Annotations/AnnotationsReader.php +++ b/src/ModelDescriber/Annotations/AnnotationsReader.php @@ -21,10 +21,13 @@ */ class AnnotationsReader { - private $phpDocReader; - private $openApiAnnotationsReader; - private $symfonyConstraintAnnotationReader; + private PropertyPhpDocReader $phpDocReader; + private OpenApiAnnotationsReader $openApiAnnotationsReader; + private SymfonyConstraintAnnotationReader $symfonyConstraintAnnotationReader; + /** + * @param string[] $mediaTypes + */ public function __construct( ?Reader $annotationsReader, ModelRegistry $modelRegistry, @@ -49,11 +52,18 @@ public function updateDefinition(\ReflectionClass $reflectionClass, OA\Schema $s ); } + /** + * @param \ReflectionProperty|\ReflectionMethod $reflection + */ public function getPropertyName($reflection, string $default): string { return $this->openApiAnnotationsReader->getPropertyName($reflection, $default); } + /** + * @param \ReflectionProperty|\ReflectionMethod $reflection + * @param string[]|null $serializationGroups + */ public function updateProperty($reflection, OA\Property $property, ?array $serializationGroups = null): void { $this->openApiAnnotationsReader->updateProperty($reflection, $property, $serializationGroups); diff --git a/src/ModelDescriber/Annotations/OpenApiAnnotationsReader.php b/src/ModelDescriber/Annotations/OpenApiAnnotationsReader.php index a8153263c..9925d07b6 100644 --- a/src/ModelDescriber/Annotations/OpenApiAnnotationsReader.php +++ b/src/ModelDescriber/Annotations/OpenApiAnnotationsReader.php @@ -28,12 +28,12 @@ class OpenApiAnnotationsReader { use SetsContextTrait; + private ?Reader $annotationsReader; + private ModelRegister $modelRegister; + /** - * @var Reader|null + * @param string[] $mediaTypes */ - private $annotationsReader; - private $modelRegister; - public function __construct(?Reader $annotationsReader, ModelRegistry $modelRegistry, array $mediaTypes) { $this->annotationsReader = $annotationsReader; @@ -56,6 +56,9 @@ public function updateSchema(\ReflectionClass $reflectionClass, OA\Schema $schem $schema->mergeProperties($oaSchema); } + /** + * @param \ReflectionProperty|\ReflectionMethod $reflection + */ public function getPropertyName($reflection, string $default): string { if (null === $oaProperty = $this->getAnnotation(new Context(), $reflection, OA\Property::class)) { @@ -65,6 +68,10 @@ public function getPropertyName($reflection, string $default): string return Generator::UNDEFINED !== $oaProperty->property ? $oaProperty->property : $default; } + /** + * @param \ReflectionProperty|\ReflectionMethod $reflection + * @param string[]|null $serializationGroups + */ public function updateProperty($reflection, OA\Property $property, ?array $serializationGroups = null): void { if (null === $oaProperty = $this->getAnnotation($property->_context, $reflection, OA\Property::class)) { diff --git a/src/ModelDescriber/Annotations/PropertyPhpDocReader.php b/src/ModelDescriber/Annotations/PropertyPhpDocReader.php index d3b347f7f..dc22111ee 100644 --- a/src/ModelDescriber/Annotations/PropertyPhpDocReader.php +++ b/src/ModelDescriber/Annotations/PropertyPhpDocReader.php @@ -15,6 +15,7 @@ use OpenApi\Generator; use phpDocumentor\Reflection\DocBlock\Tags\Var_; use phpDocumentor\Reflection\DocBlockFactory; +use phpDocumentor\Reflection\DocBlockFactoryInterface; use phpDocumentor\Reflection\PseudoTypes\IntegerRange; use phpDocumentor\Reflection\PseudoTypes\NegativeInteger; use phpDocumentor\Reflection\PseudoTypes\PositiveInteger; @@ -27,7 +28,7 @@ */ class PropertyPhpDocReader { - private $docBlockFactory; + private DocBlockFactoryInterface $docBlockFactory; public function __construct() { @@ -36,6 +37,8 @@ public function __construct() /** * Update the Swagger information with information from the DocBlock comment. + * + * @param \ReflectionProperty|\ReflectionMethod $reflection */ public function updateProperty($reflection, OA\Property $property): void { @@ -50,7 +53,7 @@ public function updateProperty($reflection, OA\Property $property): void /** @var Var_ $var */ foreach ($docBlock->getTagsByName('var') as $var) { - if (!$title && method_exists($var, 'getDescription') && null !== $description = $var->getDescription()) { + if ('' === $title && method_exists($var, 'getDescription') && null !== $description = $var->getDescription()) { $title = $description->render(); } @@ -81,10 +84,10 @@ public function updateProperty($reflection, OA\Property $property): void } } - if (Generator::UNDEFINED === $property->title && $title) { + if (Generator::UNDEFINED === $property->title && '' !== $title) { $property->title = $title; } - if (Generator::UNDEFINED === $property->description && $docBlock->getDescription() && $docBlock->getDescription()->render()) { + if (Generator::UNDEFINED === $property->description && '' !== $docBlock->getDescription()->render()) { $property->description = $docBlock->getDescription()->render(); } if (Generator::UNDEFINED === $property->minimum && isset($min)) { diff --git a/src/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php b/src/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php index 78c09f4ed..db69a41b6 100644 --- a/src/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php +++ b/src/ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php @@ -52,6 +52,7 @@ public function __construct(?Reader $annotationsReader, bool $useValidationGroup * 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 { @@ -64,7 +65,11 @@ public function updateProperty($reflection, OA\Property $property, ?array $valid } } - private function processPropertyAnnotations($reflection, OA\Property $property, $annotations) + /** + * @param \ReflectionProperty|\ReflectionMethod $reflection + * @param Constraint[] $annotations + */ + private function processPropertyAnnotations($reflection, OA\Property $property, array $annotations): void { foreach ($annotations as $annotation) { if ($annotation instanceof Assert\NotBlank || $annotation instanceof Assert\NotNull) { @@ -138,7 +143,7 @@ private function processPropertyAnnotations($reflection, OA\Property $property, } } - public function setSchema($schema): void + public function setSchema(OA\Schema $schema): void { $this->schema = $schema; } @@ -146,7 +151,7 @@ public function setSchema($schema): void /** * Append the pattern from the constraint to the existing pattern. */ - private function appendPattern(OA\Schema $property, $newPattern): void + private function appendPattern(OA\Schema $property, ?string $newPattern): void { if (null === $newPattern) { return; @@ -179,6 +184,9 @@ private function applyEnumFromChoiceConstraint(OA\Schema $property, Assert\Choic /** * @param \ReflectionProperty|\ReflectionMethod $reflection + * @param string[]|null $validationGroups + * + * @return iterable */ private function getAnnotations(Context $parentContext, $reflection, ?array $validationGroups): iterable { @@ -200,10 +208,12 @@ private function getAnnotations(Context $parentContext, $reflection, ?array $val /** * @param \ReflectionProperty|\ReflectionMethod $reflection + * + * @return \Traversable */ private function locateAnnotations($reflection): \Traversable { - if (\PHP_VERSION_ID >= 80000 && class_exists(Constraint::class)) { + if (\PHP_VERSION_ID >= 80000) { foreach ($reflection->getAttributes(Constraint::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute) { yield $attribute->newInstance(); } @@ -225,6 +235,8 @@ private function locateAnnotations($reflection): \Traversable * 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. + * + * @param string[]|null $validationGroups */ private function isConstraintInGroup(Constraint $annotation, ?array $validationGroups): bool { diff --git a/src/ModelDescriber/Annotations/UpdateClassDefinitionResult.php b/src/ModelDescriber/Annotations/UpdateClassDefinitionResult.php index 3a3945c3e..1cafbd4f2 100644 --- a/src/ModelDescriber/Annotations/UpdateClassDefinitionResult.php +++ b/src/ModelDescriber/Annotations/UpdateClassDefinitionResult.php @@ -20,14 +20,14 @@ final class UpdateClassDefinitionResult { /** - * Whether or not the model describer shoudl continue reading class properties + * Whether the model describer should 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 + * Users may manually 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; + private bool $shouldDescribeModelProperties; public function __construct(bool $shouldDescribeModelProperties) { diff --git a/src/ModelDescriber/BazingaHateoasModelDescriber.php b/src/ModelDescriber/BazingaHateoasModelDescriber.php index c1cfa0b3c..ef7803780 100644 --- a/src/ModelDescriber/BazingaHateoasModelDescriber.php +++ b/src/ModelDescriber/BazingaHateoasModelDescriber.php @@ -26,8 +26,8 @@ class BazingaHateoasModelDescriber implements ModelDescriberInterface, ModelRegi { use ModelRegistryAwareTrait; - private $factory; - private $JMSModelDescriber; + private MetadataFactoryInterface $factory; + private JMSModelDescriber $JMSModelDescriber; public function __construct(MetadataFactoryInterface $factory, JMSModelDescriber $JMSModelDescriber) { @@ -45,18 +45,14 @@ public function describe(Model $model, OA\Schema $schema): void { $this->JMSModelDescriber->describe($model, $schema); - /** - * @var ClassMetadata - */ $metadata = $this->getHateoasMetadata($model); - if (null === $metadata) { + if (!$metadata instanceof ClassMetadata) { return; } $schema->type = 'object'; $context = $this->JMSModelDescriber->getSerializationContext($model); - /** @var Relation $relation */ foreach ($metadata->getRelations() as $relation) { if (null === $relation->getEmbedded() && null === $relation->getHref()) { continue; @@ -89,14 +85,10 @@ public function describe(Model $model, OA\Schema $schema): void } } - private function getHateoasMetadata(Model $model) + private function getHateoasMetadata(Model $model): ?object { - $className = $model->getType()->getClassName(); - try { - if ($metadata = $this->factory->getMetadataForClass($className)) { - return $metadata; - } + return $this->factory->getMetadataForClass($model->getType()->getClassName()); } catch (\ReflectionException $e) { } @@ -105,7 +97,8 @@ private function getHateoasMetadata(Model $model) public function supports(Model $model): bool { - return $this->JMSModelDescriber->supports($model) || null !== $this->getHateoasMetadata($model); + return $this->JMSModelDescriber->supports($model) + || $this->getHateoasMetadata($model) instanceof ClassMetadata; } private function setAttributeProperties(Relation $relation, OA\Property $subProperty): void diff --git a/src/ModelDescriber/FallbackObjectModelDescriber.php b/src/ModelDescriber/FallbackObjectModelDescriber.php index 19ff1fc27..696bd15fc 100644 --- a/src/ModelDescriber/FallbackObjectModelDescriber.php +++ b/src/ModelDescriber/FallbackObjectModelDescriber.php @@ -17,6 +17,9 @@ class FallbackObjectModelDescriber implements ModelDescriberInterface { + /** + * @return void + */ public function describe(Model $model, OA\Schema $schema) { } diff --git a/src/ModelDescriber/FormModelDescriber.php b/src/ModelDescriber/FormModelDescriber.php index 998ab3eb5..5ad00753b 100644 --- a/src/ModelDescriber/FormModelDescriber.php +++ b/src/ModelDescriber/FormModelDescriber.php @@ -39,16 +39,19 @@ final class FormModelDescriber implements ModelDescriberInterface, ModelRegistry use ModelRegistryAwareTrait; use SetsContextTrait; - private $formFactory; + private ?FormFactoryInterface $formFactory; + private ?Reader $doctrineReader; /** - * @var Reader|null + * @var string[] */ - private $doctrineReader; - private $mediaTypes; - private $useValidationGroups; - private $isFormCsrfExtensionEnabled; + private array $mediaTypes; + private bool $useValidationGroups; + private bool $isFormCsrfExtensionEnabled; + /** + * @param string[]|null $mediaTypes + */ public function __construct( ?FormFactoryInterface $formFactory = null, ?Reader $reader = null, @@ -69,7 +72,7 @@ public function __construct( $this->isFormCsrfExtensionEnabled = $isFormCsrfExtensionEnabled; } - public function describe(Model $model, OA\Schema $schema) + public function describe(Model $model, OA\Schema $schema): void { if (method_exists(AbstractType::class, 'setDefaultOptions')) { throw new \LogicException('symfony/form < 3.0 is not supported, please upgrade to an higher version to use a form as a model.'); @@ -107,7 +110,7 @@ public function supports(Model $model): bool return is_a($model->getType()->getClassName(), FormTypeInterface::class, true); } - private function parseForm(OA\Schema $schema, FormInterface $form) + private function parseForm(OA\Schema $schema, FormInterface $form): void { foreach ($form as $name => $child) { $config = $child->getConfig(); @@ -157,10 +160,8 @@ private function parseForm(OA\Schema $schema, FormInterface $form) /** * Finds and sets the schema type on $property based on $config info. - * - * Returns true if a native OpenAPi type was found, false otherwise */ - private function findFormType(FormConfigInterface $config, OA\Schema $property) + private function findFormType(FormConfigInterface $config, OA\Schema $property): void { $type = $config->getType(); @@ -305,6 +306,8 @@ private function findFormType(FormConfigInterface $config, OA\Schema $property) } /** + * @param mixed[] $array + * * @return bool true if $array contains only numbers, false otherwise */ private function isNumbersArray(array $array): bool @@ -319,6 +322,8 @@ private function isNumbersArray(array $array): bool } /** + * @param mixed[] $array + * * @return bool true if $array contains only booleans, false otherwise */ private function isBooleansArray(array $array): bool @@ -332,10 +337,7 @@ private function isBooleansArray(array $array): bool return true; } - /** - * @return ResolvedFormTypeInterface|null - */ - private function getBuiltinFormType(ResolvedFormTypeInterface $type) + private function getBuiltinFormType(ResolvedFormTypeInterface $type): ?ResolvedFormTypeInterface { do { $class = get_class($type->getInnerType()); diff --git a/src/ModelDescriber/JMSModelDescriber.php b/src/ModelDescriber/JMSModelDescriber.php index 3d38eebae..a5a9ea66c 100644 --- a/src/ModelDescriber/JMSModelDescriber.php +++ b/src/ModelDescriber/JMSModelDescriber.php @@ -36,33 +36,39 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn use ModelRegistryAwareTrait; use ApplyOpenApiDiscriminatorTrait; - private $factory; + private MetadataFactoryInterface $factory; - private $contextFactory; + private ?SerializationContextFactoryInterface $contextFactory; - private $namingStrategy; + private ?PropertyNamingStrategyInterface $namingStrategy; + + private ?Reader $doctrineReader; /** - * @var Reader|null + * @var array */ - private $doctrineReader; - - private $contexts = []; + private array $contexts = []; - private $metadataStacks = []; - - private $mediaTypes; + /** + * @var array + */ + private array $metadataStacks = []; /** - * @var array + * @var string[] */ - private $propertyTypeUseGroupsCache = []; + private array $mediaTypes; /** - * @var bool + * @var array */ - private $useValidationGroups; + private array $propertyTypeUseGroupsCache = []; + + private bool $useValidationGroups; + /** + * @param string[] $mediaTypes + */ public function __construct( MetadataFactoryInterface $factory, ?Reader $reader, @@ -79,6 +85,9 @@ public function __construct( $this->contextFactory = $contextFactory; } + /** + * @return void + */ public function describe(Model $model, OA\Schema $schema) { $className = $model->getType()->getClassName(); @@ -200,7 +209,7 @@ public function describe(Model $model, OA\Schema $schema) /** * @internal */ - public function getSerializationContext(Model $model): SerializationContext + public function getSerializationContext(Model $model): Context { if (isset($this->contexts[$model->getHash()])) { $context = $this->contexts[$model->getHash()]; @@ -214,7 +223,9 @@ public function getSerializationContext(Model $model): SerializationContext $stack->unshift($metadataCopy); } } else { - $context = $this->contextFactory ? $this->contextFactory->createSerializationContext() : SerializationContext::create(); + $context = null !== $this->contextFactory + ? $this->contextFactory->createSerializationContext() + : SerializationContext::create(); if (null !== $model->getGroups()) { $context->addExclusionStrategy(new GroupsExclusionStrategy($model->getGroups())); @@ -224,7 +235,12 @@ public function getSerializationContext(Model $model): SerializationContext return $context; } - private function computeGroups(Context $context, ?array $type = null) + /** + * @param mixed[]|null $type + * + * @return string[]|null + */ + private function computeGroups(Context $context, ?array $type = null): ?array { if (null === $type || true !== $this->propertyTypeUsesGroups($type)) { return null; @@ -248,7 +264,7 @@ public function supports(Model $model): bool $className = $model->getType()->getClassName(); try { - if ($this->factory->getMetadataForClass($className)) { + if (null !== $this->factory->getMetadataForClass($className)) { return true; } } catch (\ReflectionException $e) { @@ -259,8 +275,10 @@ public function supports(Model $model): bool /** * @internal + * + * @param mixed[] $type */ - public function describeItem(array $type, OA\Schema $property, Context $context) + public function describeItem(array $type, OA\Schema $property, Context $context): void { $nestedTypeInfo = $this->getNestedTypeInArray($type); if (null !== $nestedTypeInfo) { @@ -329,7 +347,12 @@ public function describeItem(array $type, OA\Schema $property, Context $context) } } - private function getNestedTypeInArray(array $type) + /** + * @param mixed[] $type + * + * @return array{0: mixed, 1: bool}|null + */ + private function getNestedTypeInArray(array $type): ?array { if ('array' !== $type['name'] && 'ArrayCollection' !== $type['name']) { return null; @@ -347,9 +370,9 @@ private function getNestedTypeInArray(array $type) } /** - * @return bool|null + * @param mixed[] $type */ - private function propertyTypeUsesGroups(array $type) + private function propertyTypeUsesGroups(array $type): ?bool { if (array_key_exists($type['name'], $this->propertyTypeUseGroupsCache)) { return $this->propertyTypeUseGroupsCache[$type['name']]; @@ -359,7 +382,7 @@ private function propertyTypeUsesGroups(array $type) $metadata = $this->factory->getMetadataForClass($type['name']); foreach ($metadata->propertyMetadata as $item) { - if (null !== $item->groups && $item->groups != [GroupsExclusionStrategy::DEFAULT_GROUP]) { + if (isset($item->groups) && $item->groups != [GroupsExclusionStrategy::DEFAULT_GROUP]) { $this->propertyTypeUseGroupsCache[$type['name']] = true; return true; diff --git a/src/ModelDescriber/ModelDescriberInterface.php b/src/ModelDescriber/ModelDescriberInterface.php index 56c2d808d..4d6415332 100644 --- a/src/ModelDescriber/ModelDescriberInterface.php +++ b/src/ModelDescriber/ModelDescriberInterface.php @@ -16,6 +16,9 @@ interface ModelDescriberInterface { + /** + * @return void + */ public function describe(Model $model, Schema $schema); public function supports(Model $model): bool; diff --git a/src/ModelDescriber/ObjectModelDescriber.php b/src/ModelDescriber/ObjectModelDescriber.php index 5a93158fa..eac0370eb 100644 --- a/src/ModelDescriber/ObjectModelDescriber.php +++ b/src/ModelDescriber/ObjectModelDescriber.php @@ -49,6 +49,7 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar /** * @param PropertyDescriberInterface|PropertyDescriberInterface[] $propertyDescribers * @param (NameConverterInterface&AdvancedNameConverterInterface)|null $nameConverter + * @param string[] $mediaTypes */ public function __construct( PropertyInfoExtractorInterface $propertyInfo, @@ -218,7 +219,7 @@ private function camelize(string $string): string /** * @param Type[] $types */ - private function describeProperty(array $types, Model $model, OA\Schema $property, string $propertyName, OA\Schema $schema) + private function describeProperty(array $types, Model $model, OA\Schema $property, string $propertyName, OA\Schema $schema): void { $propertyDescribers = is_iterable($this->propertyDescriber) ? $this->propertyDescriber : [$this->propertyDescriber]; diff --git a/src/OpenApiPhp/ModelRegister.php b/src/OpenApiPhp/ModelRegister.php index 7de7a46c9..de20ee6bb 100644 --- a/src/OpenApiPhp/ModelRegister.php +++ b/src/OpenApiPhp/ModelRegister.php @@ -26,19 +26,24 @@ */ final class ModelRegister { - /** @var ModelRegistry */ - private $modelRegistry; + private ModelRegistry $modelRegistry; /** @var string[] */ - private $mediaTypes; + private array $mediaTypes; + /** + * @param string[] $mediaTypes + */ public function __construct(ModelRegistry $modelRegistry, array $mediaTypes) { $this->modelRegistry = $modelRegistry; $this->mediaTypes = $mediaTypes; } - public function __invoke(Analysis $analysis, ?array $parentGroups = null) + /** + * @param string[]|null $parentGroups + */ + public function __invoke(Analysis $analysis, ?array $parentGroups = null): void { foreach ($analysis->annotations as $annotation) { // @Model using the ref field @@ -92,11 +97,6 @@ public function __invoke(Analysis $analysis, ?array $parentGroups = null) $annotationClass = OA\Schema::class; } - if (!is_string($model->type)) { - // Ignore invalid annotations, they are validated later - continue; - } - $annotation->merge([new $annotationClass([ 'ref' => $this->modelRegistry->register(new Model($this->createType($model->type), $this->getGroups($model, $parentGroups), $model->options, $model->serializationContext)), ])]); @@ -106,6 +106,11 @@ public function __invoke(Analysis $analysis, ?array $parentGroups = null) } } + /** + * @param string[]|null $parentGroups + * + * @return string[]|null + */ private function getGroups(ModelAnnotation $model, ?array $parentGroups = null): ?array { if (null === $model->groups) { @@ -152,12 +157,15 @@ private function getModel(OA\AbstractAnnotation $annotation): ?ModelAnnotation return null; } + /** + * @param mixed[] $properties + */ private function createContentForMediaType( string $type, array $properties, OA\AbstractAnnotation $annotation, Analysis $analysis - ) { + ): void { switch ($type) { case 'json': $modelAnnotation = new OA\JsonContent($properties); diff --git a/src/OpenApiPhp/Util.php b/src/OpenApiPhp/Util.php index fa3f18e2c..9c967b731 100644 --- a/src/OpenApiPhp/Util.php +++ b/src/OpenApiPhp/Util.php @@ -54,7 +54,7 @@ final class Util /** * All http method verbs as known by swagger. * - * @var array + * @var string[] */ public const OPERATIONS = ['get', 'post', 'put', 'patch', 'delete', 'options', 'head', 'trace']; @@ -64,10 +64,8 @@ final class Util * * @see OA\OpenApi::$paths * @see OA\PathItem::path - * - * @param string $path */ - public static function getPath(OA\OpenApi $api, $path): OA\PathItem + public static function getPath(OA\OpenApi $api, string $path): OA\PathItem { return self::getIndexedCollectionItem($api, OA\PathItem::class, $path); } @@ -76,12 +74,10 @@ public static function getPath(OA\OpenApi $api, $path): OA\PathItem * Return an existing Schema object from $api->components->schemas[] having its member schema set to $schema. * Create, add to $api->components->schemas[] and return this new Schema object and set the property if none found. * - * @param string $schema - * * @see OA\Schema::$schema * @see OA\Components::$schemas */ - public static function getSchema(OA\OpenApi $api, $schema): OA\Schema + public static function getSchema(OA\OpenApi $api, string $schema): OA\Schema { if (!$api->components instanceof OA\Components) { $api->components = new OA\Components(['_context' => self::createWeakContext($api->_context)]); @@ -99,10 +95,8 @@ public static function getSchema(OA\OpenApi $api, $schema): OA\Schema * * @see OA\Schema::$properties * @see OA\Property::$property - * - * @param string $property */ - public static function getProperty(OA\Schema $schema, $property): OA\Property + public static function getProperty(OA\Schema $schema, string $property): OA\Property { return self::getIndexedCollectionItem($schema, OA\Property::class, $property); } @@ -111,17 +105,15 @@ public static function getProperty(OA\Schema $schema, $property): OA\Property * Return an existing Operation from $path->{$method} * or create, set $path->{$method} and return this new Operation object. * - * @see OA\PathItem::$get * @see OA\PathItem::$post * @see OA\PathItem::$put * @see OA\PathItem::$patch * @see OA\PathItem::$delete * @see OA\PathItem::$options * @see OA\PathItem::$head - * - * @param string $method + * @see OA\PathItem::$get */ - public static function getOperation(OA\PathItem $path, $method): OA\Operation + public static function getOperation(OA\PathItem $path, string $method): OA\Operation { $class = array_keys($path::$_nested, \strtolower($method), true)[0]; @@ -160,13 +152,14 @@ public static function getOperationParameter(OA\Operation $operation, $name, $in * * @template T of OA\AbstractAnnotation * - * @param class-string $class + * @param class-string $class + * @param array $properties * * @return T * * @see OA\AbstractAnnotation::$_nested */ - public static function getChild(OA\AbstractAnnotation $parent, $class, array $properties = []): OA\AbstractAnnotation + public static function getChild(OA\AbstractAnnotation $parent, string $class, array $properties = []): OA\AbstractAnnotation { $nested = $parent::$_nested; $property = $nested[$class]; @@ -190,13 +183,14 @@ public static function getChild(OA\AbstractAnnotation $parent, $class, array $pr * * @template T of OA\AbstractAnnotation * - * @param class-string $class + * @param class-string $class + * @param array $properties * * @return T * * @see OA\AbstractAnnotation::$_nested */ - public static function getCollectionItem(OA\AbstractAnnotation $parent, $class, array $properties = []): OA\AbstractAnnotation + public static function getCollectionItem(OA\AbstractAnnotation $parent, string $class, array $properties = []): OA\AbstractAnnotation { $key = null; $nested = $parent::$_nested; @@ -228,12 +222,13 @@ public static function getCollectionItem(OA\AbstractAnnotation $parent, $class, * @template T of OA\AbstractAnnotation * * @param class-string $class + * @param mixed $value The value to set * * @return T * * @see OA\AbstractAnnotation::$_nested */ - public static function getIndexedCollectionItem(OA\AbstractAnnotation $parent, $class, $value): OA\AbstractAnnotation + public static function getIndexedCollectionItem(OA\AbstractAnnotation $parent, string $class, $value): OA\AbstractAnnotation { $nested = $parent::$_nested; [$collection, $property] = $nested[$class]; @@ -255,6 +250,9 @@ public static function getIndexedCollectionItem(OA\AbstractAnnotation $parent, $ * Search for an Annotation within $collection that has all members set * to the respective values in the associative array $properties. * + * @param mixed[] $properties + * @param mixed[] $collection + * * @return int|string|null */ public static function searchCollectionItem(array $collection, array $properties) @@ -275,11 +273,12 @@ public static function searchCollectionItem(array $collection, array $properties /** * Search for an Annotation within the $collection that has its member $index set to $value. * - * @param string $member + * @param mixed[] $collection + * @param mixed $value The value to search for * * @return false|int|string */ - public static function searchIndexedCollectionItem(array $collection, $member, $value) + public static function searchIndexedCollectionItem(array $collection, string $member, $value) { return array_search($value, array_column($collection, $member), true); } @@ -288,10 +287,12 @@ public static function searchIndexedCollectionItem(array $collection, $member, $ * Create a new Object of $class with members $properties within $parent->{$collection}[] * and return the created index. * - * @param string $collection - * @param string $class + * @template T of OA\AbstractAnnotation + * + * @param class-string $class + * @param array $properties */ - public static function createCollectionItem(OA\AbstractAnnotation $parent, $collection, $class, array $properties = []): int + public static function createCollectionItem(OA\AbstractAnnotation $parent, string $collection, string $class, array $properties = []): int { if (Generator::UNDEFINED === $parent->{$collection}) { $parent->{$collection} = []; @@ -308,13 +309,14 @@ public static function createCollectionItem(OA\AbstractAnnotation $parent, $coll * * @template T of OA\AbstractAnnotation * - * @param class-string $class + * @param class-string $class + * @param array $properties * * @return T * * @throws \InvalidArgumentException at an attempt to pass in properties that are found in $parent::$_nested */ - public static function createChild(OA\AbstractAnnotation $parent, $class, array $properties = []): OA\AbstractAnnotation + public static function createChild(OA\AbstractAnnotation $parent, string $class, array $properties = []): OA\AbstractAnnotation { $nesting = self::getNestingIndexes($class); @@ -330,6 +332,8 @@ public static function createChild(OA\AbstractAnnotation $parent, $class, array /** * Create a new Context with members $properties and parent context $parent. * + * @param array $properties + * * @see Context */ public static function createContext(array $properties = [], ?Context $parent = null): Context @@ -340,6 +344,8 @@ public static function createContext(array $properties = [], ?Context $parent = /** * Create a new Context by copying the properties of the parent, but without a reference to the parent. * + * @param array $additionalProperties + * * @see Context */ public static function createWeakContext(?Context $parent = null, array $additionalProperties = []): Context @@ -375,9 +381,9 @@ public static function createWeakContext(?Context $parent = null, array $additio * The main purpose is to create a Swagger Object from array config values * in the structure of a json serialized Swagger object. * - * @param array|\ArrayObject|OA\AbstractAnnotation $from + * @param array|\ArrayObject|OA\AbstractAnnotation $from */ - public static function merge(OA\AbstractAnnotation $annotation, $from, bool $overwrite = false) + public static function merge(OA\AbstractAnnotation $annotation, $from, bool $overwrite = false): void { if (\is_array($from)) { self::mergeFromArray($annotation, $from, $overwrite); @@ -408,7 +414,10 @@ public static function getSchemaPropertyName(OA\Schema $schema, OA\Schema $prope return null; } - private static function mergeFromArray(OA\AbstractAnnotation $annotation, array $properties, bool $overwrite) + /** + * @param array $properties + */ + private static function mergeFromArray(OA\AbstractAnnotation $annotation, array $properties, bool $overwrite): void { $done = []; @@ -428,7 +437,7 @@ private static function mergeFromArray(OA\AbstractAnnotation $annotation, array } elseif (\array_key_exists($propertyName[0], $properties)) { $collection = $propertyName[0]; $property = $propertyName[1] ?? null; - self::mergeCollection($annotation, $className, $collection, $property, $properties[$collection], $overwrite); + self::mergeCollection($annotation, $className, $property, $properties[$collection], $overwrite); $done[] = $collection; } } @@ -463,12 +472,24 @@ private static function mergeFromArray(OA\AbstractAnnotation $annotation, array } } - private static function mergeChild(OA\AbstractAnnotation $annotation, $className, $value, bool $overwrite) + /** + * @template T of OA\AbstractAnnotation + * + * @param class-string $className + * @param mixed $value The value of the property + */ + private static function mergeChild(OA\AbstractAnnotation $annotation, string $className, $value, bool $overwrite): void { self::merge(self::getChild($annotation, $className), $value, $overwrite); } - private static function mergeCollection(OA\AbstractAnnotation $annotation, $className, $collection, $property, $items, bool $overwrite) + /** + * @template T of OA\AbstractAnnotation + * + * @param class-string $className + * @param array|\ArrayObject $items + */ + private static function mergeCollection(OA\AbstractAnnotation $annotation, string $className, ?string $property, $items, bool $overwrite): void { if (null !== $property) { foreach ($items as $prop => $value) { @@ -492,7 +513,12 @@ private static function mergeCollection(OA\AbstractAnnotation $annotation, $clas } } - private static function mergeTyped(OA\AbstractAnnotation $annotation, $propertyName, $type, array $properties, array $defaults, bool $overwrite) + /** + * @param array $properties + * @param array $defaults + * @param string|array $type + */ + private static function mergeTyped(OA\AbstractAnnotation $annotation, string $propertyName, $type, array $properties, array $defaults, bool $overwrite): void { if (\is_string($type) && 0 === strpos($type, '[')) { $innerType = substr($type, 1, -1); @@ -521,14 +547,25 @@ private static function mergeTyped(OA\AbstractAnnotation $annotation, $propertyN } } - private static function mergeProperty(OA\AbstractAnnotation $annotation, $propertyName, $value, $default, bool $overwrite) + /** + * @param mixed $value The new value of the property + * @param mixed $default The default value of the property + */ + private static function mergeProperty(OA\AbstractAnnotation $annotation, string $propertyName, $value, $default, bool $overwrite): void { if (true === $overwrite || $default === $annotation->{$propertyName}) { $annotation->{$propertyName} = $value; } } - private static function getNestingIndexes($class): array + /** + * @template T of OA\AbstractAnnotation + * + * @param class-string $class + * + * @return array + */ + private static function getNestingIndexes(string $class): array { return array_values(array_map( function ($value) { @@ -540,6 +577,8 @@ function ($value) { /** * Helper method to modify an annotation value only if its value has not yet been set. + * + * @param mixed $value The new value to set */ public static function modifyAnnotationValue(OA\AbstractAnnotation $parameter, string $property, $value): void { diff --git a/src/Processor/MapQueryStringProcessor.php b/src/Processor/MapQueryStringProcessor.php index 48446456a..273b4e0f5 100644 --- a/src/Processor/MapQueryStringProcessor.php +++ b/src/Processor/MapQueryStringProcessor.php @@ -29,7 +29,7 @@ */ final class MapQueryStringProcessor implements ProcessorInterface { - public function __invoke(Analysis $analysis) + public function __invoke(Analysis $analysis): void { /** @var OA\Operation[] $operations */ $operations = $analysis->getAnnotationsOfType(OA\Operation::class); @@ -50,6 +50,9 @@ public function __invoke(Analysis $analysis) } } + /** + * @param array $mapQueryStringContext + */ private function addQueryParameters(Analysis $analysis, OA\Operation $operation, array $mapQueryStringContext): void { $argumentMetaData = $mapQueryStringContext[SymfonyMapQueryStringDescriber::CONTEXT_ARGUMENT_METADATA]; diff --git a/src/Processor/MapRequestPayloadProcessor.php b/src/Processor/MapRequestPayloadProcessor.php index ea86e9355..36cc345ac 100644 --- a/src/Processor/MapRequestPayloadProcessor.php +++ b/src/Processor/MapRequestPayloadProcessor.php @@ -30,7 +30,7 @@ */ final class MapRequestPayloadProcessor implements ProcessorInterface { - public function __invoke(Analysis $analysis) + public function __invoke(Analysis $analysis): void { /** @var OA\Operation[] $operations */ $operations = $analysis->getAnnotationsOfType(OA\Operation::class); diff --git a/src/PropertyDescriber/ArrayPropertyDescriber.php b/src/PropertyDescriber/ArrayPropertyDescriber.php index 0b1287073..4f7d4230e 100644 --- a/src/PropertyDescriber/ArrayPropertyDescriber.php +++ b/src/PropertyDescriber/ArrayPropertyDescriber.php @@ -22,6 +22,9 @@ class ArrayPropertyDescriber implements PropertyDescriberInterface, ModelRegistr use ModelRegistryAwareTrait; use PropertyDescriberAwareTrait; + /** + * @param array $context Context options for describing the property + */ public function describe(array $types, OA\Schema $property, ?array $groups = null, ?OA\Schema $schema = null, array $context = []) { $property->type = 'array'; diff --git a/src/PropertyDescriber/BooleanPropertyDescriber.php b/src/PropertyDescriber/BooleanPropertyDescriber.php index b90a37eb8..e6fef6172 100644 --- a/src/PropertyDescriber/BooleanPropertyDescriber.php +++ b/src/PropertyDescriber/BooleanPropertyDescriber.php @@ -16,6 +16,9 @@ class BooleanPropertyDescriber implements PropertyDescriberInterface { + /** + * @param array $context Context options for describing the property + */ public function describe(array $types, OA\Schema $property, ?array $groups = null, ?OA\Schema $schema = null, array $context = []) { $property->type = 'boolean'; diff --git a/src/PropertyDescriber/CompoundPropertyDescriber.php b/src/PropertyDescriber/CompoundPropertyDescriber.php index 29686f2aa..da57a8494 100644 --- a/src/PropertyDescriber/CompoundPropertyDescriber.php +++ b/src/PropertyDescriber/CompoundPropertyDescriber.php @@ -22,6 +22,9 @@ class CompoundPropertyDescriber implements PropertyDescriberInterface, ModelRegi use ModelRegistryAwareTrait; use PropertyDescriberAwareTrait; + /** + * @param array $context Context options for describing the property + */ 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 : []; diff --git a/src/PropertyDescriber/DateTimePropertyDescriber.php b/src/PropertyDescriber/DateTimePropertyDescriber.php index 1e57292e5..dcddb7fbf 100644 --- a/src/PropertyDescriber/DateTimePropertyDescriber.php +++ b/src/PropertyDescriber/DateTimePropertyDescriber.php @@ -16,6 +16,9 @@ class DateTimePropertyDescriber implements PropertyDescriberInterface { + /** + * @param array $context Context options for describing the property + */ public function describe(array $types, OA\Schema $property, ?array $groups = null, ?OA\Schema $schema = null, array $context = []) { $property->type = 'string'; diff --git a/src/PropertyDescriber/DictionaryPropertyDescriber.php b/src/PropertyDescriber/DictionaryPropertyDescriber.php index 0c3d31c37..8476d5716 100644 --- a/src/PropertyDescriber/DictionaryPropertyDescriber.php +++ b/src/PropertyDescriber/DictionaryPropertyDescriber.php @@ -22,6 +22,9 @@ final class DictionaryPropertyDescriber implements PropertyDescriberInterface, M use ModelRegistryAwareTrait; use PropertyDescriberAwareTrait; + /** + * @param array $context Context options for describing the property + */ public function describe(array $types, OA\Schema $property, ?array $groups = null, ?OA\Schema $schema = null, array $context = []) { $property->type = 'object'; diff --git a/src/PropertyDescriber/FloatPropertyDescriber.php b/src/PropertyDescriber/FloatPropertyDescriber.php index 4dee3ef35..8749e0780 100644 --- a/src/PropertyDescriber/FloatPropertyDescriber.php +++ b/src/PropertyDescriber/FloatPropertyDescriber.php @@ -16,6 +16,9 @@ class FloatPropertyDescriber implements PropertyDescriberInterface { + /** + * @param array $context Context options for describing the property + */ public function describe(array $types, OA\Schema $property, ?array $groups = null, ?OA\Schema $schema = null, array $context = []) { $property->type = 'number'; diff --git a/src/PropertyDescriber/IntegerPropertyDescriber.php b/src/PropertyDescriber/IntegerPropertyDescriber.php index 746fd9116..0d4ba17e9 100644 --- a/src/PropertyDescriber/IntegerPropertyDescriber.php +++ b/src/PropertyDescriber/IntegerPropertyDescriber.php @@ -16,6 +16,9 @@ class IntegerPropertyDescriber implements PropertyDescriberInterface { + /** + * @param array $context Context options for describing the property + */ public function describe(array $types, OA\Schema $property, ?array $groups = null, ?OA\Schema $schema = null, array $context = []) { $property->type = 'integer'; diff --git a/src/PropertyDescriber/NullablePropertyDescriber.php b/src/PropertyDescriber/NullablePropertyDescriber.php index d9adad526..3666ced89 100644 --- a/src/PropertyDescriber/NullablePropertyDescriber.php +++ b/src/PropertyDescriber/NullablePropertyDescriber.php @@ -18,6 +18,9 @@ final class NullablePropertyDescriber implements PropertyDescriberInterface, Pro { use PropertyDescriberAwareTrait; + /** + * @param array $context Context options for describing the property + */ public function describe(array $types, OA\Schema $property, ?array $groups = null, ?OA\Schema $schema = null, array $context = []) { if (Generator::UNDEFINED === $property->nullable) { diff --git a/src/PropertyDescriber/ObjectPropertyDescriber.php b/src/PropertyDescriber/ObjectPropertyDescriber.php index 7f065b378..e7df42c13 100644 --- a/src/PropertyDescriber/ObjectPropertyDescriber.php +++ b/src/PropertyDescriber/ObjectPropertyDescriber.php @@ -22,6 +22,9 @@ class ObjectPropertyDescriber implements PropertyDescriberInterface, ModelRegist { use ModelRegistryAwareTrait; + /** + * @param array $context Context options for describing the property + */ public function describe(array $types, OA\Schema $property, ?array $groups = null, ?OA\Schema $schema = null, array $context = []) { $type = new Type( diff --git a/src/PropertyDescriber/PropertyDescriber.php b/src/PropertyDescriber/PropertyDescriber.php index 7d707c15a..d820ec596 100644 --- a/src/PropertyDescriber/PropertyDescriber.php +++ b/src/PropertyDescriber/PropertyDescriber.php @@ -16,23 +16,30 @@ use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface; use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait; use OpenApi\Annotations as OA; +use Symfony\Component\PropertyInfo\Type; final class PropertyDescriber implements PropertyDescriberInterface, ModelRegistryAwareInterface { use ModelRegistryAwareTrait; /** @var array Recursion helper */ - private $called = []; + private array $called = []; - /** @var PropertyDescriberInterface[] */ - private $propertyDescribers; + /** @var iterable */ + private iterable $propertyDescribers; + /** + * @param iterable $propertyDescribers + */ public function __construct( iterable $propertyDescribers ) { $this->propertyDescribers = $propertyDescribers; } + /** + * @param array $context Context options for describing the property + */ public function describe(array $types, OA\Schema $property, ?array $groups = null, ?OA\Schema $schema = null, array $context = []): void { if (null === $propertyDescriber = $this->getPropertyDescriber($types)) { @@ -49,11 +56,17 @@ public function supports(array $types): bool return null !== $this->getPropertyDescriber($types); } + /** + * @param Type[] $types + */ private function getHash(array $types): string { return md5(serialize($types)); } + /** + * @param Type[] $types + */ private function getPropertyDescriber(array $types): ?PropertyDescriberInterface { foreach ($this->propertyDescribers as $propertyDescriber) { diff --git a/src/PropertyDescriber/PropertyDescriberInterface.php b/src/PropertyDescriber/PropertyDescriberInterface.php index d2ec894a2..feac04bb3 100644 --- a/src/PropertyDescriber/PropertyDescriberInterface.php +++ b/src/PropertyDescriber/PropertyDescriberInterface.php @@ -21,6 +21,8 @@ interface PropertyDescriberInterface * @param string[]|null $groups Deprecated use $context['groups'] instead * @param Schema $schema Allows to make changes inside of the schema (e.g. adding required fields) * @param array $context Context options for describing the property + * + * @return void */ public function describe(array $types, Schema $property, ?array $groups = null /* , ?Schema $schema = null */ /* , array $context = [] */); diff --git a/src/PropertyDescriber/RequiredPropertyDescriber.php b/src/PropertyDescriber/RequiredPropertyDescriber.php index 2709ff4df..1b8b70e68 100644 --- a/src/PropertyDescriber/RequiredPropertyDescriber.php +++ b/src/PropertyDescriber/RequiredPropertyDescriber.php @@ -21,6 +21,9 @@ final class RequiredPropertyDescriber implements PropertyDescriberInterface, Pro { use PropertyDescriberAwareTrait; + /** + * @param array $context Context options for describing the property + */ public function describe(array $types, OA\Schema $property, ?array $groups = null, ?OA\Schema $schema = null, array $context = []) { $this->propertyDescriber->describe($types, $property, $groups, $schema, $context); diff --git a/src/PropertyDescriber/StringPropertyDescriber.php b/src/PropertyDescriber/StringPropertyDescriber.php index 417992ba3..f67c36e5e 100644 --- a/src/PropertyDescriber/StringPropertyDescriber.php +++ b/src/PropertyDescriber/StringPropertyDescriber.php @@ -16,6 +16,9 @@ class StringPropertyDescriber implements PropertyDescriberInterface { + /** + * @param array $context Context options for describing the property + */ public function describe(array $types, OA\Schema $property, ?array $groups = null, ?OA\Schema $schema = null, array $context = []) { $property->type = 'string'; diff --git a/src/Render/Html/GetNelmioAsset.php b/src/Render/Html/GetNelmioAsset.php index 0eada281e..bb15af4e8 100644 --- a/src/Render/Html/GetNelmioAsset.php +++ b/src/Render/Html/GetNelmioAsset.php @@ -20,9 +20,9 @@ */ class GetNelmioAsset extends AbstractExtension { - private $assetExtension; - private $resourcesDir; - private $cdnUrl; + private AssetExtension $assetExtension; + private string $resourcesDir; + private string $cdnUrl; public function __construct(AssetExtension $assetExtension) { @@ -38,7 +38,7 @@ public function getFunctions(): array ]; } - public function __invoke($defaultAssetsMode, $asset) + public function __invoke(string $defaultAssetsMode, string $asset): string { [$extension, $mode] = $this->getExtension($defaultAssetsMode, $asset); [$resource, $isInline] = $this->getResource($asset, $mode); @@ -51,7 +51,10 @@ public function __invoke($defaultAssetsMode, $asset) } } - private function getExtension($assetsMode, $asset) + /** + * @return array{string, string} + */ + private function getExtension(string $assetsMode, string $asset): array { $extension = mb_substr($asset, -3, 3, 'utf-8'); if ('.js' === $extension) { @@ -63,7 +66,10 @@ private function getExtension($assetsMode, $asset) } } - private function getResource($asset, $mode) + /** + * @return array{string, bool} + */ + private function getResource(string $asset, string $mode): array { if (filter_var($asset, FILTER_VALIDATE_URL)) { return [$asset, false]; @@ -76,7 +82,7 @@ private function getResource($asset, $mode) } } - private function renderJavascript(string $script, bool $isInline) + private function renderJavascript(string $script, bool $isInline): string { if ($isInline) { return sprintf('', $script); @@ -85,7 +91,7 @@ private function renderJavascript(string $script, bool $isInline) } } - private function renderCss(string $stylesheet, bool $isInline) + private function renderCss(string $stylesheet, bool $isInline): string { if ($isInline) { return sprintf('', $stylesheet); diff --git a/src/Render/Html/HtmlOpenApiRenderer.php b/src/Render/Html/HtmlOpenApiRenderer.php index f5181e47a..edb47d6c9 100644 --- a/src/Render/Html/HtmlOpenApiRenderer.php +++ b/src/Render/Html/HtmlOpenApiRenderer.php @@ -24,6 +24,9 @@ class HtmlOpenApiRenderer implements OpenApiRenderer /** @var Environment|\Twig_Environment */ private $twig; + /** + * @param Environment|\Twig_Environment $twig + */ public function __construct($twig) { if (!$twig instanceof \Twig_Environment && !$twig instanceof Environment) { diff --git a/src/Render/Json/JsonOpenApiRenderer.php b/src/Render/Json/JsonOpenApiRenderer.php index 12bbf378f..b903f72ae 100644 --- a/src/Render/Json/JsonOpenApiRenderer.php +++ b/src/Render/Json/JsonOpenApiRenderer.php @@ -30,7 +30,7 @@ public function render(OpenApi $spec, array $options = []): string $options += [ 'no-pretty' => false, ]; - $flags = $options['no-pretty'] ? 0 : JSON_PRETTY_PRINT; + $flags = true === $options['no-pretty'] ? 0 : JSON_PRETTY_PRINT; return json_encode($spec, $flags | JSON_UNESCAPED_SLASHES); } diff --git a/src/Render/OpenApiRenderer.php b/src/Render/OpenApiRenderer.php index 73edb66ca..a474002ce 100644 --- a/src/Render/OpenApiRenderer.php +++ b/src/Render/OpenApiRenderer.php @@ -20,5 +20,8 @@ interface OpenApiRenderer { public function getFormat(): string; + /** + * @param array $options + */ public function render(OpenApi $spec, array $options = []): string; } diff --git a/src/Render/RenderOpenApi.php b/src/Render/RenderOpenApi.php index 12a2d64c4..a7d1ae5c5 100644 --- a/src/Render/RenderOpenApi.php +++ b/src/Render/RenderOpenApi.php @@ -25,11 +25,10 @@ class RenderOpenApi public const JSON = 'json'; public const YAML = 'yaml'; - /** @var ContainerInterface */ - private $generatorLocator; + private ContainerInterface $generatorLocator; /** @var array */ - private $openApiRenderers = []; + private array $openApiRenderers = []; public function __construct(ContainerInterface $generatorLocator, ?OpenApiRenderer ...$openApiRenderers) { @@ -43,12 +42,20 @@ public function __construct(ContainerInterface $generatorLocator, ?OpenApiRender } } + /** + * @return string[] + */ public function getAvailableFormats(): array { return array_keys($this->openApiRenderers); } - public function renderFromRequest(Request $request, string $format, $area, array $extraOptions = []) + /** + * @param array $extraOptions + * + * @return string + */ + public function renderFromRequest(Request $request, string $format, string $area, array $extraOptions = []) { $options = []; if ('' !== $request->getBaseUrl()) { @@ -62,6 +69,8 @@ public function renderFromRequest(Request $request, string $format, $area, array } /** + * @param array $options + * * @throws \InvalidArgumentException If the area to dump is not valid */ public function render(string $format, string $area, array $options = []): string @@ -84,9 +93,14 @@ public function render(string $format, string $area, array $options = []): strin } } + /** + * @param array $options + * + * @return Server[]|Generator::UNDEFINED + */ private function getServersFromOptions(OpenApi $spec, array $options) { - if (array_key_exists('server_url', $options) && $options['server_url']) { + if (array_key_exists('server_url', $options)) { return [new Server(['url' => $options['server_url'], '_context' => new Context()])]; } @@ -94,7 +108,7 @@ private function getServersFromOptions(OpenApi $spec, array $options) return $spec->servers; } - if (array_key_exists('fallback_url', $options) && $options['fallback_url']) { + if (array_key_exists('fallback_url', $options)) { return [new Server(['url' => $options['fallback_url'], '_context' => new Context()])]; } diff --git a/src/RouteDescriber/FosRestDescriber.php b/src/RouteDescriber/FosRestDescriber.php index e7bbeeaa9..3ee28847d 100644 --- a/src/RouteDescriber/FosRestDescriber.php +++ b/src/RouteDescriber/FosRestDescriber.php @@ -12,6 +12,7 @@ namespace Nelmio\ApiDocBundle\RouteDescriber; use Doctrine\Common\Annotations\Reader; +use FOS\RestBundle\Controller\Annotations\AbstractScalarParam; use FOS\RestBundle\Controller\Annotations\QueryParam; use FOS\RestBundle\Controller\Annotations\RequestParam; use Nelmio\ApiDocBundle\OpenApiPhp\Util; @@ -27,19 +28,21 @@ final class FosRestDescriber implements RouteDescriberInterface { use RouteDescriberTrait; - /** @var Reader|null */ - private $annotationReader; + private ?Reader $annotationReader; /** @var string[] */ - private $mediaTypes; + private array $mediaTypes; + /** + * @param string[] $mediaTypes + */ public function __construct(?Reader $annotationReader, array $mediaTypes) { $this->annotationReader = $annotationReader; $this->mediaTypes = $mediaTypes; } - public function describe(OA\OpenApi $api, Route $route, \ReflectionMethod $reflectionMethod) + public function describe(OA\OpenApi $api, Route $route, \ReflectionMethod $reflectionMethod): void { $annotations = null !== $this->annotationReader ? $this->annotationReader->getMethodAnnotations($reflectionMethod) @@ -91,7 +94,10 @@ public function describe(OA\OpenApi $api, Route $route, \ReflectionMethod $refle } } - private function getPattern($requirements) + /** + * @param mixed $requirements Value to retrieve a pattern from + */ + private function getPattern($requirements): ?string { if (is_array($requirements) && isset($requirements['rule'])) { return (string) $requirements['rule']; @@ -108,7 +114,10 @@ private function getPattern($requirements) return null; } - private function getFormat($requirements) + /** + * @param mixed $requirements Value to retrieve a format from + */ + private function getFormat($requirements): ?string { if ($requirements instanceof Constraint && !$requirements instanceof Regex) { if ($requirements instanceof DateTime) { @@ -132,7 +141,12 @@ private function getFormat($requirements) return null; } - private function getEnum($requirements) + /** + * @param mixed $requirements Value to retrieve an enum from + * + * @return mixed[]|null + */ + private function getEnum($requirements): ?array { if ($requirements instanceof Choice) { return $requirements->choices; @@ -178,7 +192,7 @@ private function getContentSchemaForType(OA\RequestBody $requestBody, string $ty ); } - private function describeCommonSchemaFromAnnotation(OA\Schema $schema, $annotation) + private function describeCommonSchemaFromAnnotation(OA\Schema $schema, AbstractScalarParam $annotation): void { $schema->default = $annotation->getDefault(); @@ -208,7 +222,11 @@ private function describeCommonSchemaFromAnnotation(OA\Schema $schema, $annotati } /** - * @return OA\AbstractAnnotation[] + * @template T of object + * + * @param class-string $className + * + * @return T[] */ private function getAttributesAsAnnotation(\ReflectionMethod $reflection, string $className): array { diff --git a/src/RouteDescriber/PhpDocDescriber.php b/src/RouteDescriber/PhpDocDescriber.php index fcde8d6e5..566568cbd 100644 --- a/src/RouteDescriber/PhpDocDescriber.php +++ b/src/RouteDescriber/PhpDocDescriber.php @@ -21,7 +21,7 @@ final class PhpDocDescriber implements RouteDescriberInterface { use RouteDescriberTrait; - private $docBlockFactory; + private ?DocBlockFactoryInterface $docBlockFactory; public function __construct(?DocBlockFactoryInterface $docBlockFactory = null) { @@ -31,7 +31,7 @@ public function __construct(?DocBlockFactoryInterface $docBlockFactory = null) $this->docBlockFactory = $docBlockFactory; } - public function describe(OA\OpenApi $api, Route $route, \ReflectionMethod $reflectionMethod) + public function describe(OA\OpenApi $api, Route $route, \ReflectionMethod $reflectionMethod): void { $classDocBlock = null; $docBlock = null; diff --git a/src/RouteDescriber/RouteArgumentDescriber/SymfonyMapQueryParameterDescriber.php b/src/RouteDescriber/RouteArgumentDescriber/SymfonyMapQueryParameterDescriber.php index 137bca56c..6cc5ea9f8 100644 --- a/src/RouteDescriber/RouteArgumentDescriber/SymfonyMapQueryParameterDescriber.php +++ b/src/RouteDescriber/RouteArgumentDescriber/SymfonyMapQueryParameterDescriber.php @@ -72,6 +72,10 @@ public function describe(ArgumentMetadata $argumentMetadata, OA\Operation $opera } /** + * @param mixed[] $options + * + * @return array + * * @see https://www.php.net/manual/en/filter.filters.validate.php */ private function describeValidateFilter(?int $filter, int $flags, array $options): array @@ -98,11 +102,11 @@ private function describeValidateFilter(?int $filter, int $flags, array $options if (FILTER_VALIDATE_INT === $filter) { $props = []; - if ($options['min_range'] ?? false) { + if (array_key_exists('min_range', $options)) { $props['minimum'] = $options['min_range']; } - if ($options['max_range'] ?? false) { + if (array_key_exists('max_range', $options)) { $props['maximum'] = $options['max_range']; } diff --git a/src/RouteDescriber/RouteDescriberInterface.php b/src/RouteDescriber/RouteDescriberInterface.php index 9067c60df..19ad2e036 100644 --- a/src/RouteDescriber/RouteDescriberInterface.php +++ b/src/RouteDescriber/RouteDescriberInterface.php @@ -16,5 +16,8 @@ interface RouteDescriberInterface { + /** + * @return void + */ public function describe(OpenApi $api, Route $route, \ReflectionMethod $reflectionMethod); } diff --git a/src/RouteDescriber/RouteMetadataDescriber.php b/src/RouteDescriber/RouteMetadataDescriber.php index 96bb6e278..b881bead7 100644 --- a/src/RouteDescriber/RouteMetadataDescriber.php +++ b/src/RouteDescriber/RouteMetadataDescriber.php @@ -25,7 +25,7 @@ final class RouteMetadataDescriber implements RouteDescriberInterface private const ALPHANUM_EXPANDED_REGEX = '/^[-a-zA-Z0-9_]*$/'; - public function describe(OA\OpenApi $api, Route $route, \ReflectionMethod $reflectionMethod) + public function describe(OA\OpenApi $api, Route $route, \ReflectionMethod $reflectionMethod): void { foreach ($this->getOperations($api, $route) as $operation) { $requirements = $route->getRequirements(); diff --git a/src/Routing/FilteredRouteCollectionBuilder.php b/src/Routing/FilteredRouteCollectionBuilder.php index eef2dc4a9..b634f8702 100644 --- a/src/Routing/FilteredRouteCollectionBuilder.php +++ b/src/Routing/FilteredRouteCollectionBuilder.php @@ -21,18 +21,20 @@ final class FilteredRouteCollectionBuilder { - /** @var Reader|null */ - private $annotationReader; + private ?Reader $annotationReader; - /** @var ControllerReflector */ - private $controllerReflector; + private ControllerReflector $controllerReflector; - /** @var string */ - private $area; + private string $area; - /** @var array */ - private $options; + /** + * @var array + */ + private array $options; + /** + * @param array $options + */ public function __construct( ?Reader $annotationReader, ControllerReflector $controllerReflector, diff --git a/src/Util/ControllerReflector.php b/src/Util/ControllerReflector.php index b63ea72c1..c986bcfdf 100644 --- a/src/Util/ControllerReflector.php +++ b/src/Util/ControllerReflector.php @@ -18,9 +18,11 @@ */ class ControllerReflector { - private $container; - - private $controllers = []; + private ContainerInterface $container; + /** + * @var array + */ + private array $controllers = []; public function __construct(ContainerInterface $container) { @@ -29,6 +31,8 @@ public function __construct(ContainerInterface $container) /** * Returns the ReflectionMethod for the given controller string. + * + * @param string|array{string, string}|null $controller */ public function getReflectionMethod($controller): ?\ReflectionMethod { @@ -54,6 +58,9 @@ private function getReflectionMethodByClassNameAndMethodName(string $class, stri return null; } + /** + * @return array{string, string}|null + */ private function getClassAndMethod(string $controller): ?array { if (isset($this->controllers[$controller])) { diff --git a/src/Util/SetsContextTrait.php b/src/Util/SetsContextTrait.php index 1a9166db7..27e51f307 100644 --- a/src/Util/SetsContextTrait.php +++ b/src/Util/SetsContextTrait.php @@ -25,6 +25,9 @@ private function setContext(?Context $context): void \OpenApi\Generator::$context = $context; } + /** + * @param \ReflectionClass|\ReflectionProperty|\ReflectionMethod $reflection + */ private function setContextFromReflection(Context $parentContext, $reflection): void { // In order to have nicer errors diff --git a/tests/ApiDocGeneratorTest.php b/tests/ApiDocGeneratorTest.php index 02b0bd3fb..223e72bb5 100644 --- a/tests/ApiDocGeneratorTest.php +++ b/tests/ApiDocGeneratorTest.php @@ -19,7 +19,7 @@ class ApiDocGeneratorTest extends TestCase { - public function testCache() + public function testCache(): void { $adapter = new ArrayAdapter(); $generator = new ApiDocGenerator([new DefaultDescriber()], [], $adapter, null, new Generator()); @@ -27,7 +27,7 @@ public function testCache() self::assertEquals(json_encode($generator->generate()), json_encode($adapter->getItem('openapi_doc')->get())); } - public function testCacheWithCustomId() + public function testCacheWithCustomId(): void { $adapter = new ArrayAdapter(); $generator = new ApiDocGenerator([new DefaultDescriber()], [], $adapter, 'custom_id', new Generator()); diff --git a/tests/Command/DumpCommandTest.php b/tests/Command/DumpCommandTest.php index 62d5ced0b..5c4374513 100644 --- a/tests/Command/DumpCommandTest.php +++ b/tests/Command/DumpCommandTest.php @@ -18,8 +18,12 @@ class DumpCommandTest extends WebTestCase { - /** @dataProvider provideJsonMode */ - public function testJson(array $jsonOptions, int $expectedJsonFlags) + /** + * @dataProvider provideJsonMode + * + * @param array $jsonOptions + */ + public function testJson(array $jsonOptions, int $expectedJsonFlags): void { $output = $this->executeDumpCommand($jsonOptions + [ '--area' => 'test', @@ -30,15 +34,14 @@ public function testJson(array $jsonOptions, int $expectedJsonFlags) ); } - public static function provideJsonMode(): iterable + public static function provideJsonMode(): \Generator { - return [ - 'pretty print' => [[], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES], - 'one line' => [['--no-pretty'], 0 | JSON_UNESCAPED_SLASHES], - ]; + yield 'pretty print' => [[], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES]; + + yield 'one line' => [['--no-pretty'], 0 | JSON_UNESCAPED_SLASHES]; } - public function testYaml() + public function testYaml(): void { $output = $this->executeDumpCommand([ '--format' => 'yaml', @@ -52,8 +55,12 @@ public function testYaml() self::assertStringContainsString($expectedYaml, $output); } - /** @dataProvider provideAssetsMode */ - public function testHtml($htmlConfig, string $expectedHtml) + /** + * @dataProvider provideAssetsMode + * + * @param mixed $htmlConfig the value of the --html-config option + */ + public function testHtml($htmlConfig, string $expectedHtml): void { $output = $this->executeDumpCommand([ '--area' => 'test', @@ -64,43 +71,49 @@ public function testHtml($htmlConfig, string $expectedHtml) self::assertStringContainsString($expectedHtml, $output); } - public static function provideAssetsMode(): iterable + public static function provideAssetsMode(): \Generator { - return [ - 'default mode is cdn' => [ - null, - 'https://cdn.jsdelivr.net', - ], - 'invalid mode fallbacks to cdn' => [ - 'invalid', - 'https://cdn.jsdelivr.net', - ], - 'select cdn mode' => [ - ['assets_mode' => AssetsMode::CDN], - 'https://cdn.jsdelivr.net', - ], - 'select offline mode' => [ - ['assets_mode' => AssetsMode::OFFLINE], - '', - ], - 'external css' => [ - AssetsMode::BUNDLE, - 'https://cdn.com/my.css', - '', - ], + yield 'bundled css' => [ + AssetsMode::BUNDLE, + 'style.css', + '', + ]; + yield 'cdn css' => [ + AssetsMode::CDN, + 'style.css', + '', + ]; + yield 'offline css' => [ + AssetsMode::OFFLINE, + 'style.css', + '', + ]; + yield 'external css' => [ + AssetsMode::BUNDLE, + 'https://cdn.com/my.css', + '', ]; } - private static function provideJs($cdnDir, $resourceDir): array + public static function provideJs(): \Generator { - return [ - 'bundled js' => [ - AssetsMode::BUNDLE, - 'init-swagger-ui.js', - '', - ], - 'cdn js' => [ - AssetsMode::CDN, - 'init-swagger-ui.js', - '', - ], - 'offline js' => [ - AssetsMode::OFFLINE, - 'init-swagger-ui.js', - '', - ], - 'external js' => [ - AssetsMode::BUNDLE, - 'https://cdn.com/my.js', - '', - ], + yield 'bundled js' => [ + AssetsMode::BUNDLE, + 'init-swagger-ui.js', + '', + ]; + yield 'cdn js' => [ + AssetsMode::CDN, + 'init-swagger-ui.js', + '', + ]; + yield 'offline js' => [ + AssetsMode::OFFLINE, + 'init-swagger-ui.js', + '', + ]; + yield 'external js' => [ + AssetsMode::BUNDLE, + 'https://cdn.com/my.js', + '', ]; } - private static function provideImage($cdnDir): array + public static function provideImage(): \Generator { - return [ - 'bundled image' => [ - AssetsMode::BUNDLE, - 'logo.png', - '/bundles/nelmioapidoc/logo.png', - ], - 'cdn image' => [ - AssetsMode::CDN, - 'logo.png', - $cdnDir.'/logo.png', - ], - 'offline image fallbacks to cdn' => [ - AssetsMode::OFFLINE, - 'logo.png', - $cdnDir.'/logo.png', - ], + yield 'bundled image' => [ + AssetsMode::BUNDLE, + 'logo.png', + '/bundles/nelmioapidoc/logo.png', + ]; + yield 'cdn image' => [ + AssetsMode::CDN, + 'logo.png', + self::CDN_DIR.'/logo.png', + ]; + yield 'offline image fallbacks to cdn' => [ + AssetsMode::OFFLINE, + 'logo.png', + self::CDN_DIR.'/logo.png', ]; } } diff --git a/tests/Render/RenderOpenApiTest.php b/tests/Render/RenderOpenApiTest.php index 5e259abf7..b9bcda55c 100644 --- a/tests/Render/RenderOpenApiTest.php +++ b/tests/Render/RenderOpenApiTest.php @@ -19,36 +19,36 @@ class RenderOpenApiTest extends TestCase { - private $area = 'irrelevant area'; - private $format = 'irrelevant format'; - private $hasArea = true; + private const AREA = 'irrelevant area'; + private const FORMAT = 'irrelevant format'; + private bool $hasArea = true; - public function testRender() + public function testRender(): void { $openApiRenderer = $this->createMock(OpenApiRenderer::class); - $openApiRenderer->method('getFormat')->willReturn($this->format); + $openApiRenderer->method('getFormat')->willReturn(self::FORMAT); $openApiRenderer->expects(self::once())->method('render'); $this->renderOpenApi($openApiRenderer); } - public function testUnknownFormat() + public function testUnknownFormat(): void { $availableOpenApiRenderers = []; - $this->expectExceptionObject(new \InvalidArgumentException(sprintf('Format "%s" is not supported.', $this->format))); + $this->expectExceptionObject(new \InvalidArgumentException(sprintf('Format "%s" is not supported.', self::FORMAT))); $this->renderOpenApi(...$availableOpenApiRenderers); } - public function testUnknownArea() + public function testUnknownArea(): void { $this->hasArea = false; - $this->expectExceptionObject(new \InvalidArgumentException(sprintf('Area "%s" is not supported.', $this->area))); + $this->expectExceptionObject(new \InvalidArgumentException(sprintf('Area "%s" is not supported.', self::AREA))); $this->renderOpenApi(); } - public function testNullFormat() + public function testNullFormat(): void { $openApiRenderer = $this->createMock(OpenApiRenderer::class); - $openApiRenderer->method('getFormat')->willReturn($this->format); + $openApiRenderer->method('getFormat')->willReturn(self::FORMAT); $openApiRenderer->expects(self::once())->method('render'); $availableOpenApiRenderers = [ @@ -58,18 +58,18 @@ public function testNullFormat() $this->renderOpenApi(...$availableOpenApiRenderers); } - private function renderOpenApi(...$openApiRenderer): void + private function renderOpenApi(?OpenApiRenderer ...$openApiRenderer): void { $spec = $this->createMock(OpenApi::class); $generator = new class($spec) { - private $spec; + private OpenApi $spec; - public function __construct($spec) + public function __construct(OpenApi $spec) { $this->spec = $spec; } - public function generate() + public function generate(): OpenApi { return $this->spec; } @@ -80,6 +80,6 @@ public function generate() $generatorLocator->method('get')->willReturn($generator); $renderOpenApi = new RenderOpenApi($generatorLocator, ...$openApiRenderer); - $renderOpenApi->render($this->format, $this->area, []); + $renderOpenApi->render(self::FORMAT, self::AREA, []); } } diff --git a/tests/RouteDescriber/FosRestDescriberTest.php b/tests/RouteDescriber/FosRestDescriberTest.php index 6770d6096..51da37a65 100644 --- a/tests/RouteDescriber/FosRestDescriberTest.php +++ b/tests/RouteDescriber/FosRestDescriberTest.php @@ -21,7 +21,7 @@ class FosRestDescriberTest extends TestCase { - public function testQueryParamWithChoiceConstraintIsAddedAsEnum() + public function testQueryParamWithChoiceConstraintIsAddedAsEnum(): void { $choices = ['foo', 'bar']; diff --git a/tests/RouteDescriber/RouteMetadataDescriberTest.php b/tests/RouteDescriber/RouteMetadataDescriberTest.php index 81e09a4c9..03027a557 100644 --- a/tests/RouteDescriber/RouteMetadataDescriberTest.php +++ b/tests/RouteDescriber/RouteMetadataDescriberTest.php @@ -20,14 +20,16 @@ class RouteMetadataDescriberTest extends TestCase { - public function testUndefinedCheck() + public function testUndefinedCheck(): void { + self::expectNotToPerformAssertions(); + $routeDescriber = new RouteMetadataDescriber(); - self::assertNull($routeDescriber->describe(new OpenApi(['_context' => new Context()]), new Route('foo'), new \ReflectionMethod(__CLASS__, 'testUndefinedCheck'))); + $routeDescriber->describe(new OpenApi(['_context' => new Context()]), new Route('foo'), new \ReflectionMethod(__CLASS__, 'testUndefinedCheck')); } - public function testRouteRequirementsWithPattern() + public function testRouteRequirementsWithPattern(): void { $api = new OpenApi([]); $routeDescriber = new RouteMetadataDescriber(); @@ -38,20 +40,20 @@ public function testRouteRequirementsWithPattern() new \ReflectionMethod(__CLASS__, 'testRouteRequirementsWithPattern') ); - self::assertEquals('/index/{bar}/{foo}.html', $api->paths[0]->path); + self::assertSame('/index/{bar}/{foo}.html', $api->paths[0]->path); $getPathParameter = $api->paths[0]->get->parameters[1]; if ('foo' === $getPathParameter->name) { - self::assertEquals('path', $getPathParameter->in); - self::assertEquals('foo', $getPathParameter->name); - self::assertEquals('string', $getPathParameter->schema->type); - self::assertEquals('[0-9]|[a-z]', $getPathParameter->schema->pattern); + self::assertSame('path', $getPathParameter->in); + self::assertSame('foo', $getPathParameter->name); + self::assertSame('string', $getPathParameter->schema->type); + self::assertSame('[0-9]|[a-z]', $getPathParameter->schema->pattern); } } /** * @dataProvider provideEnumPattern */ - public function testSimpleOrRequirementsAreHandledAsEnums($req) + public function testSimpleOrRequirementsAreHandledAsEnums(string $req): void { $api = new OpenApi([]); $routeDescriber = new RouteMetadataDescriber(); @@ -62,19 +64,19 @@ public function testSimpleOrRequirementsAreHandledAsEnums($req) new \ReflectionMethod(__CLASS__, 'testSimpleOrRequirementsAreHandledAsEnums') ); - self::assertEquals('/index/{bar}/{foo}.html', $api->paths[0]->path); + self::assertSame('/index/{bar}/{foo}.html', $api->paths[0]->path); $getPathParameter = $api->paths[0]->get->parameters[1]; - self::assertEquals('path', $getPathParameter->in); - self::assertEquals('foo', $getPathParameter->name); - self::assertEquals('string', $getPathParameter->schema->type); - self::assertEquals(explode('|', $req), $getPathParameter->schema->enum); - self::assertEquals($req, $getPathParameter->schema->pattern); + self::assertSame('path', $getPathParameter->in); + self::assertSame('foo', $getPathParameter->name); + self::assertSame('string', $getPathParameter->schema->type); + self::assertSame(explode('|', $req), $getPathParameter->schema->enum); + self::assertSame($req, $getPathParameter->schema->pattern); } /** * @dataProvider provideInvalidEnumPattern */ - public function testNonEnumPatterns($pattern) + public function testNonEnumPatterns(string $pattern): void { $api = new OpenApi([]); $routeDescriber = new RouteMetadataDescriber(); @@ -86,11 +88,11 @@ public function testNonEnumPatterns($pattern) ); $getPathParameter = $api->paths[0]->get->parameters[0]; - self::assertEquals($pattern, $getPathParameter->schema->pattern); - self::assertEquals(Generator::UNDEFINED, $getPathParameter->schema->enum); + self::assertSame($pattern, $getPathParameter->schema->pattern); + self::assertSame(Generator::UNDEFINED, $getPathParameter->schema->enum); } - public static function provideEnumPattern(): iterable + public static function provideEnumPattern(): \Generator { yield ['1|2|3']; yield ['srf|rtr|rsi']; @@ -98,7 +100,7 @@ public static function provideEnumPattern(): iterable yield ['srf-1|srf-2']; } - public static function provideInvalidEnumPattern(): iterable + public static function provideInvalidEnumPattern(): \Generator { yield ['|']; yield ['|a']; diff --git a/tests/Routing/FilteredRouteCollectionBuilderTest.php b/tests/Routing/FilteredRouteCollectionBuilderTest.php index 9bc1d102b..fa30bafde 100644 --- a/tests/Routing/FilteredRouteCollectionBuilderTest.php +++ b/tests/Routing/FilteredRouteCollectionBuilderTest.php @@ -40,7 +40,7 @@ protected function setUp(): void $this->doctrineAnnotations = class_exists(AnnotationReader::class) ? new AnnotationReader() : null; } - public function testFilter() + public function testFilter(): void { $options = [ 'path_patterns' => [ @@ -74,7 +74,7 @@ public function testFilter() * * @expectedDeprecation Passing an indexed array with a collection of path patterns as argument 1 for `Nelmio\ApiDocBundle\Routing\FilteredRouteCollectionBuilder::__construct()` is deprecated since 3.2.0, expected structure is an array containing parameterized options. */ - public function testFilterWithDeprecatedArgument() + public function testFilterWithDeprecatedArgument(): void { $pathPattern = [ '^/api/foo', @@ -99,8 +99,10 @@ public function testFilterWithDeprecatedArgument() /** * @dataProvider getInvalidOptions + * + * @param array $options */ - public function testFilterWithInvalidOption(array $options) + public function testFilterWithInvalidOption(array $options): void { $this->expectException(InvalidArgumentException::class); @@ -112,27 +114,28 @@ public function testFilterWithInvalidOption(array $options) ); } - public static function getInvalidOptions(): iterable + public static function getInvalidOptions(): \Generator { - return [ - [['invalid_option' => null]], - [['invalid_option' => 42]], - [['invalid_option' => []]], - [['path_patterns' => [22]]], - [['path_patterns' => [null]]], - [['path_patterns' => [new \stdClass()]]], - [['path_patterns' => ['^/foo$', 1]]], - [['with_annotation' => ['an array']]], - [['path_patterns' => 'a string']], - [['path_patterns' => 11]], - [['name_patterns' => 22]], - [['name_patterns' => 'a string']], - [['name_patterns' => [22]]], - [['name_patterns' => [null]]], - [['name_patterns' => [new \stdClass()]]], - ]; + yield [['invalid_option' => null]]; + yield [['invalid_option' => 42]]; + yield [['invalid_option' => []]]; + yield [['path_patterns' => [22]]]; + yield [['path_patterns' => [null]]]; + yield [['path_patterns' => [new \stdClass()]]]; + yield [['path_patterns' => ['^/foo$', 1]]]; + yield [['with_annotation' => ['an array']]]; + yield [['path_patterns' => 'a string']]; + yield [['path_patterns' => 11]]; + yield [['name_patterns' => 22]]; + yield [['name_patterns' => 'a string']]; + yield [['name_patterns' => [22]]]; + yield [['name_patterns' => [null]]]; + yield [['name_patterns' => [new \stdClass()]]]; } + /** + * @return array + */ private function getRoutes(): array { return [ @@ -151,8 +154,10 @@ private function getRoutes(): array /** * @dataProvider getMatchingRoutes + * + * @param array $options */ - public function testMatchingRoutes(string $name, Route $route, array $options = []) + public function testMatchingRoutes(string $name, Route $route, array $options = []): void { $routes = new RouteCollection(); $routes->add($name, $route); @@ -168,7 +173,7 @@ public function testMatchingRoutes(string $name, Route $route, array $options = self::assertCount(1, $filteredRoutes); } - public static function getMatchingRoutes(): iterable + public static function getMatchingRoutes(): \Generator { yield from [ ['r1', new Route('/api/bar/action1')], @@ -188,8 +193,10 @@ public static function getMatchingRoutes(): iterable * @group test * * @dataProvider getMatchingRoutesWithAnnotation + * + * @param array $options */ - public function testMatchingRoutesWithAnnotation(string $name, Route $route, array $options = []) + public function testMatchingRoutesWithAnnotation(string $name, Route $route, array $options = []): void { $routes = new RouteCollection(); $routes->add($name, $route); @@ -220,7 +227,7 @@ public function testMatchingRoutesWithAnnotation(string $name, Route $route, arr self::assertCount(1, $filteredRoutes); } - public static function getMatchingRoutesWithAnnotation(): iterable + public static function getMatchingRoutesWithAnnotation(): \Generator { yield from [ 'with annotation only' => [ @@ -253,8 +260,10 @@ public static function getMatchingRoutesWithAnnotation(): iterable /** * @dataProvider getNonMatchingRoutes + * + * @param array $options */ - public function testNonMatchingRoutes(string $name, Route $route, array $options = []) + public function testNonMatchingRoutes(string $name, Route $route, array $options = []): void { $routes = new RouteCollection(); $routes->add($name, $route); @@ -270,16 +279,14 @@ public function testNonMatchingRoutes(string $name, Route $route, array $options self::assertCount(0, $filteredRoutes); } - public static function getNonMatchingRoutes(): iterable + public static function getNonMatchingRoutes(): \Generator { - return [ - ['r1', new Route('/api/bar/action1'), ['path_patterns' => ['^/apis']]], - ['r2', new Route('/api/foo/action1'), ['path_patterns' => ['^/apis', 'i/foo/b', 'n1/$'], 'name_patterns' => ['r2']]], - ['r3_matching_path_and_non_matching_host', new Route('/api/foo/action2'), ['path_patterns' => ['^/api/foo/action2$'], 'host_patterns' => ['^api\.']]], - ['r4_matching_path_and_non_matching_host', new Route('/api/bar/action1', [], [], [], 'www.example.com'), ['path_patterns' => ['^/api/'], 'host_patterns' => ['^api\.']]], - ['r5_non_matching_path_and_matching_host', new Route('/admin/bar/action1', [], [], [], 'api.example.com'), ['path_patterns' => ['^/api/'], 'host_patterns' => ['^api\.']]], - ['r6_non_matching_path_and_non_matching_host', new Route('/admin/bar/action1', [], [], [], 'www.example.com'), ['path_patterns' => ['^/api/'], 'host_patterns' => ['^api\.ex']]], - ]; + yield ['r1', new Route('/api/bar/action1'), ['path_patterns' => ['^/apis']]]; + yield ['r2', new Route('/api/foo/action1'), ['path_patterns' => ['^/apis', 'i/foo/b', 'n1/$'], 'name_patterns' => ['r2']]]; + yield ['r3_matching_path_and_non_matching_host', new Route('/api/foo/action2'), ['path_patterns' => ['^/api/foo/action2$'], 'host_patterns' => ['^api\.']]]; + yield ['r4_matching_path_and_non_matching_host', new Route('/api/bar/action1', [], [], [], 'www.example.com'), ['path_patterns' => ['^/api/'], 'host_patterns' => ['^api\.']]]; + yield ['r5_non_matching_path_and_matching_host', new Route('/admin/bar/action1', [], [], [], 'api.example.com'), ['path_patterns' => ['^/api/'], 'host_patterns' => ['^api\.']]]; + yield ['r6_non_matching_path_and_non_matching_host', new Route('/admin/bar/action1', [], [], [], 'www.example.com'), ['path_patterns' => ['^/api/'], 'host_patterns' => ['^api\.ex']]]; } /** @@ -323,33 +330,28 @@ public function testRoutesWithDisabledDefaultRoutes( self::assertCount($expectedRoutesCount, $filteredRoutes); } - /** - * @return array - */ - public static function getRoutesWithDisabledDefaultRoutes(): iterable + public static function getRoutesWithDisabledDefaultRoutes(): \Generator { - return [ - 'non matching route without Annotation' => [ - 'r10', - new Route('/api/foo', ['_controller' => 'ApiController::fooAction']), - [], - ['disable_default_routes' => true], - 0, - ], - 'matching route with Nelmio Annotation' => [ - 'r10', - new Route('/api/foo', ['_controller' => 'ApiController::fooAction']), - [new Operation(['_context' => new Context()])], - ['disable_default_routes' => true], - 1, - ], - 'matching route with Swagger Annotation' => [ - 'r10', - new Route('/api/foo', ['_controller' => 'ApiController::fooAction']), - [new Parameter(['_context' => new Context()])], - ['disable_default_routes' => true], - 1, - ], + yield 'non matching route without Annotation' => [ + 'r10', + new Route('/api/foo', ['_controller' => 'ApiController::fooAction']), + [], + ['disable_default_routes' => true], + 0, + ]; + yield 'matching route with Nelmio Annotation' => [ + 'r10', + new Route('/api/foo', ['_controller' => 'ApiController::fooAction']), + [new Operation(['_context' => new Context()])], + ['disable_default_routes' => true], + 1, + ]; + yield 'matching route with Swagger Annotation' => [ + 'r10', + new Route('/api/foo', ['_controller' => 'ApiController::fooAction']), + [new Parameter(['_context' => new Context()])], + ['disable_default_routes' => true], + 1, ]; } diff --git a/tests/SwaggerPhp/UtilTest.php b/tests/SwaggerPhp/UtilTest.php index ff550f7d7..f45699059 100644 --- a/tests/SwaggerPhp/UtilTest.php +++ b/tests/SwaggerPhp/UtilTest.php @@ -41,10 +41,9 @@ */ class UtilTest extends TestCase { - public $rootContext; + private Context $rootContext; - /** @var OA\OpenApi */ - public $rootAnnotation; + private OA\OpenApi $rootAnnotation; public function setUp(): void { @@ -66,14 +65,14 @@ public function tearDown(): void restore_error_handler(); } - public function testCreateContextSetsParentContext() + public function testCreateContextSetsParentContext(): void { $context = Util::createContext([], $this->rootContext); $this->assertContextIsConnectedToRootContext($context); } - public function testCreateContextWithProperties() + public function testCreateContextWithProperties(): void { $context = Util::createContext(['testing' => 'trait']); @@ -81,21 +80,21 @@ public function testCreateContextWithProperties() self::assertSame('trait', $context->testing); } - public function testCreateChild() + public function testCreateChild(): void { $info = Util::createChild($this->rootAnnotation, OA\Info::class); self::assertInstanceOf(OA\Info::class, $info); } - public function testCreateChildHasContext() + public function testCreateChildHasContext(): void { $info = Util::createChild($this->rootAnnotation, OA\Info::class); self::assertInstanceOf(Context::class, $info->_context); } - public function testCreateChildHasNestedContext() + public function testCreateChildHasNestedContext(): void { $path = Util::createChild($this->rootAnnotation, OA\PathItem::class); $this->assertIsNested($this->rootAnnotation, $path); @@ -109,7 +108,7 @@ public function testCreateChildHasNestedContext() $this->assertIsConnectedToRootContext($schema); } - public function testCreateChildWithEmptyProperties() + public function testCreateChildWithEmptyProperties(): void { $properties = []; /** @var OA\Info $info */ @@ -125,7 +124,7 @@ public function testCreateChildWithEmptyProperties() $this->assertIsConnectedToRootContext($info); } - public function testCreateChildWithProperties() + public function testCreateChildWithProperties(): void { $properties = ['title' => 'testing', 'version' => '999', 'x' => new \stdClass()]; /** @var OA\Info $info */ @@ -139,7 +138,7 @@ public function testCreateChildWithProperties() $this->assertIsConnectedToRootContext($info); } - public function testCreateCollectionItemAddsCreatedItemToCollection() + public function testCreateCollectionItemAddsCreatedItemToCollection(): void { $collection = 'paths'; $class = OA\PathItem::class; @@ -171,7 +170,7 @@ public function testCreateCollectionItemAddsCreatedItemToCollection() $this->assertIsConnectedToRootContext($this->rootAnnotation->components->{$collection}[$d1]); } - public function testCreateCollectionItemDoesNotAddToUnknownProperty() + public function testCreateCollectionItemDoesNotAddToUnknownProperty(): void { $collection = 'foobars'; $class = OA\Info::class; @@ -185,7 +184,7 @@ public function testCreateCollectionItemDoesNotAddToUnknownProperty() self::assertNull($this->rootAnnotation->{$collection}); /* @phpstan-ignore-line */ } - public function testSearchCollectionItem() + public function testSearchCollectionItem(): void { $item1 = new \stdClass(); $item1->prop1 = 'item 1 prop 1'; @@ -222,8 +221,11 @@ public function testSearchCollectionItem() /** * @dataProvider provideIndexedCollectionData + * + * @param array $setup + * @param array $asserts */ - public function testSearchIndexedCollectionItem($setup, $asserts) + public function testSearchIndexedCollectionItem(array $setup, array $asserts): void { foreach ($asserts as $collection => $items) { foreach ($items as $assert) { @@ -253,8 +255,11 @@ public function testSearchIndexedCollectionItem($setup, $asserts) /** * @dataProvider provideIndexedCollectionData + * + * @param array $setup + * @param array $asserts */ - public function testGetIndexedCollectionItem($setup, $asserts) + public function testGetIndexedCollectionItem(array $setup, array $asserts): void { $parent = new $setup['class'](array_merge( $this->getSetupPropertiesWithoutClass($setup), @@ -265,6 +270,7 @@ public function testGetIndexedCollectionItem($setup, $asserts) foreach ($items as $assert) { $itemParent = !isset($assert['components']) ? $parent : $parent->components; + self::assertTrue(is_a($assert['class'], OA\AbstractAnnotation::class, true), sprintf('Invalid class %s', $assert['class'])); $child = Util::getIndexedCollectionItem( $itemParent, $assert['class'], @@ -291,9 +297,9 @@ public function testGetIndexedCollectionItem($setup, $asserts) } } - public static function provideIndexedCollectionData(): iterable + public static function provideIndexedCollectionData(): \Generator { - return [[ + yield [ 'setup' => [ 'class' => OA\OpenApi::class, 'paths' => [ @@ -374,13 +380,16 @@ public static function provideIndexedCollectionData(): iterable ], ], ], - ]]; + ]; } /** * @dataProvider provideChildData + * + * @param array $setup + * @param array $asserts */ - public function testGetChild($setup, $asserts) + public function testGetChild(array $setup, array $asserts): void { $parent = new $setup['class'](array_merge( $this->getSetupPropertiesWithoutClass($setup), @@ -391,6 +400,7 @@ public function testGetChild($setup, $asserts) if (\array_key_exists('exceptionMessage', $assert)) { $this->expectExceptionMessage($assert['exceptionMessage']); } + self::assertTrue(is_a($assert['class'], OA\AbstractAnnotation::class, true), sprintf('Invalid class %s', $assert['class'])); $child = Util::getChild($parent, $assert['class'], $assert['props']); self::assertInstanceOf($assert['class'], $child); @@ -404,9 +414,9 @@ public function testGetChild($setup, $asserts) } } - public static function provideChildData(): iterable + public static function provideChildData(): \Generator { - return [[ + yield [ 'setup' => [ 'class' => OA\PathItem::class, 'get' => self::createObj(OA\Get::class, []), @@ -431,7 +441,9 @@ public static function provideChildData(): iterable ], ], ], - ], [ + ]; + + yield [ 'setup' => [ 'class' => OA\Parameter::class, ], @@ -446,7 +458,9 @@ public static function provideChildData(): iterable ], ], ], - ], [ + ]; + + yield [ 'setup' => [ 'class' => OA\Parameter::class, ], @@ -460,10 +474,10 @@ public static function provideChildData(): iterable 'exceptionMessage' => 'Nesting Annotations is not supported.', ], ], - ]]; + ]; } - public function testGetOperationParameterReturnsExisting() + public function testGetOperationParameterReturnsExisting(): void { $name = 'operation name'; $in = 'operation in'; @@ -482,7 +496,7 @@ public function testGetOperationParameterReturnsExisting() self::assertSame($parameter, $actual); } - public function testGetOperationParameterCreatesWithNameAndIn() + public function testGetOperationParameterCreatesWithNameAndIn(): void { $name = 'operation name'; $in = 'operation in'; @@ -500,7 +514,7 @@ public function testGetOperationParameterCreatesWithNameAndIn() self::assertSame($in, $actual->in); } - public function testGetOperationReturnsExisting() + public function testGetOperationReturnsExisting(): void { $get = self::createObj(OA\Get::class, []); $path = self::createObj(OA\PathItem::class, ['get' => $get]); @@ -508,7 +522,7 @@ public function testGetOperationReturnsExisting() self::assertSame($get, Util::getOperation($path, 'get')); } - public function testGetOperationCreatesWithPath() + public function testGetOperationCreatesWithPath(): void { $pathStr = '/testing/get/path'; $path = self::createObj(OA\PathItem::class, ['path' => $pathStr]); @@ -518,7 +532,7 @@ public function testGetOperationCreatesWithPath() self::assertSame($pathStr, $get->path); } - public function testMergeWithEmptyArray() + public function testMergeWithEmptyArray(): void { $api = self::createObj(OA\OpenApi::class, ['_context' => new Context()]); $expected = json_encode($api); @@ -536,8 +550,12 @@ public function testMergeWithEmptyArray() /** * @dataProvider provideMergeData + * + * @param array $setup + * @param array|\ArrayObject $merge + * @param array $assert */ - public function testMerge($setup, $merge, $assert) + public function testMerge(array $setup, $merge, array $assert): void { $api = self::createObj(OA\OpenApi::class, $setup + ['_context' => new Context()]); @@ -548,7 +566,7 @@ public function testMerge($setup, $merge, $assert) self::assertEquals($assert, $actual); } - public static function provideMergeData(): array + public static function provideMergeData(): \Generator { $no = 'do not overwrite'; $yes = 'do overwrite'; @@ -565,7 +583,7 @@ public static function provideMergeData(): array 'paths' => [], ]; - return [[ + yield [ // simple child merge 'setup' => [ 'info' => self::createObj(OA\Info::class, ['version' => $no]), @@ -577,7 +595,9 @@ public static function provideMergeData(): array 'assert' => [ 'info' => ['title' => $yes, 'version' => $no], ] + $assertDefaults, - ], [ + ]; + + yield [ // Parse server url with variables, see https://github.com/nelmio/NelmioApiDocBundle/issues/1691 'setup' => $setupDefaults, 'merge' => [ @@ -596,7 +616,9 @@ public static function provideMergeData(): array ], ], ] + $assertDefaults, - ], [ + ]; + + yield [ // indexed collection merge 'setup' => [ 'components' => self::createObj(OA\Components::class, [ @@ -619,7 +641,9 @@ public static function provideMergeData(): array ], ], ] + $assertDefaults, - ], [ + ]; + + yield [ // collection merge 'setup' => [ 'tags' => [self::createObj(OA\Tag::class, ['name' => $no])], @@ -645,56 +669,56 @@ public static function provideMergeData(): array ['name' => $no, 'description' => $yes], ], ] + $assertDefaults, - ], - [ - // heavy nested merge array - 'setup' => $setupDefaults, - 'merge' => $merge = [ - 'servers' => [ - ['url' => 'http'], - ['url' => 'https'], - ], - 'paths' => [ - '/path/to/resource' => [ - 'get' => [ - 'responses' => [ - '200' => [ - '$ref' => '#/components/responses/default', - ], + ]; + + yield [ + // heavy nested merge array + 'setup' => $setupDefaults, + 'merge' => $merge = [ + 'servers' => [ + ['url' => 'http'], + ['url' => 'https'], + ], + 'paths' => [ + '/path/to/resource' => [ + 'get' => [ + 'responses' => [ + '200' => [ + '$ref' => '#/components/responses/default', ], - 'requestBody' => [ - 'description' => 'request foo', - 'content' => [ - 'foo-request' => [ - 'schema' => [ - 'type' => 'object', - 'required' => ['baz', 'bar'], - ], + ], + 'requestBody' => [ + 'description' => 'request foo', + 'content' => [ + 'foo-request' => [ + 'schema' => [ + 'type' => 'object', + 'required' => ['baz', 'bar'], ], ], ], ], ], ], - 'tags' => [ - ['name' => 'baz'], - ['name' => 'foo'], - ['name' => 'baz'], - ['name' => 'foo'], - ['name' => 'foo'], - ], - 'components' => [ - 'responses' => [ - 'default' => [ - 'description' => 'default response', - 'headers' => [ - 'foo-header' => [ - 'schema' => [ - 'type' => 'array', - 'items' => [ - 'type' => 'string', - 'enum' => ['foo', 'bar', 'baz'], - ], + ], + 'tags' => [ + ['name' => 'baz'], + ['name' => 'foo'], + ['name' => 'baz'], + ['name' => 'foo'], + ['name' => 'foo'], + ], + 'components' => [ + 'responses' => [ + 'default' => [ + 'description' => 'default response', + 'headers' => [ + 'foo-header' => [ + 'schema' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'string', + 'enum' => ['foo', 'bar', 'baz'], ], ], ], @@ -702,163 +726,176 @@ public static function provideMergeData(): array ], ], ], - 'assert' => array_merge( - $assertDefaults, - $merge, - ['tags' => \array_slice($merge['tags'], 0, 2, true)] - ), - ], [ - // heavy nested merge array object - 'setup' => $setupDefaults, - 'merge' => new \ArrayObject([ - 'servers' => [ - ['url' => 'http'], - ['url' => 'https'], - ], - 'paths' => [ - '/path/to/resource' => [ - 'get' => new \ArrayObject([ - 'responses' => [ - '200' => [ - '$ref' => '#/components/responses/default', - ], + ], + 'assert' => array_merge( + $assertDefaults, + $merge, + ['tags' => \array_slice($merge['tags'], 0, 2, true)] + ), + ]; + + yield [ + // heavy nested merge array object + 'setup' => $setupDefaults, + 'merge' => new \ArrayObject([ + 'servers' => [ + ['url' => 'http'], + ['url' => 'https'], + ], + 'paths' => [ + '/path/to/resource' => [ + 'get' => new \ArrayObject([ + 'responses' => [ + '200' => [ + '$ref' => '#/components/responses/default', ], - 'requestBody' => new \ArrayObject([ - 'description' => 'request foo', - 'content' => [ - 'foo-request' => [ - 'schema' => [ - 'required' => ['baz', 'bar'], - 'type' => 'object', - ], + ], + 'requestBody' => new \ArrayObject([ + 'description' => 'request foo', + 'content' => [ + 'foo-request' => [ + 'schema' => [ + 'required' => ['baz', 'bar'], + 'type' => 'object', ], ], - ]), + ], ]), - ], + ]), ], - 'tags' => new \ArrayObject([ - ['name' => 'baz'], - ['name' => 'foo'], - new \ArrayObject(['name' => 'baz']), - ['name' => 'foo'], - ['name' => 'foo'], - ]), - 'components' => new \ArrayObject([ - 'responses' => [ - 'default' => [ - 'description' => 'default response', - 'headers' => new \ArrayObject([ - 'foo-header' => new \ArrayObject([ - 'schema' => new \ArrayObject([ - 'type' => 'array', - 'items' => new \ArrayObject([ - 'type' => 'string', - 'enum' => ['foo', 'bar', 'baz'], - ]), + ], + 'tags' => new \ArrayObject([ + ['name' => 'baz'], + ['name' => 'foo'], + new \ArrayObject(['name' => 'baz']), + ['name' => 'foo'], + ['name' => 'foo'], + ]), + 'components' => new \ArrayObject([ + 'responses' => [ + 'default' => [ + 'description' => 'default response', + 'headers' => new \ArrayObject([ + 'foo-header' => new \ArrayObject([ + 'schema' => new \ArrayObject([ + 'type' => 'array', + 'items' => new \ArrayObject([ + 'type' => 'string', + 'enum' => ['foo', 'bar', 'baz'], ]), ]), ]), - ], + ]), ], - ]), - ]), - 'assert' => array_merge( - $assertDefaults, - $merge, - ['tags' => \array_slice($merge['tags'], 0, 2, true)] - ), - ], [ - // heavy nested merge swagger instance - 'setup' => $setupDefaults, - 'merge' => self::createObj(OA\OpenApi::class, [ - 'servers' => [ - self::createObj(OA\Server::class, ['url' => 'http']), - self::createObj(OA\Server::class, ['url' => 'https']), ], - 'paths' => [ - self::createObj(OA\PathItem::class, [ - 'path' => '/path/to/resource', - 'get' => self::createObj(OA\Get::class, [ - 'responses' => [ - self::createObj(OA\Response::class, [ - 'response' => '200', - 'ref' => '#/components/responses/default', - ]), - ], - 'requestBody' => self::createObj(OA\RequestBody::class, [ - 'description' => 'request foo', - 'content' => [ - self::createObj(OA\MediaType::class, [ - 'mediaType' => 'foo-request', - 'schema' => self::createObj(OA\Schema::class, [ - 'type' => 'object', - 'required' => ['baz', 'bar'], - ]), - ]), - ], + ]), + ]), + 'assert' => array_merge( + $assertDefaults, + $merge, + ['tags' => \array_slice($merge['tags'], 0, 2, true)] + ), + ]; + + yield [ + // heavy nested merge swagger instance + 'setup' => $setupDefaults, + 'merge' => self::createObj(OA\OpenApi::class, [ + 'servers' => [ + self::createObj(OA\Server::class, ['url' => 'http']), + self::createObj(OA\Server::class, ['url' => 'https']), + ], + 'paths' => [ + self::createObj(OA\PathItem::class, [ + 'path' => '/path/to/resource', + 'get' => self::createObj(OA\Get::class, [ + 'responses' => [ + self::createObj(OA\Response::class, [ + 'response' => '200', + 'ref' => '#/components/responses/default', ]), - ]), - ]), - ], - 'tags' => [ - self::createObj(OA\Tag::class, ['name' => 'baz']), - self::createObj(OA\Tag::class, ['name' => 'foo']), - self::createObj(OA\Tag::class, ['name' => 'baz']), - self::createObj(OA\Tag::class, ['name' => 'foo']), - self::createObj(OA\Tag::class, ['name' => 'foo']), - ], - 'components' => self::createObj(OA\Components::class, [ - 'responses' => [ - self::createObj(OA\Response::class, [ - 'response' => 'default', - 'description' => 'default response', - 'headers' => [ - self::createObj(OA\Header::class, [ - 'header' => 'foo-header', + ], + 'requestBody' => self::createObj(OA\RequestBody::class, [ + 'description' => 'request foo', + 'content' => [ + self::createObj(OA\MediaType::class, [ + 'mediaType' => 'foo-request', 'schema' => self::createObj(OA\Schema::class, [ - 'type' => 'array', - 'items' => self::createObj(OA\Items::class, [ - 'type' => 'string', - 'enum' => ['foo', 'bar', 'baz'], - ]), + 'type' => 'object', + 'required' => ['baz', 'bar'], ]), ]), ], ]), - ], + ]), ]), + ], + 'tags' => [ + self::createObj(OA\Tag::class, ['name' => 'baz']), + self::createObj(OA\Tag::class, ['name' => 'foo']), + self::createObj(OA\Tag::class, ['name' => 'baz']), + self::createObj(OA\Tag::class, ['name' => 'foo']), + self::createObj(OA\Tag::class, ['name' => 'foo']), + ], + 'components' => self::createObj(OA\Components::class, [ + 'responses' => [ + self::createObj(OA\Response::class, [ + 'response' => 'default', + 'description' => 'default response', + 'headers' => [ + self::createObj(OA\Header::class, [ + 'header' => 'foo-header', + 'schema' => self::createObj(OA\Schema::class, [ + 'type' => 'array', + 'items' => self::createObj(OA\Items::class, [ + 'type' => 'string', + 'enum' => ['foo', 'bar', 'baz'], + ]), + ]), + ]), + ], + ]), + ], ]), - 'assert' => array_merge( - $assertDefaults, - $merge, - ['tags' => \array_slice($merge['tags'], 0, 2, true)] - ), - ], ]; + ]), + 'assert' => array_merge( + $assertDefaults, + $merge, + ['tags' => \array_slice($merge['tags'], 0, 2, true)] + ), + ]; } - public function assertIsNested(OA\AbstractAnnotation $parent, OA\AbstractAnnotation $child) + public function assertIsNested(OA\AbstractAnnotation $parent, OA\AbstractAnnotation $child): void { self::assertTrue($child->_context->is('nested')); self::assertSame($parent, $child->_context->nested); } - public function assertIsConnectedToRootContext(OA\AbstractAnnotation $annotation) + public function assertIsConnectedToRootContext(OA\AbstractAnnotation $annotation): void { $this->assertContextIsConnectedToRootContext($annotation->_context); } - public function assertContextIsConnectedToRootContext(Context $context) + public function assertContextIsConnectedToRootContext(Context $context): void { self::assertSame($this->rootContext, $context->root()); } - private function getSetupPropertiesWithoutClass(array $setup) + /** + * @param array $setup + * + * @return array + */ + private function getSetupPropertiesWithoutClass(array $setup): array { return array_filter($setup, function ($k) {return 'class' !== $k; }, ARRAY_FILTER_USE_KEY); } - private function getNonDefaultProperties($object) + /** + * @return array + */ + private function getNonDefaultProperties(OA\AbstractAnnotation $object): array { $objectVars = \get_object_vars($object); $classVars = \get_class_vars(\get_class($object)); @@ -872,7 +909,11 @@ private function getNonDefaultProperties($object) return $props; } - private static function createObj(string $className, array $props = []) + /** + * @param class-string $className + * @param array $props + */ + private static function createObj(string $className, array $props = []): object { return new $className($props + ['_context' => new Context()]); }