diff --git a/composer.json b/composer.json index 9c41d859be7..ec324f12a30 100644 --- a/composer.json +++ b/composer.json @@ -142,17 +142,17 @@ "illuminate/pagination": "^11.0 || ^12.0", "illuminate/routing": "^11.0 || ^12.0", "illuminate/support": "^11.0 || ^12.0", - "jangregor/phpstan-prophecy": "^1.0", + "jangregor/phpstan-prophecy": "^2.1.11", "justinrainbow/json-schema": "^5.2.11", "laravel/framework": "^11.0 || ^12.0", "orchestra/testbench": "^9.1", "phpspec/prophecy-phpunit": "^2.2", "phpstan/extension-installer": "^1.1", "phpstan/phpdoc-parser": "^1.29 || ^2.0", - "phpstan/phpstan": "^1.10", - "phpstan/phpstan-doctrine": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-symfony": "^1.0", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-doctrine": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-symfony": "^2.0", "phpunit/phpunit": "11.5.x-dev", "psr/log": "^1.0 || ^2.0 || ^3.0", "ramsey/uuid": "^4.7", diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 8aa7268a1b9..dbfe3153b84 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -17,7 +17,6 @@ parameters: excludePaths: # uses larastan - src/Laravel - - src/Symfony/Bundle/Command/OpenApiCommand.php # Symfony config - tests/Fixtures/app/config/config_swagger.php # Symfony cache @@ -33,10 +32,7 @@ parameters: - src/Doctrine/*/vendor/* - src/*/vendor/* # Symfony 6 support - - src/OpenApi/Serializer/CacheableSupportsMethodInterface.php - - src/Serializer/CacheableSupportsMethodInterface.php - tests/Hal/Serializer/ItemNormalizerTest.php - - tests/Symfony/Validator/Metadata/Property/ValidatorPropertyMetadataFactoryTest.php - src/Symfony/Bundle/ArgumentResolver/CompatibleValueResolverInterface.php earlyTerminatingMethodCalls: PHPUnit\Framework\Constraint\Constraint: @@ -75,6 +71,15 @@ parameters: # Expected, due to backward compatibility - '#Method GraphQL\\Type\\Definition\\WrappingType::getWrappedType\(\) invoked with 1 parameter, 0 required\.#' - '#Access to an undefined property GraphQL\\Type\\Definition\\NamedType&GraphQL\\Type\\Definition\\Type::\$name\.#' + - "#Call to function method_exists\\(\\) with GraphQL\\\\Type\\\\Definition\\\\Type&GraphQL\\\\Type\\\\Definition\\\\WrappingType and 'getInnermostType' will always evaluate to true\\.#" + - "#Call to function method_exists\\(\\) with Symfony\\\\Component\\\\Console\\\\Application and 'addCommand' will always evaluate to false\\.#" + - "#Call to function method_exists\\(\\) with 'Symfony\\\\\\\\Component\\\\\\\\PropertyInfo\\\\\\\\PropertyInfoExtractor' and 'getType' will always evaluate to true\\.#" + - "#Call to function method_exists\\(\\) with 'Symfony\\\\\\\\Component\\\\\\\\HttpFoundation\\\\\\\\Request' and 'getContentTypeFormat' will always evaluate to true\\.#" + - '#Call to an undefined method Symfony\\Component\\HttpFoundation\\Request::getContentType\(\)\.#' + - "#Call to function method_exists\\(\\) with 'Symfony\\\\\\\\Component\\\\\\\\Serializer\\\\\\\\Serializer' and 'getSupportedTypes' will always evaluate to true\\.#" + - "#Call to function method_exists\\(\\) with Symfony\\\\Component\\\\Serializer\\\\Normalizer\\\\NormalizerInterface and 'getSupportedTypes' will always evaluate to true\\.#" + - "#Call to function method_exists\\(\\) with Doctrine\\\\ODM\\\\MongoDB\\\\Configuration and 'setMetadataCache' will always evaluate to true\\.#" + - "#Call to function method_exists\\(\\) with Doctrine\\\\ODM\\\\MongoDB\\\\Mapping\\\\ClassMetadata\\|Doctrine\\\\ORM\\\\Mapping\\\\ClassMetadata and 'isChangeTrackingDef…' will always evaluate to true\\.#" - message: '#Instanceof between Symfony\\Component\\Serializer\\NameConverter\\NameConverterInterface and Symfony\\Component\\Serializer\\NameConverter\\MetadataAwareNameConverter will always evaluate to false\.#' paths: @@ -90,4 +95,28 @@ parameters: - message: '#^Service "[^"]+" is private.$#' path: src - - '#Call to an undefined method Symfony\\Component\\HttpFoundation\\Request::getContentType\(\)\.#' + + # 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\.$#' + + # TODO For PHPStan 2.0 + - + path: tests/ + identifier: function.alreadyNarrowedType + - + identifier: property.unusedType + - + identifier: return.unusedType + - + path: tests/ + identifier: void.pure + - + identifier: varTag.nativeType + - + identifier: isset.offset + - + identifier: trait.unused + - + identifier: instanceof.alwaysTrue + - + identifier: catch.neverThrown diff --git a/src/Doctrine/Orm/State/CollectionProvider.php b/src/Doctrine/Orm/State/CollectionProvider.php index 3815447a8d3..d67f7fe4431 100644 --- a/src/Doctrine/Orm/State/CollectionProvider.php +++ b/src/Doctrine/Orm/State/CollectionProvider.php @@ -56,7 +56,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c $manager = $this->managerRegistry->getManagerForClass($entityClass); $repository = $manager->getRepository($entityClass); - if (!method_exists($repository, 'createQueryBuilder')) { + if (!method_exists($repository, 'createQueryBuilder')) { // @phpstan-ignore-line function.alreadyNarrowedType throw new RuntimeException('The repository class must have a "createQueryBuilder" method.'); } diff --git a/src/Doctrine/Orm/State/ItemProvider.php b/src/Doctrine/Orm/State/ItemProvider.php index b201d03b7d0..f69a208c7a4 100644 --- a/src/Doctrine/Orm/State/ItemProvider.php +++ b/src/Doctrine/Orm/State/ItemProvider.php @@ -65,7 +65,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c } $repository = $manager->getRepository($entityClass); - if (!method_exists($repository, 'createQueryBuilder')) { + if (!method_exists($repository, 'createQueryBuilder')) { // @phpstan-ignore-line function.alreadyNarrowedType throw new RuntimeException('The repository class must have a "createQueryBuilder" method.'); } diff --git a/src/Elasticsearch/Extension/SortExtension.php b/src/Elasticsearch/Extension/SortExtension.php index 013d269ca48..e327f7908f4 100644 --- a/src/Elasticsearch/Extension/SortExtension.php +++ b/src/Elasticsearch/Extension/SortExtension.php @@ -49,7 +49,6 @@ public function applyToCollection(array $requestBody, string $resourceClass, ?Op if ( $operation && null !== ($defaultOrder = $operation->getOrder()) - && \is_array($defaultOrder) ) { foreach ($defaultOrder as $property => $direction) { if (\is_int($property)) { diff --git a/src/GraphQl/State/Provider/ReadProvider.php b/src/GraphQl/State/Provider/ReadProvider.php index 58c32f08097..def06700581 100644 --- a/src/GraphQl/State/Provider/ReadProvider.php +++ b/src/GraphQl/State/Provider/ReadProvider.php @@ -82,10 +82,6 @@ public function provide(Operation $operation, array $uriVariables = [], array $c return $item; } - if (!\is_object($item)) { - throw new \LogicException('Item from read provider should be a nullable object.'); - } - if (isset($context['graphql_context']) && !enum_exists($item::class)) { $context['graphql_context']['previous_object'] = clone $item; } @@ -124,6 +120,9 @@ private function getNormalizedFilters(array $args): array $filters = $args; foreach ($filters as $name => $value) { + // Prevent numeric keys like `'1'` + $name = (string) $name; + if (\is_array($value)) { if (strpos($name, '_list')) { $name = substr($name, 0, \strlen($name) - \strlen('_list')); @@ -136,7 +135,7 @@ private function getNormalizedFilters(array $args): array $filters[$name] = $this->getNormalizedFilters($value); } - if (\is_string($name) && strpos($name, $this->nestingSeparator)) { + if (strpos($name, $this->nestingSeparator)) { // Gives a chance to relations/nested fields. $index = array_search($name, array_keys($filters), true); $filters = diff --git a/src/GraphQl/Tests/Serializer/SerializerContextBuilderTest.php b/src/GraphQl/Tests/Serializer/SerializerContextBuilderTest.php index f6a913b4670..d4bc15990dc 100644 --- a/src/GraphQl/Tests/Serializer/SerializerContextBuilderTest.php +++ b/src/GraphQl/Tests/Serializer/SerializerContextBuilderTest.php @@ -77,8 +77,6 @@ private function buildOperationFromContext(bool $isMutation, bool $isSubscriptio } } - \assert($operation instanceof Operation); - return $operation; } diff --git a/src/GraphQl/Tests/State/Processor/NormalizeProcessorTest.php b/src/GraphQl/Tests/State/Processor/NormalizeProcessorTest.php index 6213938e7dd..3d9a23c9c30 100644 --- a/src/GraphQl/Tests/State/Processor/NormalizeProcessorTest.php +++ b/src/GraphQl/Tests/State/Processor/NormalizeProcessorTest.php @@ -49,7 +49,7 @@ public function testProcess($body, $operation): void $serializerContext = ['resource_class' => $operation->getClass()]; $normalizer = $this->createMock(NormalizerInterface::class); $serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class); - $serializerContextBuilder->expects($this->once())->method('create')->with($operation->getClass(), $operation, $context, normalization: true)->willReturn($serializerContext); + $serializerContextBuilder->expects($this->once())->method('create')->with($operation->getClass(), $operation, $context, true)->willReturn($serializerContext); $normalizer->expects($this->once())->method('normalize')->with($body, 'graphql', $serializerContext); $processor = new NormalizeProcessor($normalizer, $serializerContextBuilder, new Pagination()); $processor->process($body, $operation, [], $context); @@ -73,7 +73,7 @@ public function testProcessCollection($collection, $operation, $args, ?array $ex $normalizer = $this->prophesize(NormalizerInterface::class); $serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class); - $serializerContextBuilder->expects($this->once())->method('create')->with($operation->getClass(), $operation, $context, normalization: true)->willReturn($serializerContext); + $serializerContextBuilder->expects($this->once())->method('create')->with($operation->getClass(), $operation, $context, true)->willReturn($serializerContext); foreach ($collection as $v) { $normalizer->normalize($v, 'graphql', $serializerContext)->willReturn(['normalized_item'])->shouldBeCalledOnce(); } diff --git a/src/GraphQl/Tests/State/Provider/DenormalizeProviderTest.php b/src/GraphQl/Tests/State/Provider/DenormalizeProviderTest.php index d356b1f92e6..ec802d5152a 100644 --- a/src/GraphQl/Tests/State/Provider/DenormalizeProviderTest.php +++ b/src/GraphQl/Tests/State/Provider/DenormalizeProviderTest.php @@ -33,7 +33,7 @@ public function testProvide(): void $decorated->expects($this->once())->method('provide')->willReturn($objectToPopulate); $denormalizer = $this->createMock(DenormalizerInterface::class); $serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class); - $serializerContextBuilder->expects($this->once())->method('create')->with($operation->getClass(), $operation, $context, normalization: false)->willReturn($serializerContext); + $serializerContextBuilder->expects($this->once())->method('create')->with($operation->getClass(), $operation, $context, false)->willReturn($serializerContext); $denormalizer->expects($this->once())->method('denormalize')->with(['test'], 'dummy', 'graphql', $serializerContext)->willReturn(new \stdClass()); $provider = new DenormalizeProvider($decorated, $denormalizer, $serializerContextBuilder); $provider->provide($operation, [], $context); @@ -49,7 +49,7 @@ public function testProvideWithObjectToPopulate(): void $decorated->expects($this->once())->method('provide')->willReturn($objectToPopulate); $denormalizer = $this->createMock(DenormalizerInterface::class); $serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class); - $serializerContextBuilder->expects($this->once())->method('create')->with($operation->getClass(), $operation, $context, normalization: false)->willReturn($serializerContext); + $serializerContextBuilder->expects($this->once())->method('create')->with($operation->getClass(), $operation, $context, false)->willReturn($serializerContext); $denormalizer->expects($this->once())->method('denormalize')->with(['test'], 'dummy', 'graphql', $serializerContext)->willReturn(new \stdClass()); $provider = new DenormalizeProvider($decorated, $denormalizer, $serializerContextBuilder); $provider->provide($operation, [], $context); @@ -65,7 +65,7 @@ public function testProvideNotCalledWithQuery(): void $decorated->expects($this->once())->method('provide')->willReturn($objectToPopulate); $denormalizer = $this->createMock(DenormalizerInterface::class); $serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class); - $serializerContextBuilder->expects($this->never())->method('create')->with($operation->getClass(), $operation, $context, normalization: false)->willReturn($serializerContext); + $serializerContextBuilder->expects($this->never())->method('create')->with($operation->getClass(), $operation, $context, false)->willReturn($serializerContext); $denormalizer->expects($this->never())->method('denormalize')->with(['test'], 'dummy', 'graphql', $serializerContext)->willReturn(new \stdClass()); $provider = new DenormalizeProvider($decorated, $denormalizer, $serializerContextBuilder); $provider->provide($operation, [], $context); @@ -81,7 +81,7 @@ public function testProvideNotCalledWithoutDeserialize(): void $decorated->expects($this->once())->method('provide')->willReturn($objectToPopulate); $denormalizer = $this->createMock(DenormalizerInterface::class); $serializerContextBuilder = $this->createMock(SerializerContextBuilderInterface::class); - $serializerContextBuilder->expects($this->never())->method('create')->with($operation->getClass(), $operation, $context, normalization: false)->willReturn($serializerContext); + $serializerContextBuilder->expects($this->never())->method('create')->with($operation->getClass(), $operation, $context, false)->willReturn($serializerContext); $denormalizer->expects($this->never())->method('denormalize')->with(['test'], 'dummy', 'graphql', $serializerContext)->willReturn(new \stdClass()); $provider = new DenormalizeProvider($decorated, $denormalizer, $serializerContextBuilder); $provider->provide($operation, [], $context); diff --git a/src/JsonSchema/Tests/Metadata/Property/Factory/SchemaPropertyMetadataFactoryTest.php b/src/JsonSchema/Tests/Metadata/Property/Factory/SchemaPropertyMetadataFactoryTest.php index 48b1528bbf6..abbbcc1a729 100644 --- a/src/JsonSchema/Tests/Metadata/Property/Factory/SchemaPropertyMetadataFactoryTest.php +++ b/src/JsonSchema/Tests/Metadata/Property/Factory/SchemaPropertyMetadataFactoryTest.php @@ -43,7 +43,7 @@ public function testEnumLegacy(): void public function testEnum(): void { $resourceClassResolver = $this->createMock(ResourceClassResolverInterface::class); - $apiProperty = new ApiProperty(nativeType: Type::nullable(Type::enum(IntEnumAsIdentifier::class))); // @phpstan-ignore-line + $apiProperty = new ApiProperty(nativeType: Type::nullable(Type::enum(IntEnumAsIdentifier::class))); $decorated = $this->createMock(PropertyMetadataFactoryInterface::class); $decorated->expects($this->once())->method('create')->with(DummyWithEnum::class, 'intEnumAsIdentifier')->willReturn($apiProperty); $schemaPropertyMetadataFactory = new SchemaPropertyMetadataFactory($resourceClassResolver, $decorated); @@ -71,7 +71,7 @@ public function testWithCustomOpenApiContext(): void { $resourceClassResolver = $this->createMock(ResourceClassResolverInterface::class); $apiProperty = new ApiProperty( - nativeType: Type::nullable(Type::enum(IntEnumAsIdentifier::class)), // @phpstan-ignore-line + nativeType: Type::nullable(Type::enum(IntEnumAsIdentifier::class)), openapiContext: ['type' => 'object', 'properties' => ['alpha' => ['type' => 'integer']]], ); $decorated = $this->createMock(PropertyMetadataFactoryInterface::class); diff --git a/src/JsonSchema/Tests/SchemaFactoryTest.php b/src/JsonSchema/Tests/SchemaFactoryTest.php index a212c439a98..873c6d0acdf 100644 --- a/src/JsonSchema/Tests/SchemaFactoryTest.php +++ b/src/JsonSchema/Tests/SchemaFactoryTest.php @@ -277,7 +277,7 @@ public function testBuildSchemaForNonResourceClassWithUnionIntersectTypes(): voi $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); $propertyMetadataFactoryProphecy->create(NotAResourceWithUnionIntersectTypes::class, 'ignoredProperty', Argument::cetera())->willReturn( (new ApiProperty()) - ->withNativeType(Type::nullable(Type::string())) // @phpstan-ignore-line + ->withNativeType(Type::nullable(Type::string())) ->withReadable(true) ->withSchema(['type' => ['string', 'null']]) ); diff --git a/src/Metadata/Extractor/ResourceExtractorTrait.php b/src/Metadata/Extractor/ResourceExtractorTrait.php index 55fbde96e77..d56abf3c63f 100644 --- a/src/Metadata/Extractor/ResourceExtractorTrait.php +++ b/src/Metadata/Extractor/ResourceExtractorTrait.php @@ -25,7 +25,7 @@ trait ResourceExtractorTrait { private function buildArrayValue(\SimpleXMLElement|array|null $resource, string $key, mixed $default = null): ?array { - if (\is_object($resource) && $resource instanceof \SimpleXMLElement) { + if (\is_object($resource)) { if (!isset($resource->{$key.'s'}->{$key})) { return $default; } @@ -61,7 +61,7 @@ private function phpize(\SimpleXMLElement|array|null $resource, string $key, str case 'integer': return (int) $resource[$key]; case 'bool': - if (\is_object($resource) && $resource instanceof \SimpleXMLElement) { + if (\is_object($resource)) { return (bool) XmlUtils::phpize($resource[$key]); } diff --git a/src/Metadata/Tests/Property/Factory/SerializerPropertyMetadataFactoryTest.php b/src/Metadata/Tests/Property/Factory/SerializerPropertyMetadataFactoryTest.php index 7f0d50f1de4..9aa1da51806 100644 --- a/src/Metadata/Tests/Property/Factory/SerializerPropertyMetadataFactoryTest.php +++ b/src/Metadata/Tests/Property/Factory/SerializerPropertyMetadataFactoryTest.php @@ -72,7 +72,7 @@ public function testCreate($readGroups, $writeGroups): void $decoratedProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); $fooPropertyMetadata = (new ApiProperty()) - ->withNativeType(Type::nullable(Type::array())) // @phpstan-ignore-line + ->withNativeType(Type::nullable(Type::array())) ->withReadable(false) ->withWritable(true); $decoratedProphecy->create(Dummy::class, 'foo', $context)->willReturn($fooPropertyMetadata); @@ -80,7 +80,7 @@ public function testCreate($readGroups, $writeGroups): void ->withNativeType(Type::nullable(Type::object(RelatedDummy::class))); $decoratedProphecy->create(Dummy::class, 'relatedDummy', $context)->willReturn($relatedDummyPropertyMetadata); $nameConvertedPropertyMetadata = (new ApiProperty()) - ->withNativeType(Type::nullable(Type::string())); // @phpstan-ignore-line + ->withNativeType(Type::nullable(Type::string())); $decoratedProphecy->create(Dummy::class, 'nameConverted', $context)->willReturn($nameConvertedPropertyMetadata); $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); @@ -126,7 +126,7 @@ public function testCreateWithIgnoredProperty(): void $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(DummyIgnoreProperty::class)->willReturn(true); - $ignoredPropertyMetadata = (new ApiProperty())->withNativeType(Type::nullable(Type::string())); // @phpstan-ignore-line + $ignoredPropertyMetadata = (new ApiProperty())->withNativeType(Type::nullable(Type::string())); $options = [ 'normalization_groups' => ['dummy'], diff --git a/src/Metadata/Tests/Resource/Factory/ParameterResourceMetadataCollectionFactoryTest.php b/src/Metadata/Tests/Resource/Factory/ParameterResourceMetadataCollectionFactoryTest.php index 3b72f06a381..95bb8f3cd6c 100644 --- a/src/Metadata/Tests/Resource/Factory/ParameterResourceMetadataCollectionFactoryTest.php +++ b/src/Metadata/Tests/Resource/Factory/ParameterResourceMetadataCollectionFactoryTest.php @@ -41,6 +41,7 @@ public function testParameterFactory(): void $filterLocator->method('get')->willReturn(new class implements FilterInterface { public function getDescription(string $resourceClass): array { + // @phpstan-ignore-next-line return.type return [ 'hydra' => [ 'property' => 'hydra', diff --git a/src/Metadata/Tests/Util/PropertyInfoToTypeInfoHelperTest.php b/src/Metadata/Tests/Util/PropertyInfoToTypeInfoHelperTest.php index 61f6e99b045..88b9e04541a 100644 --- a/src/Metadata/Tests/Util/PropertyInfoToTypeInfoHelperTest.php +++ b/src/Metadata/Tests/Util/PropertyInfoToTypeInfoHelperTest.php @@ -50,10 +50,10 @@ public static function convertLegacyTypesToTypeDataProvider(): iterable yield [Type::int(), [new LegacyType('int')]]; yield [Type::object(\stdClass::class), [new LegacyType('object', false, \stdClass::class)]]; yield [ - Type::generic(Type::object('Foo'), Type::string(), Type::int()), // @phpstan-ignore-line - [new LegacyType('object', false, 'Foo', false, [new LegacyType('string')], new LegacyType('int'))], + Type::generic(Type::object(\stdClass::class), Type::string(), Type::int()), + [new LegacyType('object', false, 'stdClass', false, [new LegacyType('string')], new LegacyType('int'))], ]; - yield [Type::nullable(Type::int()), [new LegacyType('int', true)]]; // @phpstan-ignore-line + yield [Type::nullable(Type::int()), [new LegacyType('int', true)]]; yield [Type::union(Type::int(), Type::string()), [new LegacyType('int'), new LegacyType('string')]]; yield [ Type::union(Type::int(), Type::string(), Type::null()), @@ -97,7 +97,7 @@ public static function convertTypeToLegacyTypesDataProvider(): iterable [new LegacyType('array', false, null, true, new LegacyType('int'), new LegacyType('string'))], Type::collection(Type::builtin(TypeIdentifier::ARRAY), Type::string(), Type::int()), // @phpstan-ignore-line ]; - yield [[new LegacyType('int', true)], Type::nullable(Type::int())]; // @phpstan-ignore-line + yield [[new LegacyType('int', true)], Type::nullable(Type::int())]; yield [[new LegacyType('int'), new LegacyType('string')], Type::union(Type::int(), Type::string())]; yield [ [new LegacyType('int', true), new LegacyType('string', true)], diff --git a/src/OpenApi/Factory/OpenApiFactory.php b/src/OpenApi/Factory/OpenApiFactory.php index ccd6c530b67..4ef3bf72f01 100644 --- a/src/OpenApi/Factory/OpenApiFactory.php +++ b/src/OpenApi/Factory/OpenApiFactory.php @@ -531,7 +531,7 @@ private function buildContent(array $responseMimeTypes, array $operationSchemas) } /** - * @return array[array, array] + * @return array{array, array} */ private function getMimeTypes(HttpOperation $operation): array { diff --git a/src/Serializer/AbstractCollectionNormalizer.php b/src/Serializer/AbstractCollectionNormalizer.php index bf983c3dc55..bde6ab863b9 100644 --- a/src/Serializer/AbstractCollectionNormalizer.php +++ b/src/Serializer/AbstractCollectionNormalizer.php @@ -101,7 +101,7 @@ public function normalize(mixed $object, ?string $format = null, array $context */ protected function normalizeRawCollection(iterable $object, ?string $format = null, array $context = []): array|\ArrayObject { - if (!$object && ($context[Serializer::EMPTY_ARRAY_AS_OBJECT] ?? false) && \is_array($object)) { + if ([] === $object && ($context[Serializer::EMPTY_ARRAY_AS_OBJECT] ?? false)) { return new \ArrayObject(); } diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index f3cd8979f95..796f7011419 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -1171,9 +1171,10 @@ private function createAndValidateAttributeValue(string $attribute, mixed $value } if ( - ($t instanceof CollectionType && $collectionValueType instanceof ObjectType && (null !== ($className = $collectionValueType->getClassName()))) - || ($t instanceof LegacyType && $t->isCollection() && null !== $collectionValueType && null !== ($className = $collectionValueType->getClassName())) + ($t instanceof CollectionType && $collectionValueType instanceof ObjectType) + || ($t instanceof LegacyType && $t->isCollection() && null !== $collectionValueType && null !== $collectionValueType->getClassName()) ) { + $className = $collectionValueType->getClassName(); if (!$this->serializer instanceof DenormalizerInterface) { throw new LogicException(\sprintf('The injected serializer must be an instance of "%s".', DenormalizerInterface::class)); } diff --git a/src/Serializer/Mapping/Loader/PropertyMetadataLoader.php b/src/Serializer/Mapping/Loader/PropertyMetadataLoader.php index b7f7212d3c0..3a61f07d4fe 100644 --- a/src/Serializer/Mapping/Loader/PropertyMetadataLoader.php +++ b/src/Serializer/Mapping/Loader/PropertyMetadataLoader.php @@ -134,7 +134,7 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool } /** - * @param ApiProperty[] $attributes + * @param array> $attributes */ private function addAttributeMetadata(ApiProperty $attribute, array &$attributes): void { diff --git a/src/Serializer/Tests/AbstractItemNormalizerTest.php b/src/Serializer/Tests/AbstractItemNormalizerTest.php index 422e56fd252..7c9467250ef 100644 --- a/src/Serializer/Tests/AbstractItemNormalizerTest.php +++ b/src/Serializer/Tests/AbstractItemNormalizerTest.php @@ -1509,7 +1509,7 @@ public function testNullable(): void if (!method_exists(PropertyInfoExtractor::class, 'getType')) { $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_STRING, true)])->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); } else { - $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn((new ApiProperty())->withNativeType(Type::nullable(Type::string()))->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); // @phpstan-ignore-line + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn((new ApiProperty())->withNativeType(Type::nullable(Type::string()))->withDescription('')->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false)); } $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); @@ -1738,7 +1738,7 @@ public function testDenormalizeObjectWithNullDisabledTypeEnforcement(): void if (!method_exists(PropertyInfoExtractor::class, 'getType')) { $propertyMetadataFactoryProphecy->create(DtoWithNullValue::class, 'dummy', [])->willReturn((new ApiProperty())->withBuiltinTypes([new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, nullable: true)])->withDescription('')->withReadable(true)->withWritable(true)); } else { - $propertyMetadataFactoryProphecy->create(DtoWithNullValue::class, 'dummy', [])->willReturn((new ApiProperty())->withNativeType(Type::nullable(Type::object()))->withDescription('')->withReadable(true)->withWritable(true)); // @phpstan-ignore-line + $propertyMetadataFactoryProphecy->create(DtoWithNullValue::class, 'dummy', [])->willReturn((new ApiProperty())->withNativeType(Type::nullable(Type::object()))->withDescription('')->withReadable(true)->withWritable(true)); } $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); diff --git a/src/Serializer/Tests/Fixtures/ApiResource/Dummy.php b/src/Serializer/Tests/Fixtures/ApiResource/Dummy.php index a85f785d10f..549421ce47a 100644 --- a/src/Serializer/Tests/Fixtures/ApiResource/Dummy.php +++ b/src/Serializer/Tests/Fixtures/ApiResource/Dummy.php @@ -86,9 +86,7 @@ class Dummy public Collection|iterable $relatedDummies; /** - * @phpstan-ignore-next-line - * - * @var Collection + * @var Collection */ public Collection $relatedDummiesWithUnionTypes; diff --git a/src/State/Pagination/Pagination.php b/src/State/Pagination/Pagination.php index 9d070631122..5daae171523 100644 --- a/src/State/Pagination/Pagination.php +++ b/src/State/Pagination/Pagination.php @@ -88,6 +88,7 @@ public function getOffset(?Operation $operation = null, array $context = []): in return ($offset = ($context['count'] ?? 0) - $last) < 0 ? 0 : $offset; } + /** @var int|float $offset */ $offset = ($this->getPage($context) - 1) * $limit; if (!\is_int($offset)) { diff --git a/src/Symfony/Bundle/Test/Constraint/MatchesJsonSchema.php b/src/Symfony/Bundle/Test/Constraint/MatchesJsonSchema.php index 39422fbefa0..d16ba5e9539 100644 --- a/src/Symfony/Bundle/Test/Constraint/MatchesJsonSchema.php +++ b/src/Symfony/Bundle/Test/Constraint/MatchesJsonSchema.php @@ -91,9 +91,6 @@ private function normalizeJson(mixed $document): object|array } $document = json_encode($document, \JSON_THROW_ON_ERROR); - if (!\is_string($document)) { - throw new \UnexpectedValueException('JSON encode failed.'); - } $document = json_decode($document, null, 512, \JSON_THROW_ON_ERROR); if (!\is_array($document) && !\is_object($document)) { throw new \UnexpectedValueException('JSON decode failed.'); diff --git a/src/Symfony/Tests/Doctrine/EventListener/PurgeHttpCacheListenerTest.php b/src/Symfony/Tests/Doctrine/EventListener/PurgeHttpCacheListenerTest.php index 68d879b8257..9080d1a7a05 100644 --- a/src/Symfony/Tests/Doctrine/EventListener/PurgeHttpCacheListenerTest.php +++ b/src/Symfony/Tests/Doctrine/EventListener/PurgeHttpCacheListenerTest.php @@ -164,7 +164,7 @@ public function testNothingToPurge(): void $classMetadata = new ClassMetadata(DummyNoGetOperation::class); $emProphecy->getClassMetadata(DummyNoGetOperation::class)->willReturn($classMetadata)->shouldBeCalled(); - $changeSet = ['lorem' => 'ipsum']; + $changeSet = ['lorem' => ['ipsum1', 'ipsum2']]; $eventArgs = new PreUpdateEventArgs($dummyNoGetOperation, $emProphecy->reveal(), $changeSet); $listener = new PurgeHttpCacheListener($purgerProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal()); diff --git a/tests/Fixtures/TestBundle/ApiResource/WithParameter.php b/tests/Fixtures/TestBundle/ApiResource/WithParameter.php index 8c130804225..066b146ac37 100644 --- a/tests/Fixtures/TestBundle/ApiResource/WithParameter.php +++ b/tests/Fixtures/TestBundle/ApiResource/WithParameter.php @@ -73,8 +73,8 @@ nativeType: new UnionType( new BuiltinType(TypeIdentifier::STRING), new CollectionType( - new GenericType( // @phpstan-ignore-line - new BuiltinType(TypeIdentifier::ARRAY), // @phpstan-ignore-line + new GenericType( + new BuiltinType(TypeIdentifier::ARRAY), new BuiltinType(TypeIdentifier::INT), new BuiltinType(TypeIdentifier::STRING), ), diff --git a/tests/Fixtures/TestBundle/Entity/Issue5587/Business.php b/tests/Fixtures/TestBundle/Entity/Issue5587/Business.php index 219a851509b..acf60ae7c4e 100644 --- a/tests/Fixtures/TestBundle/Entity/Issue5587/Business.php +++ b/tests/Fixtures/TestBundle/Entity/Issue5587/Business.php @@ -48,7 +48,7 @@ class Business #[ORM\Column(type: 'string', length: 255, nullable: true)] #[Groups(['w', 'r'])] private $name; - /** @var Collection|Employee[] */ + /** @var Collection */ #[ORM\JoinTable(name: 'issue5584_business_users')] #[ORM\ManyToMany(targetEntity: Employee::class, inversedBy: 'businesses')] #[Groups(['w', 'r'])] diff --git a/tests/Symfony/Bundle/DataCollector/RequestDataCollectorTest.php b/tests/Symfony/Bundle/DataCollector/RequestDataCollectorTest.php index 087544cf934..69ac46b2250 100644 --- a/tests/Symfony/Bundle/DataCollector/RequestDataCollectorTest.php +++ b/tests/Symfony/Bundle/DataCollector/RequestDataCollectorTest.php @@ -37,11 +37,15 @@ class RequestDataCollectorTest extends TestCase { use ProphecyTrait; - private ObjectProphecy|Request $request; - private MockObject|Response $response; - private ObjectProphecy|ParameterBag $attributes; - private ObjectProphecy|ResourceMetadataCollectionFactoryInterface $metadataFactory; - private ObjectProphecy|ContainerInterface $filterLocator; + /** @var ObjectProphecy */ + private ObjectProphecy $request; + private MockObject&Response $response; + /** @var ObjectProphecy */ + private ObjectProphecy $attributes; + /** @var ObjectProphecy */ + private ObjectProphecy $metadataFactory; + /** @var ObjectProphecy */ + private ObjectProphecy $filterLocator; protected function setUp(): void { diff --git a/tests/Symfony/Bundle/Test/ClientTest.php b/tests/Symfony/Bundle/Test/ClientTest.php index 3e5c808bc09..6be9fa5745f 100644 --- a/tests/Symfony/Bundle/Test/ClientTest.php +++ b/tests/Symfony/Bundle/Test/ClientTest.php @@ -47,7 +47,6 @@ public static function getResources(): array public function testRequest(): void { $client = self::createClient(); - $client->getKernelBrowser(); $this->assertSame(static::$kernel, $client->getKernel()); $client->enableProfiler();