From 12b00893025f82996ae95b39f19cd952034a7db3 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 3 Sep 2025 17:14:13 +0200 Subject: [PATCH 1/2] fix(phpdoc): array shape --- .../0006-filtering-system-and-parameters.md | 4 +-- .../Type/ContextAwareTypeBuilderInterface.php | 6 +++- .../Eloquent/Metadata/ModelMetadata.php | 6 +++- src/Laravel/Routing/Router.php | 6 +++- src/Metadata/IriConverterInterface.php | 15 ++++++++-- src/Metadata/Parameter.php | 28 ++++++++++++------- src/Serializer/TagCollectorInterface.php | 6 +++- src/State/ParameterProviderInterface.php | 9 ++++-- src/State/ProcessorInterface.php | 10 +++++-- src/State/ProviderInterface.php | 8 ++++-- .../SerializerContextBuilderInterface.php | 7 ++++- 11 files changed, 78 insertions(+), 27 deletions(-) diff --git a/docs/adr/0006-filtering-system-and-parameters.md b/docs/adr/0006-filtering-system-and-parameters.md index c1accc30412..0f81cc55cbf 100644 --- a/docs/adr/0006-filtering-system-and-parameters.md +++ b/docs/adr/0006-filtering-system-and-parameters.md @@ -193,8 +193,8 @@ During the `Provider` phase (`RequestEvent::REQUEST`), we could use a `Parameter interface ParameterProviderInterface { /** - * @param array $parameters - * @param array|array{request?: Request, resource_class?: string, operation: HttpOperation} $context + * @param array $parameters + * @param array $context */ public function provide(Parameter $parameter, array $parameters = [], array $context = []): ?HttpOperation; } diff --git a/src/GraphQl/Type/ContextAwareTypeBuilderInterface.php b/src/GraphQl/Type/ContextAwareTypeBuilderInterface.php index 7d0aab55342..d945ff175e5 100644 --- a/src/GraphQl/Type/ContextAwareTypeBuilderInterface.php +++ b/src/GraphQl/Type/ContextAwareTypeBuilderInterface.php @@ -31,7 +31,11 @@ interface ContextAwareTypeBuilderInterface /** * Gets the object type of the given resource. * - * @param array&array{input?: bool, wrapped?: bool, depth?: int} $context + * @param array $context + * + * @phpstan-param array $context + * + * @psalm-param array{input?: bool, wrapped?: bool, depth?: int, ...} $context * * @return GraphQLType the object type, possibly wrapped by NonNull */ diff --git a/src/Laravel/Eloquent/Metadata/ModelMetadata.php b/src/Laravel/Eloquent/Metadata/ModelMetadata.php index fbb0139a70d..3fc977f1241 100644 --- a/src/Laravel/Eloquent/Metadata/ModelMetadata.php +++ b/src/Laravel/Eloquent/Metadata/ModelMetadata.php @@ -256,7 +256,11 @@ private function getCastsWithDates(Model $model): Collection /** * Gets the default value for the given column. * - * @param array&array{name: string, default: string} $column + * @param array $column + * + * @phpstan-param array $column + * + * @psalm-param array{name: string, default: string, ...} $column */ private function getColumnDefault(array $column, Model $model): mixed { diff --git a/src/Laravel/Routing/Router.php b/src/Laravel/Routing/Router.php index 6bb47fada8b..e57e59bd0ba 100644 --- a/src/Laravel/Routing/Router.php +++ b/src/Laravel/Routing/Router.php @@ -72,7 +72,11 @@ public function getRouteCollection(): RouteCollection /** * {@inheritdoc} * - * @return array|array{_api_resource_class?: class-string|string, _api_operation_name?: string, uri_variables?: array} + * @return array + * + * @phpstan-return array + * + * @psalm-return array{_api_resource_class?: class-string|string, _api_operation_name?: string, uri_variables?: array, ...} */ public function match(string $pathInfo): array { diff --git a/src/Metadata/IriConverterInterface.php b/src/Metadata/IriConverterInterface.php index 2347f1c9c7d..a79206a110b 100644 --- a/src/Metadata/IriConverterInterface.php +++ b/src/Metadata/IriConverterInterface.php @@ -17,6 +17,7 @@ use ApiPlatform\Metadata\Exception\ItemNotFoundException; use ApiPlatform\Metadata\Exception\OperationNotFoundException; use ApiPlatform\Metadata\Exception\RuntimeException; +use Symfony\Component\HttpFoundation\Request; /** * Converts item and resources to IRI and vice versa. @@ -28,7 +29,11 @@ interface IriConverterInterface /** * Retrieves an item from its IRI. * - * @param array|array{request?: Request, resource_class?: string|class-string} $context + * @param array $context + * + * @phpstan-param array $context + * + * @psalm-param array{request?: Request, resource_class?: string|class-string, ...} $context * * @throws InvalidArgumentException * @throws ItemNotFoundException @@ -38,8 +43,12 @@ public function getResourceFromIri(string $iri, array $context = [], ?Operation /** * Gets the IRI associated with the given item. * - * @param object|class-string $resource - * @param array|array{force_resource_class?: string|class-string, item_uri_template?: string, uri_variables?: array} $context + * @param object|class-string $resource + * @param array $context + * + * @phpstan-param array $context + * + * @psalm-param array{force_resource_class?: string|class-string, item_uri_template?: string, uri_variables?: array, ...} $context * * @throws OperationNotFoundException * @throws InvalidArgumentException diff --git a/src/Metadata/Parameter.php b/src/Metadata/Parameter.php index 68ec235bc30..784e886949e 100644 --- a/src/Metadata/Parameter.php +++ b/src/Metadata/Parameter.php @@ -21,15 +21,19 @@ abstract class Parameter { /** - * @param (array&array{type?: string, default?: mixed})|null $schema - * @param array $extraProperties - * @param ParameterProviderInterface|callable|string|null $provider - * @param list $properties a list of properties this parameter applies to (works with the :property placeholder) - * @param FilterInterface|string|null $filter - * @param mixed $constraints an array of Symfony constraints, or an array of Laravel rules - * @param Type $nativeType the PHP native type, we cast values to an array if its a CollectionType, if not and it's an array with a single value we use it (eg: HTTP Header) - * @param ?bool $castToNativeType whether API Platform should cast your parameter to the nativeType declared - * @param ?callable(mixed): mixed $castFn the closure used to cast your parameter, this gets called only when $castToNativeType is set + * @param array|null $schema + * @param array $extraProperties + * @param ParameterProviderInterface|callable|string|null $provider + * @param list $properties a list of properties this parameter applies to (works with the :property placeholder) + * @param FilterInterface|string|null $filter + * @param mixed $constraints an array of Symfony constraints, or an array of Laravel rules + * @param Type $nativeType the PHP native type, we cast values to an array if its a CollectionType, if not and it's an array with a single value we use it (eg: HTTP Header) + * @param ?bool $castToNativeType whether API Platform should cast your parameter to the nativeType declared + * @param ?callable(mixed): mixed $castFn the closure used to cast your parameter, this gets called only when $castToNativeType is set + * + * @phpstan-param array|null $schema + * + * @psalm-param array{type?: string, default?: mixed, ...}|null $schema */ public function __construct( protected ?string $key = null, @@ -61,7 +65,11 @@ public function getKey(): ?string } /** - * @return (array&array{type?: string, default?: mixed})|null $schema + * @return array|null + * + * @phpstan-return array|null + * + * @psalm-return array{type?: string, default?: string, ...}|null */ public function getSchema(): ?array { diff --git a/src/Serializer/TagCollectorInterface.php b/src/Serializer/TagCollectorInterface.php index 523075677a2..2099c815881 100644 --- a/src/Serializer/TagCollectorInterface.php +++ b/src/Serializer/TagCollectorInterface.php @@ -23,7 +23,11 @@ interface TagCollectorInterface /** * Collect cache tags for cache invalidation. * - * @param array&array{iri?: string, data?: mixed, object?: mixed, property_metadata?: \ApiPlatform\Metadata\ApiProperty, api_attribute?: string, resources?: array, format?: string, operation?: \ApiPlatform\Metadata\Operation} $context + * @param array $context + * + * @phpstan-param array $context + * + * @psalm-param array{iri?: string, data?: mixed, object?: mixed, property_metadata?: \ApiPlatform\Metadata\ApiProperty, api_attribute?: string, resources?: array, format?: string, operation?: \ApiPlatform\Metadata\Operation, ...} $context */ public function collect(array $context = []): void; } diff --git a/src/State/ParameterProviderInterface.php b/src/State/ParameterProviderInterface.php index 49f84f0d500..a1945b27531 100644 --- a/src/State/ParameterProviderInterface.php +++ b/src/State/ParameterProviderInterface.php @@ -15,6 +15,7 @@ use ApiPlatform\Metadata\Operation; use ApiPlatform\Metadata\Parameter; +use Symfony\Component\HttpFoundation\Request; /** * Optionnaly transforms request parameters and provides modification to the current Operation. @@ -22,8 +23,12 @@ interface ParameterProviderInterface { /** - * @param array $parameters - * @param array|array{request?: Request, resource_class?: string, operation: Operation} $context + * @param array $parameters + * @param array $context + * + * @phpstan-param array $context + * + * @psalm-param array{request?: Request, resource_class?: string, operation: Operation, ...} $context */ public function provide(Parameter $parameter, array $parameters = [], array $context = []): ?Operation; } diff --git a/src/State/ProcessorInterface.php b/src/State/ProcessorInterface.php index 5d676e16ae5..0d63dfbd7fb 100644 --- a/src/State/ProcessorInterface.php +++ b/src/State/ProcessorInterface.php @@ -29,9 +29,13 @@ interface ProcessorInterface /** * Handles the state. * - * @param T1 $data - * @param array $uriVariables - * @param array&array{request?: Request, previous_data?: mixed, resource_class?: string|null, original_data?: mixed} $context + * @param T1 $data + * @param array $uriVariables + * @param array $context + * + * @phpstan-param array $context + * + * @psalm-param array{request?: Request, previous_data?: mixed, resource_class?: string|null, original_data?: mixed, ...} $context * * @return T2 */ diff --git a/src/State/ProviderInterface.php b/src/State/ProviderInterface.php index eadcaa05384..68b8f9f82c0 100644 --- a/src/State/ProviderInterface.php +++ b/src/State/ProviderInterface.php @@ -29,8 +29,12 @@ interface ProviderInterface /** * Provides data. * - * @param array $uriVariables - * @param array|array{request?: Request, resource_class?: string} $context + * @param array $uriVariables + * @param array $context + * + * @phpstan-param array $context + * + * @psalm-param array{request?: Request, resource_class?: string, ...} $context * * @return T|PartialPaginatorInterface|iterable|null */ diff --git a/src/State/SerializerContextBuilderInterface.php b/src/State/SerializerContextBuilderInterface.php index 78f4cf990b8..1b2c79164ae 100644 --- a/src/State/SerializerContextBuilderInterface.php +++ b/src/State/SerializerContextBuilderInterface.php @@ -32,7 +32,11 @@ interface SerializerContextBuilderInterface * * @throws RuntimeException * - * @return array&array{ + * @return array + * + * @phpstan-return array + * + * @psalm-return array{ * groups?: string[]|string, * operation_name?: string, * operation?: HttpOperation, @@ -55,6 +59,7 @@ interface SerializerContextBuilderInterface * attributes?: string[], * deserializer_type?: string, * api_assign_object_to_populate?: bool, + * ... * } */ public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array; From b837ab293cb79cb170d1ca8ef5522634f4803f95 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 3 Sep 2025 18:19:35 +0200 Subject: [PATCH 2/2] Fix phpstan --- src/Laravel/Eloquent/Serializer/SerializerContextBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Laravel/Eloquent/Serializer/SerializerContextBuilder.php b/src/Laravel/Eloquent/Serializer/SerializerContextBuilder.php index 1ee0796cede..7f4685c043b 100644 --- a/src/Laravel/Eloquent/Serializer/SerializerContextBuilder.php +++ b/src/Laravel/Eloquent/Serializer/SerializerContextBuilder.php @@ -39,7 +39,7 @@ public function __construct( public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array { $context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes); - if (!isset($context['resource_class']) || !is_a($context['resource_class'], Model::class, true)) { + if (!isset($context['resource_class']) || !\is_string($context['resource_class']) || !is_a($context['resource_class'], Model::class, true)) { return $context; }