From 22590c8d1b3551ea8911233ae64fe4a0bc3b0316 Mon Sep 17 00:00:00 2001 From: Rico Sonntag Date: Thu, 13 Nov 2025 08:34:12 +0100 Subject: [PATCH] fix: specify generics for phpstan compliance --- TASKS.md | 53 +++++++++++++++++++ src/JsonMapper.php | 12 +++++ .../CollectionDocBlockTypeResolver.php | 8 +++ .../Collection/CollectionFactory.php | 11 ++++ .../Collection/CollectionFactoryInterface.php | 8 ++- src/JsonMapper/Type/TypeResolver.php | 6 +++ .../BuiltinValueConversionStrategy.php | 9 ++++ .../CollectionValueConversionStrategy.php | 3 ++ .../ObjectTypeConversionGuardTrait.php | 4 ++ .../ObjectValueConversionStrategy.php | 2 + tests/Classes/Base.php | 2 +- tests/Classes/ClassMap/CollectionSource.php | 3 ++ tests/Classes/ClassMap/CollectionTarget.php | 3 ++ 13 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 TASKS.md diff --git a/TASKS.md b/TASKS.md new file mode 100644 index 0000000..428c2b6 --- /dev/null +++ b/TASKS.md @@ -0,0 +1,53 @@ +# Follow-up tasks + +## PHPStan maximum-level review (composer ci:test:php:phpstan) + +### MagicSunday\\JsonMapper +- [x] Resolve PHPStan: `Property MagicSunday\\JsonMapper::$collectionFactory with generic interface MagicSunday\\JsonMapper\\Collection\\CollectionFactoryInterface does not specify its types: TKey, TValue` (`src/JsonMapper.php:101`). +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper::convertUnionValue() has parameter $type with generic class Symfony\\Component\\TypeInfo\\Type\\UnionType but does not specify its types: T` (`src/JsonMapper.php:497`). +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper::describeUnionType() has parameter $type with generic class Symfony\\Component\\TypeInfo\\Type\\UnionType but does not specify its types: T` (`src/JsonMapper.php:586`). +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper::unionAllowsNull() has parameter $type with generic class Symfony\\Component\\TypeInfo\\Type\\UnionType but does not specify its types: T` (`src/JsonMapper.php:597`). +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper::getReflectionClass() return type with generic class ReflectionClass does not specify its types: T` (`src/JsonMapper.php:735`). + +### MagicSunday\\JsonMapper\\Collection\\CollectionDocBlockTypeResolver +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper\\Collection\\CollectionDocBlockTypeResolver::resolve() return type with generic class Symfony\\Component\\TypeInfo\\Type\\CollectionType does not specify its types: T` (`src/JsonMapper/Collection/CollectionDocBlockTypeResolver.php:53`). + +### MagicSunday\\JsonMapper\\Collection\\CollectionFactory +- [x] Resolve PHPStan: `Class MagicSunday\\JsonMapper\\Collection\\CollectionFactory implements generic interface MagicSunday\\JsonMapper\\Collection\\CollectionFactoryInterface but does not specify its types: TKey, TValue` (`src/JsonMapper/Collection/CollectionFactory.php:35`). +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper\\Collection\\CollectionFactory::fromCollectionType() has parameter $type with generic class Symfony\\Component\\TypeInfo\\Type\\CollectionType but does not specify its types: T` (`src/JsonMapper/Collection/CollectionFactory.php:94`). +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper\\Collection\\CollectionFactory::resolveWrappedClass() has parameter $objectType with generic class Symfony\\Component\\TypeInfo\\Type\\ObjectType but does not specify its types: T` (`src/JsonMapper/Collection/CollectionFactory.php:120`). + +### MagicSunday\\JsonMapper\\Collection\\CollectionFactoryInterface +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper\\Collection\\CollectionFactoryInterface::fromCollectionType() has parameter $type with generic class Symfony\\Component\\TypeInfo\\Type\\CollectionType but does not specify its types: T` (`src/JsonMapper/Collection/CollectionFactoryInterface.php:42`). + +### MagicSunday\\JsonMapper\\Type\\TypeResolver +- [x] Resolve PHPStan: `Property MagicSunday\\JsonMapper\\Type\\TypeResolver::$defaultType with generic class Symfony\\Component\\TypeInfo\\Type\\BuiltinType does not specify its types: T` (`src/JsonMapper/Type/TypeResolver.php:33`). +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper\\Type\\TypeResolver::normalizeUnionType() has parameter $type with generic class Symfony\\Component\\TypeInfo\\Type\\UnionType but does not specify its types: T` (`src/JsonMapper/Type/TypeResolver.php:224`). + +### MagicSunday\\JsonMapper\\Value\\Strategy\\BuiltinValueConversionStrategy +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper\\Value\\Strategy\\BuiltinValueConversionStrategy::normalizeValue() has parameter $type with generic class Symfony\\Component\\TypeInfo\\Type\\BuiltinType but does not specify its types: T` (`src/JsonMapper/Value/Strategy/BuiltinValueConversionStrategy.php:66`). +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper\\Value\\Strategy\\BuiltinValueConversionStrategy::guardCompatibility() has parameter $type with generic class Symfony\\Component\\TypeInfo\\Type\\BuiltinType but does not specify its types: T` (`src/JsonMapper/Value/Strategy/BuiltinValueConversionStrategy.php:125`). +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper\\Value\\Strategy\\BuiltinValueConversionStrategy::allowsNull() has parameter $type with generic class Symfony\\Component\\TypeInfo\\Type\\BuiltinType but does not specify its types: T` (`src/JsonMapper/Value/Strategy/BuiltinValueConversionStrategy.php:156`). + +### MagicSunday\\JsonMapper\\Value\\Strategy\\CollectionValueConversionStrategy +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper\\Value\\Strategy\\CollectionValueConversionStrategy::__construct() has parameter $collectionFactory with generic interface MagicSunday\\JsonMapper\\Collection\\CollectionFactoryInterface but does not specify its types: TKey, TValue` (`src/JsonMapper/Value/Strategy/CollectionValueConversionStrategy.php:26`). + +### MagicSunday\\JsonMapper\\Value\\Strategy\\DateTimeValueConversionStrategy +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper\\Value\\Strategy\\DateTimeValueConversionStrategy::extractObjectType() return type with generic class Symfony\\Component\\TypeInfo\\Type\\ObjectType does not specify its types: T` (`src/JsonMapper/Value/Strategy/ObjectTypeConversionGuardTrait.php:27`). +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper\\Value\\Strategy\\DateTimeValueConversionStrategy::guardNullableValue() has parameter $type with generic class Symfony\\Component\\TypeInfo\\Type\\ObjectType but does not specify its types: T` (`src/JsonMapper/Value/Strategy/ObjectTypeConversionGuardTrait.php:43`). + +### MagicSunday\\JsonMapper\\Value\\Strategy\\EnumValueConversionStrategy +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper\\Value\\Strategy\\EnumValueConversionStrategy::extractObjectType() return type with generic class Symfony\\Component\\TypeInfo\\Type\\ObjectType does not specify its types: T` (`src/JsonMapper/Value/Strategy/ObjectTypeConversionGuardTrait.php:27`). +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper\\Value\\Strategy\\EnumValueConversionStrategy::guardNullableValue() has parameter $type with generic class Symfony\\Component\\TypeInfo\\Type\\ObjectType but does not specify its types: T` (`src/JsonMapper/Value/Strategy/ObjectTypeConversionGuardTrait.php:43`). + +### MagicSunday\\JsonMapper\\Value\\Strategy\\ObjectValueConversionStrategy +- [x] Resolve PHPStan: `Method MagicSunday\\JsonMapper\\Value\\Strategy\\ObjectValueConversionStrategy::resolveClassName() has parameter $type with generic class Symfony\\Component\\TypeInfo\\Type\\ObjectType but does not specify its types: T` (`src/JsonMapper/Value/Strategy/ObjectValueConversionStrategy.php:73`). + +### MagicSunday\\Test\\Classes\\Base +- [x] Resolve PHPStan: `Property MagicSunday\\Test\\Classes\\Base::$simpleCollection with generic class MagicSunday\\Test\\Classes\\Collection does not specify its types: TKey, TValue` (`tests/Classes/Base.php:54`). + +### MagicSunday\\Test\\Classes\\ClassMap\\CollectionSource +- [x] Resolve PHPStan: `Class MagicSunday\\Test\\Classes\\ClassMap\\CollectionSource extends generic class MagicSunday\\Test\\Classes\\Collection but does not specify its types: TKey, TValue` (`tests/Classes/ClassMap/CollectionSource.php:23`). + +### MagicSunday\\Test\\Classes\\ClassMap\\CollectionTarget +- [x] Resolve PHPStan: `Class MagicSunday\\Test\\Classes\\ClassMap\\CollectionTarget extends generic class ArrayObject but does not specify its types: TKey, TValue` (`tests/Classes/ClassMap/CollectionTarget.php:23`). diff --git a/src/JsonMapper.php b/src/JsonMapper.php index 3f29a33..5a45020 100644 --- a/src/JsonMapper.php +++ b/src/JsonMapper.php @@ -98,6 +98,9 @@ class JsonMapper private ValueConverter $valueConverter; + /** + * @var CollectionFactoryInterface + */ private CollectionFactoryInterface $collectionFactory; private CollectionDocBlockTypeResolver $collectionDocBlockTypeResolver; @@ -493,6 +496,8 @@ private function convertValue(mixed $json, Type $type, MappingContext $context): /** * Converts the value according to the provided union type. + * + * @param UnionType $type */ private function convertUnionValue(mixed $json, UnionType $type, MappingContext $context): mixed { @@ -582,6 +587,8 @@ private function describeType(Type $type): string /** * Returns a textual representation of the union type. + * + * @param UnionType $type */ private function describeUnionType(UnionType $type): string { @@ -594,6 +601,9 @@ private function describeUnionType(UnionType $type): string return implode('|', $parts); } + /** + * @param UnionType $type + */ private function unionAllowsNull(UnionType $type): bool { foreach ($type->getTypes() as $candidate) { @@ -731,6 +741,8 @@ private function getReflectionProperty(string $className, string $propertyName): * Returns the specified reflection class. * * @param class-string $className + * + * @return ReflectionClass|null */ private function getReflectionClass(string $className): ?ReflectionClass { diff --git a/src/JsonMapper/Collection/CollectionDocBlockTypeResolver.php b/src/JsonMapper/Collection/CollectionDocBlockTypeResolver.php index cb0cbd0..69cb081 100644 --- a/src/JsonMapper/Collection/CollectionDocBlockTypeResolver.php +++ b/src/JsonMapper/Collection/CollectionDocBlockTypeResolver.php @@ -19,10 +19,16 @@ use phpDocumentor\Reflection\Types\ContextFactory; use ReflectionClass; use Symfony\Component\PropertyInfo\Util\PhpDocTypeHelper; +use Symfony\Component\TypeInfo\Type\BuiltinType; use Symfony\Component\TypeInfo\Type\CollectionType; +use Symfony\Component\TypeInfo\Type\GenericType; +use Symfony\Component\TypeInfo\Type\ObjectType; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * Resolves collection value types from PHPDoc annotations on collection classes. + * + * @phpstan-type CollectionWrappedType BuiltinType|BuiltinType|ObjectType */ final class CollectionDocBlockTypeResolver { @@ -49,6 +55,8 @@ public function __construct( * Attempts to resolve a {@see CollectionType} from the collection class PHPDoc. * * @param class-string $collectionClassName + * + * @return CollectionType>|null */ public function resolve(string $collectionClassName): ?CollectionType { diff --git a/src/JsonMapper/Collection/CollectionFactory.php b/src/JsonMapper/Collection/CollectionFactory.php index a5ccfd7..004cd2e 100644 --- a/src/JsonMapper/Collection/CollectionFactory.php +++ b/src/JsonMapper/Collection/CollectionFactory.php @@ -18,9 +18,12 @@ use MagicSunday\JsonMapper\Resolver\ClassResolver; use MagicSunday\JsonMapper\Value\ValueConverter; use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\BuiltinType; use Symfony\Component\TypeInfo\Type\CollectionType; +use Symfony\Component\TypeInfo\Type\GenericType; use Symfony\Component\TypeInfo\Type\ObjectType; use Symfony\Component\TypeInfo\Type\WrappingTypeInterface; +use Symfony\Component\TypeInfo\TypeIdentifier; use Traversable; use function get_debug_type; @@ -31,6 +34,10 @@ /** * Creates collections and hydrates wrapping collection classes. + * + * @phpstan-type CollectionWrappedType BuiltinType|BuiltinType|ObjectType + * + * @implements CollectionFactoryInterface */ final readonly class CollectionFactory implements CollectionFactoryInterface { @@ -89,6 +96,8 @@ public function mapIterable(mixed $json, Type $valueType, MappingContext $contex /** * Builds a collection based on the specified collection type description. * + * @param CollectionType> $type + * * @return array|object|null */ public function fromCollectionType(CollectionType $type, mixed $json, MappingContext $context): mixed @@ -113,6 +122,8 @@ public function fromCollectionType(CollectionType $type, mixed $json, MappingCon /** * Resolves the wrapped collection class name. * + * @param ObjectType $objectType + * * @return class-string * * @throws DomainException diff --git a/src/JsonMapper/Collection/CollectionFactoryInterface.php b/src/JsonMapper/Collection/CollectionFactoryInterface.php index bfc3d6b..eee3018 100644 --- a/src/JsonMapper/Collection/CollectionFactoryInterface.php +++ b/src/JsonMapper/Collection/CollectionFactoryInterface.php @@ -13,13 +13,19 @@ use MagicSunday\JsonMapper\Context\MappingContext; use Symfony\Component\TypeInfo\Type; +use Symfony\Component\TypeInfo\Type\BuiltinType; use Symfony\Component\TypeInfo\Type\CollectionType; +use Symfony\Component\TypeInfo\Type\GenericType; +use Symfony\Component\TypeInfo\Type\ObjectType; +use Symfony\Component\TypeInfo\TypeIdentifier; /** * Describes the operations required to materialize collection values. * * @template TKey of array-key * @template TValue + * + * @phpstan-type CollectionWrappedType BuiltinType|BuiltinType|ObjectType */ interface CollectionFactoryInterface { @@ -35,7 +41,7 @@ public function mapIterable(mixed $json, Type $valueType, MappingContext $contex /** * Builds a collection based on the specified collection type description. * - * @param CollectionType $type The collection type metadata extracted from PHPStan/Psalm annotations. + * @param CollectionType> $type The collection type metadata extracted from PHPStan/Psalm annotations. * * @return array|object|null */ diff --git a/src/JsonMapper/Type/TypeResolver.php b/src/JsonMapper/Type/TypeResolver.php index a6acbc8..a03fcf1 100644 --- a/src/JsonMapper/Type/TypeResolver.php +++ b/src/JsonMapper/Type/TypeResolver.php @@ -30,6 +30,9 @@ final class TypeResolver { private const string CACHE_KEY_PREFIX = 'jsonmapper.property_type.'; + /** + * @var BuiltinType + */ private BuiltinType $defaultType; public function __construct( @@ -221,6 +224,9 @@ private function createTypeFromNamedReflection(ReflectionNamedType $type, ?bool return $resolved; } + /** + * @param UnionType $type + */ private function normalizeUnionType(UnionType $type): Type { $types = []; diff --git a/src/JsonMapper/Value/Strategy/BuiltinValueConversionStrategy.php b/src/JsonMapper/Value/Strategy/BuiltinValueConversionStrategy.php index 4eacbbb..ee4a602 100644 --- a/src/JsonMapper/Value/Strategy/BuiltinValueConversionStrategy.php +++ b/src/JsonMapper/Value/Strategy/BuiltinValueConversionStrategy.php @@ -63,6 +63,9 @@ public function convert(mixed $value, Type $type, MappingContext $context): mixe return $converted; } + /** + * @param BuiltinType $type + */ private function normalizeValue(mixed $value, BuiltinType $type): mixed { if ($value === null) { @@ -122,6 +125,9 @@ private function normalizeValue(mixed $value, BuiltinType $type): mixed return $value; } + /** + * @param BuiltinType $type + */ private function guardCompatibility(mixed $value, BuiltinType $type, MappingContext $context): void { $identifier = $type->getTypeIdentifier(); @@ -153,6 +159,9 @@ private function guardCompatibility(mixed $value, BuiltinType $type, MappingCont } } + /** + * @param BuiltinType $type + */ private function allowsNull(BuiltinType $type): bool { return $type->isNullable(); diff --git a/src/JsonMapper/Value/Strategy/CollectionValueConversionStrategy.php b/src/JsonMapper/Value/Strategy/CollectionValueConversionStrategy.php index 5227c83..12d3db5 100644 --- a/src/JsonMapper/Value/Strategy/CollectionValueConversionStrategy.php +++ b/src/JsonMapper/Value/Strategy/CollectionValueConversionStrategy.php @@ -23,6 +23,9 @@ */ final readonly class CollectionValueConversionStrategy implements ValueConversionStrategyInterface { + /** + * @param CollectionFactoryInterface $collectionFactory + */ public function __construct( private CollectionFactoryInterface $collectionFactory, ) { diff --git a/src/JsonMapper/Value/Strategy/ObjectTypeConversionGuardTrait.php b/src/JsonMapper/Value/Strategy/ObjectTypeConversionGuardTrait.php index 0483ad6..1c9b474 100644 --- a/src/JsonMapper/Value/Strategy/ObjectTypeConversionGuardTrait.php +++ b/src/JsonMapper/Value/Strategy/ObjectTypeConversionGuardTrait.php @@ -23,6 +23,8 @@ trait ObjectTypeConversionGuardTrait { /** * Returns the provided type when it represents an object with a class name. + * + * @return ObjectType|null */ private function extractObjectType(Type $type): ?ObjectType { @@ -39,6 +41,8 @@ private function extractObjectType(Type $type): ?ObjectType /** * Ensures null values comply with the target object's nullability. + * + * @param ObjectType $type */ private function guardNullableValue(mixed $value, ObjectType $type, MappingContext $context): void { diff --git a/src/JsonMapper/Value/Strategy/ObjectValueConversionStrategy.php b/src/JsonMapper/Value/Strategy/ObjectValueConversionStrategy.php index 717ff35..51fb6ae 100644 --- a/src/JsonMapper/Value/Strategy/ObjectValueConversionStrategy.php +++ b/src/JsonMapper/Value/Strategy/ObjectValueConversionStrategy.php @@ -68,6 +68,8 @@ public function convert(mixed $value, Type $type, MappingContext $context): mixe /** * Resolves the class name from the provided object type. * + * @param ObjectType $type + * * @return class-string */ private function resolveClassName(ObjectType $type): string diff --git a/tests/Classes/Base.php b/tests/Classes/Base.php index bc04379..fedda98 100644 --- a/tests/Classes/Base.php +++ b/tests/Classes/Base.php @@ -49,7 +49,7 @@ class Base /** * A collection of Simple instances. * - * @var Collection|Simple[] + * @var Collection|array */ public $simpleCollection; diff --git a/tests/Classes/ClassMap/CollectionSource.php b/tests/Classes/ClassMap/CollectionSource.php index ff07cf5..5090ec7 100644 --- a/tests/Classes/ClassMap/CollectionSource.php +++ b/tests/Classes/ClassMap/CollectionSource.php @@ -20,6 +20,9 @@ * @license https://opensource.org/licenses/MIT * @link https://github.com/magicsunday/jsonmapper/ */ +/** + * @extends Collection + */ class CollectionSource extends Collection { } diff --git a/tests/Classes/ClassMap/CollectionTarget.php b/tests/Classes/ClassMap/CollectionTarget.php index a649180..ae057c2 100644 --- a/tests/Classes/ClassMap/CollectionTarget.php +++ b/tests/Classes/ClassMap/CollectionTarget.php @@ -20,6 +20,9 @@ * @license https://opensource.org/licenses/MIT * @link https://github.com/magicsunday/jsonmapper/ */ +/** + * @extends ArrayObject + */ class CollectionTarget extends ArrayObject { }