From bfd1fa9333a09134860b61bcb60e83804ab47f96 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 1 Jul 2025 15:29:52 +0200 Subject: [PATCH 1/5] chore: introduce phpstan level 6 --- phpstan.neon.dist | 62 ++++++++++++++++++- .../Resolver/Factory/ResolverFactory.php | 2 +- src/Metadata/ApiProperty.php | 8 ++- .../Extractor/XmlPropertyExtractor.php | 4 +- .../Extractor/YamlPropertyExtractor.php | 4 +- src/Metadata/HttpOperation.php | 23 ++++++- src/Metadata/Metadata.php | 19 +++--- ...ationResourceMetadataCollectionFactory.php | 6 +- .../UriVariableTransformerInterface.php | 2 + src/OpenApi/Model/Header.php | 4 +- src/OpenApi/Model/Link.php | 6 +- src/OpenApi/Model/MediaType.php | 2 +- src/OpenApi/Model/Parameter.php | 6 +- src/OpenApi/Model/Schema.php | 14 ++--- src/State/Pagination/Pagination.php | 2 +- .../SerializerAwareProviderInterface.php | 3 + src/Symfony/Action/EntrypointAction.php | 2 +- src/Symfony/Routing/IriConverter.php | 4 +- src/Symfony/Routing/SkolemIriConverter.php | 8 +-- .../ValidationExceptionNormalizer.php | 3 + 20 files changed, 137 insertions(+), 47 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 78ddabb1e76..f5defe04968 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 5 + level: 6 paths: - src - tests @@ -112,3 +112,63 @@ parameters: # Allow extra assertions in tests: https://github.com/phpstan/phpstan-strict-rules/issues/130 - '#^Call to (static )?method PHPUnit\\Framework\\Assert::.* will always evaluate to true\.$#' + + # Level 6 + - + identifier: missingType.iterableValue + - + identifier: missingType.generics + - + identifier: missingType.property + paths: + - src/Doctrine/Common/Tests + - src/Doctrine/Odm/Tests + - src/Doctrine/Orm/Tests + - src/Elasticsearch/Tests + - src/Hydra/Tests + - src/JsonApi/Tests + - src/JsonSchema/Tests + - src/Metadata/Tests + - src/Serializer/Tests + - src/Symfony/Tests + - tests + - + identifier: missingType.parameter + paths: + - src/Doctrine/Common/Tests + - src/Doctrine/Odm/Tests + - src/Doctrine/Orm/Tests + - src/Elasticsearch/Tests + - src/GraphQl/Tests + - src/Hydra/Tests + - src/JsonApi/Tests + - src/JsonSchema/Tests + - src/Metadata/Tests + - src/OpenApi/Tests + - src/Serializer/Tests + - src/Symfony/Bundle/Test + - src/Symfony/Tests + - tests + - src # TODO + - + identifier: missingType.return + paths: + - src/Doctrine/Common/Tests + - src/Doctrine/Odm/Tests + - src/Doctrine/Orm/Tests + - src/Elasticsearch/Tests + - src/GraphQl/Tests + - src/Hydra/Tests + - src/JsonApi/Tests + - src/JsonSchema/Tests + - src/Metadata/Tests + - src/OpenApi/Tests + - src/Serializer/Tests + - src/Symfony/Tests + - tests + - + identifier: argument.templateType + paths: + - src/Symfony/Bundle/Test + - tests + - src # TODO diff --git a/src/GraphQl/Resolver/Factory/ResolverFactory.php b/src/GraphQl/Resolver/Factory/ResolverFactory.php index 81c5d9cea3d..8d129314ca3 100644 --- a/src/GraphQl/Resolver/Factory/ResolverFactory.php +++ b/src/GraphQl/Resolver/Factory/ResolverFactory.php @@ -87,7 +87,7 @@ public function __invoke(?string $resourceClass = null, ?string $rootClass = nul }; } - private function resolve(?array $source, array $args, ResolveInfo $info, ?string $rootClass = null, ?Operation $operation = null, mixed $body = null) + private function resolve(?array $source, array $args, ResolveInfo $info, ?string $rootClass = null, ?Operation $operation = null, mixed $body = null): mixed { // Handles relay nodes if (!$operation) { diff --git a/src/Metadata/ApiProperty.php b/src/Metadata/ApiProperty.php index 4feca9647ba..1c99d03eec1 100644 --- a/src/Metadata/ApiProperty.php +++ b/src/Metadata/ApiProperty.php @@ -345,12 +345,12 @@ public function withIdentifier(bool $identifier): static return $self; } - public function getDefault() + public function getDefault(): mixed { return $this->default; } - public function withDefault($default): static + public function withDefault(mixed $default): static { $self = clone $this; $self->default = $default; @@ -587,6 +587,8 @@ public function withExtraProperties(array $extraProperties = []): static /** * Gets IRI of this property. + * + * @return string[] */ public function getIris() { @@ -608,6 +610,8 @@ public function withIris(string|array $iris): static /** * Whether to generate a skolem iri on anonymous resources. + * + * @return bool */ public function getGenId() { diff --git a/src/Metadata/Extractor/XmlPropertyExtractor.php b/src/Metadata/Extractor/XmlPropertyExtractor.php index 8544a8aaf89..d1d9a13b755 100644 --- a/src/Metadata/Extractor/XmlPropertyExtractor.php +++ b/src/Metadata/Extractor/XmlPropertyExtractor.php @@ -110,10 +110,10 @@ private function buildValues(\SimpleXMLElement $resource): array return $data; } - private function buildArrayValue(?\SimpleXMLElement $resource, string $key, mixed $default = null) + private function buildArrayValue(?\SimpleXMLElement $resource, string $key): ?array { if (!isset($resource->{$key.'s'}->{$key})) { - return $default; + return null; } return (array) $resource->{$key.'s'}->{$key}; diff --git a/src/Metadata/Extractor/YamlPropertyExtractor.php b/src/Metadata/Extractor/YamlPropertyExtractor.php index f9678169c53..c15c32ab277 100644 --- a/src/Metadata/Extractor/YamlPropertyExtractor.php +++ b/src/Metadata/Extractor/YamlPropertyExtractor.php @@ -101,10 +101,10 @@ private function buildProperties(array $resourcesYaml): void } } - private function buildAttribute(array $resource, string $key, mixed $default = null) + private function buildAttribute(array $resource, string $key): ?array { if (empty($resource[$key])) { - return $default; + return null; } if (!\is_array($resource[$key])) { diff --git a/src/Metadata/HttpOperation.php b/src/Metadata/HttpOperation.php index 2efd87ede65..f9b16e2bd57 100644 --- a/src/Metadata/HttpOperation.php +++ b/src/Metadata/HttpOperation.php @@ -287,7 +287,7 @@ public function getUriTemplate(): ?string return $this->uriTemplate; } - public function withUriTemplate(?string $uriTemplate = null) + public function withUriTemplate(?string $uriTemplate = null): static { $self = clone $this; $self->uriTemplate = $uriTemplate; @@ -311,11 +311,17 @@ public function withTypes($types): static return $self; } + /** + * @return array|string|null + */ public function getFormats() { return $this->formats; } + /** + * @param array|string|null $formats + */ public function withFormats($formats = null): static { $self = clone $this; @@ -324,11 +330,17 @@ public function withFormats($formats = null): static return $self; } + /** + * @return array|string|null + */ public function getInputFormats() { return $this->inputFormats; } + /** + * @param array|string|null $inputFormats + */ public function withInputFormats($inputFormats = null): static { $self = clone $this; @@ -337,11 +349,17 @@ public function withInputFormats($inputFormats = null): static return $self; } + /** + * @return array|string|null + */ public function getOutputFormats() { return $this->outputFormats; } + /** + * @param array|string|null $outputFormats + */ public function withOutputFormats($outputFormats = null): static { $self = clone $this; @@ -350,6 +368,9 @@ public function withOutputFormats($outputFormats = null): static return $self; } + /** + * @return array|array|list|null + */ public function getUriVariables() { return $this->uriVariables; diff --git a/src/Metadata/Metadata.php b/src/Metadata/Metadata.php index fca9c5945a7..91263d7001a 100644 --- a/src/Metadata/Metadata.php +++ b/src/Metadata/Metadata.php @@ -224,15 +224,12 @@ public function withFilters(array $filters): static return $self; } - /** - * @return array|bool|mixed|null - */ - public function getMercure() + public function getMercure(): mixed { return $this->mercure; } - public function withMercure($mercure): static + public function withMercure(mixed $mercure): static { $self = clone $this; $self->mercure = $mercure; @@ -240,12 +237,12 @@ public function withMercure($mercure): static return $self; } - public function getMessenger() + public function getMessenger(): mixed { return $this->messenger; } - public function withMessenger($messenger): static + public function withMessenger(mixed $messenger): static { $self = clone $this; $self->messenger = $messenger; @@ -253,12 +250,12 @@ public function withMessenger($messenger): static return $self; } - public function getInput() + public function getInput(): mixed { return $this->input; } - public function withInput($input): static + public function withInput(mixed $input): static { $self = clone $this; $self->input = $input; @@ -266,12 +263,12 @@ public function withInput($input): static return $self; } - public function getOutput() + public function getOutput(): mixed { return $this->output; } - public function withOutput($output): static + public function withOutput(mixed $output): static { $self = clone $this; $self->output = $output; diff --git a/src/Metadata/Resource/Factory/NotExposedOperationResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/NotExposedOperationResourceMetadataCollectionFactory.php index f5fc104fc7e..ea9c3db8dbb 100644 --- a/src/Metadata/Resource/Factory/NotExposedOperationResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/NotExposedOperationResourceMetadataCollectionFactory.php @@ -29,10 +29,10 @@ final class NotExposedOperationResourceMetadataCollectionFactory implements Reso { use OperationDefaultsTrait; - public static $skolemUriTemplate = '/.well-known/genid/{id}'; + public static string $skolemUriTemplate = '/.well-known/genid/{id}'; - private $linkFactory; - private $decorated; + private LinkFactoryInterface $linkFactory; + private ResourceMetadataCollectionFactoryInterface|null $decorated; public function __construct(LinkFactoryInterface $linkFactory, ?ResourceMetadataCollectionFactoryInterface $decorated = null) { diff --git a/src/Metadata/UriVariableTransformerInterface.php b/src/Metadata/UriVariableTransformerInterface.php index 16d88b291e7..b9f9476065b 100644 --- a/src/Metadata/UriVariableTransformerInterface.php +++ b/src/Metadata/UriVariableTransformerInterface.php @@ -25,6 +25,8 @@ interface UriVariableTransformerInterface * @param array $context Options available to the transformer * * @throws InvalidUriVariableException Occurs when the URI variable could not be transformed + * + * @return mixed */ public function transform(mixed $value, array $types, array $context = []); diff --git a/src/OpenApi/Model/Header.php b/src/OpenApi/Model/Header.php index 8f5ca7f0080..271cb981e3a 100644 --- a/src/OpenApi/Model/Header.php +++ b/src/OpenApi/Model/Header.php @@ -17,7 +17,7 @@ final class Header { use ExtensionTrait; - public function __construct(private readonly string $in = 'header', private string $description = '', private bool $required = false, private bool $deprecated = false, private bool $allowEmptyValue = false, private array $schema = [], private ?string $style = null, private bool $explode = false, private bool $allowReserved = false, private $example = null, private ?\ArrayObject $examples = null, private ?\ArrayObject $content = null) + public function __construct(private readonly string $in = 'header', private string $description = '', private bool $required = false, private bool $deprecated = false, private bool $allowEmptyValue = false, private array $schema = [], private ?string $style = null, private bool $explode = false, private bool $allowReserved = false, private mixed $example = null, private ?\ArrayObject $examples = null, private ?\ArrayObject $content = null) { if (null === $style) { $this->style = 'simple'; @@ -84,7 +84,7 @@ public function getAllowReserved(): bool return $this->allowReserved; } - public function getExample() + public function getExample(): mixed { return $this->example; } diff --git a/src/OpenApi/Model/Link.php b/src/OpenApi/Model/Link.php index 692c4f7d2ee..a7d48cfdaf4 100644 --- a/src/OpenApi/Model/Link.php +++ b/src/OpenApi/Model/Link.php @@ -17,7 +17,7 @@ final class Link { use ExtensionTrait; - public function __construct(private string $operationId, private ?\ArrayObject $parameters = null, private $requestBody = null, private string $description = '', private ?Server $server = null) + public function __construct(private string $operationId, private ?\ArrayObject $parameters = null, private mixed $requestBody = null, private string $description = '', private ?Server $server = null) { } @@ -31,7 +31,7 @@ public function getParameters(): \ArrayObject return $this->parameters; } - public function getRequestBody() + public function getRequestBody(): mixed { return $this->requestBody; } @@ -62,7 +62,7 @@ public function withParameters(\ArrayObject $parameters): self return $clone; } - public function withRequestBody($requestBody): self + public function withRequestBody(mixed $requestBody): self { $clone = clone $this; $clone->requestBody = $requestBody; diff --git a/src/OpenApi/Model/MediaType.php b/src/OpenApi/Model/MediaType.php index 97b7e79156d..25be3431c9a 100644 --- a/src/OpenApi/Model/MediaType.php +++ b/src/OpenApi/Model/MediaType.php @@ -26,7 +26,7 @@ public function getSchema(): ?\ArrayObject return $this->schema; } - public function getExample() + public function getExample(): mixed { return $this->example; } diff --git a/src/OpenApi/Model/Parameter.php b/src/OpenApi/Model/Parameter.php index d242fa8a6d9..f796bfaa58f 100644 --- a/src/OpenApi/Model/Parameter.php +++ b/src/OpenApi/Model/Parameter.php @@ -17,7 +17,7 @@ final class Parameter { use ExtensionTrait; - public function __construct(private string $name, private string $in, private string $description = '', private bool $required = false, private bool $deprecated = false, private ?bool $allowEmptyValue = null, private array $schema = [], private ?string $style = null, private bool $explode = false, private ?bool $allowReserved = null, private $example = null, private ?\ArrayObject $examples = null, private ?\ArrayObject $content = null) + public function __construct(private string $name, private string $in, private string $description = '', private bool $required = false, private bool $deprecated = false, private ?bool $allowEmptyValue = null, private array $schema = [], private ?string $style = null, private bool $explode = false, private ?bool $allowReserved = null, private mixed $example = null, private ?\ArrayObject $examples = null, private ?\ArrayObject $content = null) { if (null === $style) { if ('query' === $in || 'cookie' === $in) { @@ -93,7 +93,7 @@ public function getAllowReserved(): ?bool return $this->allowReserved; } - public function getExample() + public function getExample(): mixed { return $this->example; } @@ -188,7 +188,7 @@ public function withAllowReserved(bool $allowReserved): self return $clone; } - public function withExample($example): self + public function withExample(mixed $example): self { $clone = clone $this; $clone->example = $example; diff --git a/src/OpenApi/Model/Schema.php b/src/OpenApi/Model/Schema.php index 7647b4b7ef3..dd6b0741291 100644 --- a/src/OpenApi/Model/Schema.php +++ b/src/OpenApi/Model/Schema.php @@ -20,7 +20,7 @@ final class Schema extends \ArrayObject use ExtensionTrait; private readonly JsonSchema $schema; - public function __construct(private $discriminator = null, private bool $readOnly = false, private bool $writeOnly = false, private ?string $xml = null, private $externalDocs = null, private $example = null, private bool $deprecated = false) + public function __construct(private mixed $discriminator = null, private bool $readOnly = false, private bool $writeOnly = false, private ?string $xml = null, private mixed $externalDocs = null, private mixed $example = null, private bool $deprecated = false) { $this->schema = new JsonSchema(); @@ -48,7 +48,7 @@ public function getDefinitions(): \ArrayObject return new \ArrayObject(array_merge($this->schema->getArrayCopy(true), $this->getArrayCopy())); } - public function getDiscriminator() + public function getDiscriminator(): mixed { return $this->discriminator; } @@ -68,12 +68,12 @@ public function getXml(): string return $this->xml; } - public function getExternalDocs() + public function getExternalDocs(): mixed { return $this->externalDocs; } - public function getExample() + public function getExample(): mixed { return $this->example; } @@ -83,7 +83,7 @@ public function getDeprecated(): bool return $this->deprecated; } - public function withDiscriminator($discriminator): self + public function withDiscriminator(mixed $discriminator): self { $clone = clone $this; $clone->discriminator = $discriminator; @@ -115,7 +115,7 @@ public function withXml(string $xml): self return $clone; } - public function withExternalDocs($externalDocs): self + public function withExternalDocs(mixed $externalDocs): self { $clone = clone $this; $clone->externalDocs = $externalDocs; @@ -123,7 +123,7 @@ public function withExternalDocs($externalDocs): self return $clone; } - public function withExample($example): self + public function withExample(mixed $example): self { $clone = clone $this; $clone->example = $example; diff --git a/src/State/Pagination/Pagination.php b/src/State/Pagination/Pagination.php index 2544baf2917..28c034bef0f 100644 --- a/src/State/Pagination/Pagination.php +++ b/src/State/Pagination/Pagination.php @@ -227,7 +227,7 @@ private function getGraphQlEnabled(?Operation $operation): bool /** * Gets the given pagination parameter name from the given context. */ - private function getParameterFromContext(array $context, string $parameterName, mixed $default = null) + private function getParameterFromContext(array $context, string $parameterName, mixed $default = null): mixed { $filters = $context['filters'] ?? []; diff --git a/src/State/SerializerAwareProviderInterface.php b/src/State/SerializerAwareProviderInterface.php index 25ff6f814a8..e213176dc4a 100644 --- a/src/State/SerializerAwareProviderInterface.php +++ b/src/State/SerializerAwareProviderInterface.php @@ -22,5 +22,8 @@ */ interface SerializerAwareProviderInterface { + /** + * @return void + */ public function setSerializerLocator(ContainerInterface $serializerLocator); } diff --git a/src/Symfony/Action/EntrypointAction.php b/src/Symfony/Action/EntrypointAction.php index 049e8ed7dfa..50b9b2442cd 100644 --- a/src/Symfony/Action/EntrypointAction.php +++ b/src/Symfony/Action/EntrypointAction.php @@ -39,7 +39,7 @@ public function __construct( ) { } - public function __invoke(Request $request) + public function __invoke(Request $request): mixed { static::$resourceNameCollection = $this->resourceNameCollectionFactory->create(); $context = [ diff --git a/src/Symfony/Routing/IriConverter.php b/src/Symfony/Routing/IriConverter.php index 5570156ea5c..d1a0f14a09c 100644 --- a/src/Symfony/Routing/IriConverter.php +++ b/src/Symfony/Routing/IriConverter.php @@ -49,8 +49,8 @@ final class IriConverter implements IriConverterInterface use ResourceClassInfoTrait; use UriVariablesResolverTrait; - private $localOperationCache = []; - private $localIdentifiersExtractorOperationCache = []; + private array $localOperationCache = []; + private array $localIdentifiersExtractorOperationCache = []; public function __construct(private readonly ProviderInterface $provider, private readonly RouterInterface $router, private readonly IdentifiersExtractorInterface $identifiersExtractor, ResourceClassResolverInterface $resourceClassResolver, private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, ?UriVariablesConverterInterface $uriVariablesConverter = null, private readonly ?IriConverterInterface $decorated = null, private readonly ?OperationMetadataFactoryInterface $operationMetadataFactory = null) { diff --git a/src/Symfony/Routing/SkolemIriConverter.php b/src/Symfony/Routing/SkolemIriConverter.php index 5ed331a5f3f..8eb34a0e303 100644 --- a/src/Symfony/Routing/SkolemIriConverter.php +++ b/src/Symfony/Routing/SkolemIriConverter.php @@ -26,11 +26,11 @@ */ final class SkolemIriConverter implements IriConverterInterface { - public static $skolemUriTemplate = '/.well-known/genid/{id}'; + public static string $skolemUriTemplate = '/.well-known/genid/{id}'; - private $objectHashMap; - private $classHashMap = []; - private $router; + private \SplObjectStorage $objectHashMap; + private array $classHashMap = []; + private RouterInterface $router; public function __construct(RouterInterface $router) { diff --git a/src/Symfony/Validator/Serializer/ValidationExceptionNormalizer.php b/src/Symfony/Validator/Serializer/ValidationExceptionNormalizer.php index f51176475a3..19fe37df119 100644 --- a/src/Symfony/Validator/Serializer/ValidationExceptionNormalizer.php +++ b/src/Symfony/Validator/Serializer/ValidationExceptionNormalizer.php @@ -53,6 +53,9 @@ public function supportsNormalization(mixed $data, ?string $format = null, array return $data instanceof ValidationException && $this->decorated->supportsNormalization($data, $format, $context); } + /** + * @param string|null $format + */ public function getSupportedTypes($format): array { return [ValidationException::class => false]; From 734f4d8275e85df7bdbafd2d92c4c3466d5d75e3 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 1 Jul 2025 15:44:11 +0200 Subject: [PATCH 2/5] chore: fix cs --- .php-cs-fixer.dist.php | 7 ++----- src/Laravel/Controller/ApiPlatformController.php | 2 +- ...otExposedOperationResourceMetadataCollectionFactory.php | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 15de4411c44..365dd5b6ff1 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -33,7 +33,7 @@ ->setRules([ '@DoctrineAnnotation' => true, '@PHP71Migration' => true, - '@PHP71Migration:risky' => true, + '@PHP70Migration:risky' => true, '@PHPUnit60Migration:risky' => true, '@Symfony' => true, '@Symfony:risky' => true, @@ -76,7 +76,7 @@ ], 'no_superfluous_elseif' => true, 'no_superfluous_phpdoc_tags' => [ - 'allow_mixed' => false, + 'allow_mixed' => true, // For PHPStan 'allow_unused_params' => true, ], 'no_unset_cast' => true, @@ -98,9 +98,6 @@ 'php_unit_test_annotation' => [ 'style' => 'prefix', ], - 'phpdoc_add_missing_param_annotation' => [ - 'only_untyped' => true, - ], 'phpdoc_no_alias_tag' => true, 'phpdoc_order' => true, 'phpdoc_trim_consecutive_blank_line_separation' => true, diff --git a/src/Laravel/Controller/ApiPlatformController.php b/src/Laravel/Controller/ApiPlatformController.php index f1017d356e7..b8334bee4dc 100644 --- a/src/Laravel/Controller/ApiPlatformController.php +++ b/src/Laravel/Controller/ApiPlatformController.php @@ -103,7 +103,7 @@ private function getUriVariables(Request $request, HttpOperation $operation): ar { $uriVariables = []; foreach ($operation->getUriVariables() ?? [] as $parameterName => $_) { - $parameter = $request->route($parameterName); + $parameter = $request->route((string) $parameterName); if (\is_string($parameter) && ($format = $request->attributes->get('_format')) && str_contains($parameter, $format)) { $parameter = substr($parameter, 0, \strlen($parameter) - (\strlen($format) + 1)); } diff --git a/src/Metadata/Resource/Factory/NotExposedOperationResourceMetadataCollectionFactory.php b/src/Metadata/Resource/Factory/NotExposedOperationResourceMetadataCollectionFactory.php index ea9c3db8dbb..5e06fa23f96 100644 --- a/src/Metadata/Resource/Factory/NotExposedOperationResourceMetadataCollectionFactory.php +++ b/src/Metadata/Resource/Factory/NotExposedOperationResourceMetadataCollectionFactory.php @@ -32,7 +32,7 @@ final class NotExposedOperationResourceMetadataCollectionFactory implements Reso public static string $skolemUriTemplate = '/.well-known/genid/{id}'; private LinkFactoryInterface $linkFactory; - private ResourceMetadataCollectionFactoryInterface|null $decorated; + private ?ResourceMetadataCollectionFactoryInterface $decorated; public function __construct(LinkFactoryInterface $linkFactory, ?ResourceMetadataCollectionFactoryInterface $decorated = null) { From fb31437219b359d1f90e17742c9f0c8fa2673d9d Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 1 Jul 2025 16:21:45 +0200 Subject: [PATCH 3/5] fix: phpstan error --- .../Serializer/DocumentationNormalizer.php | 2 +- src/Laravel/Exception/ErrorHandler.php | 2 +- src/Metadata/HttpOperation.php | 29 +++++++++++++------ src/Symfony/EventListener/ErrorListener.php | 2 +- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/Hydra/Serializer/DocumentationNormalizer.php b/src/Hydra/Serializer/DocumentationNormalizer.php index ad9edeb18c0..ce6b1eb4d82 100644 --- a/src/Hydra/Serializer/DocumentationNormalizer.php +++ b/src/Hydra/Serializer/DocumentationNormalizer.php @@ -313,7 +313,7 @@ private function getHydraOperation(HttpOperation $operation, string $prefixedSho if (null !== $inputClass) { $possibleValue = []; - foreach ($operation->getInputFormats() as $mimeTypes) { + foreach ($operation->getInputFormats() ?? [] as $mimeTypes) { foreach ($mimeTypes as $mimeType) { $possibleValue[] = $mimeType; } diff --git a/src/Laravel/Exception/ErrorHandler.php b/src/Laravel/Exception/ErrorHandler.php index f496c5d1eb5..3369152233b 100644 --- a/src/Laravel/Exception/ErrorHandler.php +++ b/src/Laravel/Exception/ErrorHandler.php @@ -77,7 +77,7 @@ public function render($request, \Throwable $exception) $operation = null; foreach ($resourceCollection as $resource) { foreach ($resource->getOperations() as $op) { - foreach ($op->getOutputFormats() as $key => $value) { + foreach ($op->getOutputFormats() ?? [] as $key => $value) { if ($key === $format) { $operation = $op; break 3; diff --git a/src/Metadata/HttpOperation.php b/src/Metadata/HttpOperation.php index f9b16e2bd57..bea5db22eae 100644 --- a/src/Metadata/HttpOperation.php +++ b/src/Metadata/HttpOperation.php @@ -29,6 +29,13 @@ class HttpOperation extends Operation public const METHOD_HEAD = 'HEAD'; public const METHOD_OPTIONS = 'OPTIONS'; + /** @var array|null */ + protected ?array $formats; + /** @var array|null */ + protected ?array $inputFormats; + /** @var array|null */ + protected ?array $outputFormats; + /** * @param string[]|null $types the RDF types of this property * @param array|string|null $formats {@see https://api-platform.com/docs/core/content-negotiation/#configuring-formats-for-a-specific-resource-or-operation} @@ -90,9 +97,9 @@ public function __construct( protected string $method = 'GET', protected ?string $uriTemplate = null, protected ?array $types = null, - protected $formats = null, - protected $inputFormats = null, - protected $outputFormats = null, + $formats = null, + $inputFormats = null, + $outputFormats = null, protected $uriVariables = null, protected ?string $routePrefix = null, protected ?string $routeName = null, @@ -214,6 +221,10 @@ public function __construct( ?bool $queryParameterValidationEnabled = null, array $extraProperties = [], ) { + $this->formats = (null === $formats || \is_array($formats)) ? $formats : [$formats]; + $this->inputFormats = (null === $inputFormats || \is_array($inputFormats)) ? $inputFormats : [$inputFormats]; + $this->outputFormats = (null === $outputFormats || \is_array($outputFormats)) ? $outputFormats : [$outputFormats]; + parent::__construct( shortName: $shortName, class: $class, @@ -312,7 +323,7 @@ public function withTypes($types): static } /** - * @return array|string|null + * @return array|null */ public function getFormats() { @@ -325,13 +336,13 @@ public function getFormats() public function withFormats($formats = null): static { $self = clone $this; - $self->formats = $formats; + $self->formats = (null === $formats || \is_array($formats)) ? $formats : [$formats]; return $self; } /** - * @return array|string|null + * @return array|null */ public function getInputFormats() { @@ -344,13 +355,13 @@ public function getInputFormats() public function withInputFormats($inputFormats = null): static { $self = clone $this; - $self->inputFormats = $inputFormats; + $self->inputFormats = (null === $inputFormats || \is_array($inputFormats)) ? $inputFormats : [$inputFormats]; return $self; } /** - * @return array|string|null + * @return array|null */ public function getOutputFormats() { @@ -363,7 +374,7 @@ public function getOutputFormats() public function withOutputFormats($outputFormats = null): static { $self = clone $this; - $self->outputFormats = $outputFormats; + $self->outputFormats = (null === $outputFormats || \is_array($outputFormats)) ? $outputFormats : [$outputFormats]; return $self; } diff --git a/src/Symfony/EventListener/ErrorListener.php b/src/Symfony/EventListener/ErrorListener.php index f973403cf92..c179100e366 100644 --- a/src/Symfony/EventListener/ErrorListener.php +++ b/src/Symfony/EventListener/ErrorListener.php @@ -227,7 +227,7 @@ class: Error::class, // TODO: move this to ResourceMetadataCollection? foreach ($resourceCollection as $resource) { foreach ($resource->getOperations() as $op) { - foreach ($op->getOutputFormats() as $key => $value) { + foreach ($op->getOutputFormats() ?? [] as $key => $value) { if ($key === $format) { $operation = $op; break 3; From c29eb4334d2e9931d0c9153610eb4526f68fe3a3 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 2 Jul 2025 11:46:20 +0200 Subject: [PATCH 4/5] fix: feedback --- src/Metadata/ApiProperty.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Metadata/ApiProperty.php b/src/Metadata/ApiProperty.php index 1c99d03eec1..d57e488e6b9 100644 --- a/src/Metadata/ApiProperty.php +++ b/src/Metadata/ApiProperty.php @@ -48,13 +48,13 @@ final class ApiProperty * @param bool|null $push https://api-platform.com/docs/core/push-relations/ * @param string|\Stringable|null $security https://api-platform.com/docs/core/security * @param string|\Stringable|null $securityPostDenormalize https://api-platform.com/docs/core/security/#executing-access-control-rules-after-denormalization - * @param string[] $types the RDF types of this property - * @param string[] $iris - * @param LegacyType[] $builtinTypes + * @param string[]|null $types the RDF types of this property + * @param string[]|null $iris + * @param LegacyType[]|null $builtinTypes * @param string|null $uriTemplate (experimental) whether to return the subRessource collection IRI instead of an iterable of IRI * @param string|null $property The property name * @param Context|Groups|Ignore|SerializedName|SerializedPath|MaxDepth|array $serialize Serializer attributes - * @param Type $nativeType The internal PHP type + * @param Type|null $nativeType The internal PHP type */ public function __construct( private ?string $description = null, @@ -507,7 +507,7 @@ public function withTypes(array|string $types = []): static /** * deprecated since 4.2, use "getNativeType" instead. * - * @return LegacyType[] + * @return LegacyType[]|null */ public function getBuiltinTypes(): ?array { @@ -588,7 +588,7 @@ public function withExtraProperties(array $extraProperties = []): static /** * Gets IRI of this property. * - * @return string[] + * @return string[]|null */ public function getIris() { @@ -611,7 +611,7 @@ public function withIris(string|array $iris): static /** * Whether to generate a skolem iri on anonymous resources. * - * @return bool + * @return bool|null */ public function getGenId() { From 45916edae7df4f91e0c1da250fe51807bc5bf9a9 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Thu, 3 Jul 2025 10:31:26 +0200 Subject: [PATCH 5/5] fix: native typehint --- src/Metadata/ApiProperty.php | 4 +--- src/Metadata/HttpOperation.php | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Metadata/ApiProperty.php b/src/Metadata/ApiProperty.php index d57e488e6b9..be5a7918015 100644 --- a/src/Metadata/ApiProperty.php +++ b/src/Metadata/ApiProperty.php @@ -610,10 +610,8 @@ public function withIris(string|array $iris): static /** * Whether to generate a skolem iri on anonymous resources. - * - * @return bool|null */ - public function getGenId() + public function getGenId(): ?bool { return $this->genId; } diff --git a/src/Metadata/HttpOperation.php b/src/Metadata/HttpOperation.php index bea5db22eae..493459a4676 100644 --- a/src/Metadata/HttpOperation.php +++ b/src/Metadata/HttpOperation.php @@ -30,11 +30,11 @@ class HttpOperation extends Operation public const METHOD_OPTIONS = 'OPTIONS'; /** @var array|null */ - protected ?array $formats; + protected $formats; /** @var array|null */ - protected ?array $inputFormats; + protected $inputFormats; /** @var array|null */ - protected ?array $outputFormats; + protected $outputFormats; /** * @param string[]|null $types the RDF types of this property