diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0d88225..57d2779 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: true matrix: - php: [ 8.2 ] + php: [ 8.2, 8.3, 8.4 ] steps: - name: Checkout code @@ -50,7 +50,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.2 + php-version: 8.4 extensions: dom, curl, libxml, mbstring, zip tools: composer:v2 coverage: none @@ -76,7 +76,7 @@ jobs: strategy: fail-fast: true matrix: - php: [ 8.2 ] + php: [ 8.2, 8.3, 8.4 ] steps: - name: Checkout code uses: actions/checkout@v3 diff --git a/composer.json b/composer.json index 89ddbdd..4fd446d 100644 --- a/composer.json +++ b/composer.json @@ -10,17 +10,17 @@ ], "require": { "php": ">=8.2", - "good-php/reflection": "^1.0", - "illuminate/support": "^10.0 || ^11.0" + "good-php/reflection": "^2.0", + "illuminate/support": "^10.0 || ^11.0 || ^12.0" }, "require-dev": { - "pestphp/pest": "^2.8", - "php-cs-fixer/shim": "~3.19.2", - "tenantcloud/php-cs-fixer-rule-sets": "~3.0.0", - "phpstan/phpstan": "~1.10.21", - "phpstan/phpstan-phpunit": "^1.3", - "phpstan/phpstan-webmozart-assert": "^1.2", - "phpstan/phpstan-mockery": "^1.1", + "pestphp/pest": "^3.8", + "php-cs-fixer/shim": "~3.80.0", + "tenantcloud/php-cs-fixer-rule-sets": "~3.4.1", + "phpstan/phpstan": "~2.1.17", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-webmozart-assert": "^2.0", + "phpstan/phpstan-mockery": "^2.0", "phake/phake": "^4.2", "tenantcloud/php-standard": "^2.2" }, diff --git a/phpstan.neon b/phpstan.neon index eb8fe97..dc514dc 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -10,5 +10,4 @@ parameters: - src ignoreErrors: - # There's no extension for that :( -# - '#Call to an undefined method Pest\\Expectations\\Expectation|Pest\\Expectations\\Support\\Extendable::#i' + - '#Parameter (\#|\$).* expects list<(.*)>, array given.#i' diff --git a/src/Serializer.php b/src/Serializer.php index c176b5b..c7a8c3d 100644 --- a/src/Serializer.php +++ b/src/Serializer.php @@ -26,5 +26,5 @@ public function reflector(): Reflector; * * @return A */ - public function adapter(string $typeAdapterType, Type|string $type, Attributes $attributes = new ArrayAttributes(), TypeAdapterFactory $skipPast = null): TypeAdapter; + public function adapter(string $typeAdapterType, Type|string $type, Attributes $attributes = new ArrayAttributes(), ?TypeAdapterFactory $skipPast = null): TypeAdapter; } diff --git a/src/Serializer/Registry/Cache/MemoizingTypeAdapterRegistry.php b/src/Serializer/Registry/Cache/MemoizingTypeAdapterRegistry.php index ce6a988..275f925 100644 --- a/src/Serializer/Registry/Cache/MemoizingTypeAdapterRegistry.php +++ b/src/Serializer/Registry/Cache/MemoizingTypeAdapterRegistry.php @@ -29,7 +29,7 @@ public function __construct( * * @return TypeAdapterType */ - public function forType(string $typeAdapterType, Serializer $serializer, Type $type, Attributes $attributes = new ArrayAttributes(), TypeAdapterFactory $skipPast = null): TypeAdapter + public function forType(string $typeAdapterType, Serializer $serializer, Type $type, Attributes $attributes = new ArrayAttributes(), ?TypeAdapterFactory $skipPast = null): TypeAdapter { $this->resolved[$typeAdapterType][(string) $type] ??= new WeakMap(); diff --git a/src/Serializer/Registry/Factory/FactoryTypeAdapterRegistry.php b/src/Serializer/Registry/Factory/FactoryTypeAdapterRegistry.php index 593b787..0934431 100644 --- a/src/Serializer/Registry/Factory/FactoryTypeAdapterRegistry.php +++ b/src/Serializer/Registry/Factory/FactoryTypeAdapterRegistry.php @@ -30,7 +30,7 @@ public function __construct( * * @return TypeAdapterType */ - public function forType(string $typeAdapterType, Serializer $serializer, Type $type, Attributes $attributes = new ArrayAttributes(), TypeAdapterFactory $skipPast = null): TypeAdapter + public function forType(string $typeAdapterType, Serializer $serializer, Type $type, Attributes $attributes = new ArrayAttributes(), ?TypeAdapterFactory $skipPast = null): TypeAdapter { if ($skipPast) { $skipPastIndex = array_search($skipPast, $this->factories, true); diff --git a/src/Serializer/Registry/TypeAdapterNotFoundException.php b/src/Serializer/Registry/TypeAdapterNotFoundException.php index cf8567a..4cfbd42 100644 --- a/src/Serializer/Registry/TypeAdapterNotFoundException.php +++ b/src/Serializer/Registry/TypeAdapterNotFoundException.php @@ -14,7 +14,7 @@ final class TypeAdapterNotFoundException extends RuntimeException /** * @param TypeAdapterFactory>|null $skipPast */ - public function __construct(string $typeAdapterType, Type $type, Attributes $attributes, ?TypeAdapterFactory $skipPast, Throwable $previous = null) + public function __construct(string $typeAdapterType, Type $type, Attributes $attributes, ?TypeAdapterFactory $skipPast, ?Throwable $previous = null) { $message = "A matching type adapter of type '{$typeAdapterType}' for type '{$type}' " . ($attributes->has() ? 'with attributes ' . $attributes : '') . diff --git a/src/Serializer/Registry/TypeAdapterRegistry.php b/src/Serializer/Registry/TypeAdapterRegistry.php index 170ea1d..6c29dd3 100644 --- a/src/Serializer/Registry/TypeAdapterRegistry.php +++ b/src/Serializer/Registry/TypeAdapterRegistry.php @@ -19,5 +19,5 @@ interface TypeAdapterRegistry * * @return TypeAdapterType */ - public function forType(string $typeAdapterType, Serializer $serializer, Type $type, Attributes $attributes = new ArrayAttributes(), TypeAdapterFactory $skipPast = null): TypeAdapter; + public function forType(string $typeAdapterType, Serializer $serializer, Type $type, Attributes $attributes = new ArrayAttributes(), ?TypeAdapterFactory $skipPast = null): TypeAdapter; } diff --git a/src/Serializer/TypeAdapterRegistrySerializer.php b/src/Serializer/TypeAdapterRegistrySerializer.php index d4bdecb..923fb3a 100644 --- a/src/Serializer/TypeAdapterRegistrySerializer.php +++ b/src/Serializer/TypeAdapterRegistrySerializer.php @@ -19,7 +19,7 @@ public function __construct( private readonly Reflector $reflector, ) {} - public function adapter(string $typeAdapterType, Type|string $type, Attributes $attributes = new ArrayAttributes(), TypeAdapterFactory $skipPast = null): TypeAdapter + public function adapter(string $typeAdapterType, Type|string $type, Attributes $attributes = new ArrayAttributes(), ?TypeAdapterFactory $skipPast = null): TypeAdapter { if (is_string($type)) { $type = new NamedType($type); diff --git a/src/TypeAdapter/Exception/UnexpectedEnumValueException.php b/src/TypeAdapter/Exception/UnexpectedEnumValueException.php index 9795935..132c4fb 100644 --- a/src/TypeAdapter/Exception/UnexpectedEnumValueException.php +++ b/src/TypeAdapter/Exception/UnexpectedEnumValueException.php @@ -13,7 +13,7 @@ class UnexpectedEnumValueException extends RuntimeException implements Unexpecte public function __construct( public readonly string|int $value, public readonly array $expectedValues, - Throwable $previous = null + ?Throwable $previous = null ) { parent::__construct( 'Expected one of [' . diff --git a/src/TypeAdapter/Exception/UnexpectedPolymorphicTypeException.php b/src/TypeAdapter/Exception/UnexpectedPolymorphicTypeException.php index 80256ea..b460286 100644 --- a/src/TypeAdapter/Exception/UnexpectedPolymorphicTypeException.php +++ b/src/TypeAdapter/Exception/UnexpectedPolymorphicTypeException.php @@ -14,7 +14,7 @@ public function __construct( public readonly string $typeNameField, public readonly string $value, public readonly array $expectedTypeNames, - Throwable $previous = null + ?Throwable $previous = null ) { parent::__construct( "Only the following polymorphic types for field '{$this->typeNameField}' are allowed: [" . diff --git a/src/TypeAdapter/Exception/UnexpectedTypeException.php b/src/TypeAdapter/Exception/UnexpectedTypeException.php index 5a91936..74f3ed6 100644 --- a/src/TypeAdapter/Exception/UnexpectedTypeException.php +++ b/src/TypeAdapter/Exception/UnexpectedTypeException.php @@ -11,7 +11,7 @@ class UnexpectedTypeException extends RuntimeException public function __construct( public readonly mixed $value, public readonly Type $expectedType, - Throwable $previous = null + ?Throwable $previous = null ) { parent::__construct( "Expected value of type '{$expectedType}', but got '" . diff --git a/src/TypeAdapter/Exception/UnexpectedValueException.php b/src/TypeAdapter/Exception/UnexpectedValueException.php index 647680d..73be550 100644 --- a/src/TypeAdapter/Exception/UnexpectedValueException.php +++ b/src/TypeAdapter/Exception/UnexpectedValueException.php @@ -4,6 +4,4 @@ use Throwable; -interface UnexpectedValueException extends Throwable -{ -} +interface UnexpectedValueException extends Throwable {} diff --git a/src/TypeAdapter/Json/FromPrimitiveJsonTypeAdapterFactory.php b/src/TypeAdapter/Json/FromPrimitiveJsonTypeAdapterFactory.php index 4dc5e40..4a89f0b 100644 --- a/src/TypeAdapter/Json/FromPrimitiveJsonTypeAdapterFactory.php +++ b/src/TypeAdapter/Json/FromPrimitiveJsonTypeAdapterFactory.php @@ -15,6 +15,9 @@ */ final class FromPrimitiveJsonTypeAdapterFactory implements TypeAdapterFactory { + /** + * @return JsonTypeAdapter|null + */ public function create(string $typeAdapterType, Type $type, Attributes $attributes, Serializer $serializer): ?JsonTypeAdapter { if ($typeAdapterType !== JsonTypeAdapter::class) { diff --git a/src/TypeAdapter/Json/JsonTypeAdapter.php b/src/TypeAdapter/Json/JsonTypeAdapter.php index 726502c..3650306 100644 --- a/src/TypeAdapter/Json/JsonTypeAdapter.php +++ b/src/TypeAdapter/Json/JsonTypeAdapter.php @@ -9,6 +9,4 @@ * * @extends TypeAdapter */ -interface JsonTypeAdapter extends TypeAdapter -{ -} +interface JsonTypeAdapter extends TypeAdapter {} diff --git a/src/TypeAdapter/Primitive/BuiltIn/ArrayMapper.php b/src/TypeAdapter/Primitive/BuiltIn/ArrayMapper.php index 238ac06..283bb7a 100644 --- a/src/TypeAdapter/Primitive/BuiltIn/ArrayMapper.php +++ b/src/TypeAdapter/Primitive/BuiltIn/ArrayMapper.php @@ -18,10 +18,10 @@ final class ArrayMapper /** * @template T * - * @param array $value - * @param NamedType $type + * @param array $value + * @param NamedType $type * - * @return array + * @return array|stdClass */ #[MapTo(PrimitiveTypeAdapter::class)] public function to(array $value, Type $type, Serializer $serializer): array|stdClass @@ -49,10 +49,10 @@ public function to(array $value, Type $type, Serializer $serializer): array|stdC } /** - * @param array $value - * @param NamedType $type + * @param array $value + * @param NamedType $type * - * @return array + * @return array */ #[MapFrom(PrimitiveTypeAdapter::class)] public function from(array $value, Type $type, Serializer $serializer): array diff --git a/src/TypeAdapter/Primitive/BuiltIn/BackedEnumMapper.php b/src/TypeAdapter/Primitive/BuiltIn/BackedEnumMapper.php index b719ba5..e2d08c4 100644 --- a/src/TypeAdapter/Primitive/BuiltIn/BackedEnumMapper.php +++ b/src/TypeAdapter/Primitive/BuiltIn/BackedEnumMapper.php @@ -25,6 +25,7 @@ public function to(BackedEnum $value): string|int #[MapFrom(PrimitiveTypeAdapter::class, new BaseTypeAcceptedByAcceptanceStrategy(BackedEnum::class))] public function from(string|int $value, Type $type): BackedEnum { + /** @var class-string $enumClass */ $enumClass = $type->name; $enum = $enumClass::tryFrom($value); diff --git a/src/TypeAdapter/Primitive/BuiltIn/Nullable/NullableTypeAdapterFactory.php b/src/TypeAdapter/Primitive/BuiltIn/Nullable/NullableTypeAdapterFactory.php index ad78e6b..8fc6ff1 100644 --- a/src/TypeAdapter/Primitive/BuiltIn/Nullable/NullableTypeAdapterFactory.php +++ b/src/TypeAdapter/Primitive/BuiltIn/Nullable/NullableTypeAdapterFactory.php @@ -14,6 +14,9 @@ */ class NullableTypeAdapterFactory implements TypeAdapterFactory { + /** + * @return NullableTypeAdapter|null + */ public function create(string $typeAdapterType, Type $type, Attributes $attributes, Serializer $serializer): ?NullableTypeAdapter { if ($typeAdapterType !== PrimitiveTypeAdapter::class || !$type instanceof NullableType) { diff --git a/src/TypeAdapter/Primitive/ClassProperties/ClassPropertiesPrimitiveTypeAdapter.php b/src/TypeAdapter/Primitive/ClassProperties/ClassPropertiesPrimitiveTypeAdapter.php index ffa7b85..215f1f0 100644 --- a/src/TypeAdapter/Primitive/ClassProperties/ClassPropertiesPrimitiveTypeAdapter.php +++ b/src/TypeAdapter/Primitive/ClassProperties/ClassPropertiesPrimitiveTypeAdapter.php @@ -10,7 +10,6 @@ use GoodPhp\Serialization\TypeAdapter\Primitive\ClassProperties\Property\BoundClassProperty; use GoodPhp\Serialization\TypeAdapter\Primitive\PrimitiveTypeAdapter; use Illuminate\Support\Arr; -use Illuminate\Support\Collection; /** * @template T of object @@ -20,13 +19,13 @@ final class ClassPropertiesPrimitiveTypeAdapter implements PrimitiveTypeAdapter { /** - * @param class-string $className - * @param Collection> $properties + * @param class-string $className + * @param list> $properties */ public function __construct( private readonly Hydrator $hydrator, private readonly string $className, - private readonly Collection $properties, + private readonly array $properties, ) {} public function serialize(mixed $value): mixed diff --git a/src/TypeAdapter/Primitive/ClassProperties/ClassPropertiesPrimitiveTypeAdapterFactory.php b/src/TypeAdapter/Primitive/ClassProperties/ClassPropertiesPrimitiveTypeAdapterFactory.php index 09a2f5b..1fc94f1 100644 --- a/src/TypeAdapter/Primitive/ClassProperties/ClassPropertiesPrimitiveTypeAdapterFactory.php +++ b/src/TypeAdapter/Primitive/ClassProperties/ClassPropertiesPrimitiveTypeAdapterFactory.php @@ -25,6 +25,9 @@ public function __construct( private readonly BoundClassPropertyFactory $boundClassPropertyFactory, ) {} + /** + * @return ClassPropertiesPrimitiveTypeAdapter|null + */ public function create(string $typeAdapterType, Type $type, Attributes $attributes, Serializer $serializer): ?ClassPropertiesPrimitiveTypeAdapter { if ($typeAdapterType !== PrimitiveTypeAdapter::class || !$type instanceof NamedType) { @@ -43,14 +46,14 @@ public function create(string $typeAdapterType, Type $type, Attributes $attribut return new ClassPropertiesPrimitiveTypeAdapter( $this->hydrator, $className, - $reflection->properties()->map(function (PropertyReflection $property) use ($serializer, $typeAdapterType) { + array_map(function (PropertyReflection $property) use ($serializer, $typeAdapterType) { $serializedName = $this->namingStrategy->translate($property); return PropertyMappingException::rethrow( $serializedName, fn () => $this->boundClassPropertyFactory->create($typeAdapterType, $serializedName, $property, $serializer), ); - }) + }, $reflection->properties()) ); } } diff --git a/src/TypeAdapter/Primitive/ClassProperties/MissingValueException.php b/src/TypeAdapter/Primitive/ClassProperties/MissingValueException.php index e561c10..6330966 100644 --- a/src/TypeAdapter/Primitive/ClassProperties/MissingValueException.php +++ b/src/TypeAdapter/Primitive/ClassProperties/MissingValueException.php @@ -7,7 +7,7 @@ class MissingValueException extends RuntimeException { - public function __construct(Throwable $previous = null) + public function __construct(?Throwable $previous = null) { parent::__construct('Missing value.', 0, $previous); } diff --git a/src/TypeAdapter/Primitive/ClassProperties/Property/Flattening/Flatten.php b/src/TypeAdapter/Primitive/ClassProperties/Property/Flattening/Flatten.php index db89b74..2eebd80 100644 --- a/src/TypeAdapter/Primitive/ClassProperties/Property/Flattening/Flatten.php +++ b/src/TypeAdapter/Primitive/ClassProperties/Property/Flattening/Flatten.php @@ -5,6 +5,4 @@ use Attribute; #[Attribute(Attribute::TARGET_PROPERTY)] -class Flatten -{ -} +class Flatten {} diff --git a/src/TypeAdapter/Primitive/ClassProperties/Property/UseDefaultForUnexpected.php b/src/TypeAdapter/Primitive/ClassProperties/Property/UseDefaultForUnexpected.php index c5690da..a1118ca 100644 --- a/src/TypeAdapter/Primitive/ClassProperties/Property/UseDefaultForUnexpected.php +++ b/src/TypeAdapter/Primitive/ClassProperties/Property/UseDefaultForUnexpected.php @@ -5,6 +5,4 @@ use Attribute; #[Attribute(Attribute::TARGET_PROPERTY)] -class UseDefaultForUnexpected -{ -} +class UseDefaultForUnexpected {} diff --git a/src/TypeAdapter/Primitive/MapperMethods/MapperMethod/InstanceMapperMethod.php b/src/TypeAdapter/Primitive/MapperMethods/MapperMethod/InstanceMapperMethod.php index 79b7cb8..4094f04 100644 --- a/src/TypeAdapter/Primitive/MapperMethods/MapperMethod/InstanceMapperMethod.php +++ b/src/TypeAdapter/Primitive/MapperMethods/MapperMethod/InstanceMapperMethod.php @@ -21,8 +21,8 @@ final class InstanceMapperMethod implements MapperMethod { /** - * @param AdapterType $adapter * @param MethodReflection> $method + * @param AdapterType $adapter */ public function __construct( private readonly MethodReflection $method, @@ -38,7 +38,7 @@ public function accepts(NamedType $type, Attributes $attributes, Serializer $ser public function invoke(mixed $value, Type $type, Attributes $attributes, Serializer $serializer, MapperMethodsPrimitiveTypeAdapterFactory $skipPast): mixed { - $map = [ + $injectables = [ MapperMethodsPrimitiveTypeAdapterFactory::class => $skipPast, Serializer::class => $serializer, Type::class => $type, @@ -50,17 +50,7 @@ public function invoke(mixed $value, Type $type, Attributes $attributes, Seriali return $this->method->invoke( $this->adapter, $value, - ...$this->method - ->parameters() - ->slice(1) - ->map(function (FunctionParameterReflection $parameter) use ($map) { - $type = $parameter->type(); - - Assert::isInstanceOf($type, NamedType::class); - Assert::keyExists($map, $type->name); - - return $map[$type->name]; - }) + ...$this->invokeParameters($injectables), ); } catch (TypeError $e) { if (!str_contains($e->getMessage(), 'Argument #1')) { @@ -68,7 +58,26 @@ public function invoke(mixed $value, Type $type, Attributes $attributes, Seriali } /* @phpstan-ignore-next-line argument.type */ - throw new UnexpectedTypeException($value, $this->method->parameters()->firstOrFail()->type()); + throw new UnexpectedTypeException($value, $this->method->parameters()[0]->type()); } } + + /** + * @param array $injectables + * + * @return list + */ + private function invokeParameters(array $injectables): array + { + $parameters = array_slice($this->method->parameters(), 1); + + return array_map(function (FunctionParameterReflection $parameter) use ($injectables) { + $type = $parameter->type(); + + Assert::isInstanceOf($type, NamedType::class); + Assert::keyExists($injectables, $type->name); + + return $injectables[$type->name]; + }, $parameters); + } } diff --git a/src/TypeAdapter/Primitive/MapperMethods/TypeAdapter/MapperMethodsPrimitiveTypeAdapterFactory.php b/src/TypeAdapter/Primitive/MapperMethods/TypeAdapter/MapperMethodsPrimitiveTypeAdapterFactory.php index ebcb983..48ff28a 100644 --- a/src/TypeAdapter/Primitive/MapperMethods/TypeAdapter/MapperMethodsPrimitiveTypeAdapterFactory.php +++ b/src/TypeAdapter/Primitive/MapperMethods/TypeAdapter/MapperMethodsPrimitiveTypeAdapterFactory.php @@ -9,7 +9,7 @@ use GoodPhp\Serialization\TypeAdapter\Primitive\MapperMethods\MapperMethod\MapperMethod; use GoodPhp\Serialization\TypeAdapter\Primitive\PrimitiveTypeAdapter; use GoodPhp\Serialization\TypeAdapter\TypeAdapterFactory; -use Illuminate\Support\Collection; +use Illuminate\Support\Arr; use Webmozart\Assert\Assert; /** @@ -18,14 +18,17 @@ final class MapperMethodsPrimitiveTypeAdapterFactory implements TypeAdapterFactory { public function __construct( - /** @var Collection */ - private readonly Collection $toMappers, - /** @var Collection */ - private readonly Collection $fromMappers, + /** @var list */ + private readonly array $toMappers, + /** @var list */ + private readonly array $fromMappers, ) { - Assert::true($this->toMappers->isNotEmpty() || $this->fromMappers->isNotEmpty()); + Assert::true($this->toMappers || $this->fromMappers); } + /** + * @return MapperMethodsPrimitiveTypeAdapter|null + */ public function create(string $typeAdapterType, Type $type, Attributes $attributes, Serializer $serializer): ?MapperMethodsPrimitiveTypeAdapter { if ($typeAdapterType !== PrimitiveTypeAdapter::class || !$type instanceof NamedType) { @@ -53,10 +56,10 @@ public function create(string $typeAdapterType, Type $type, Attributes $attribut } /** - * @param Collection $mappers + * @param list $mappers */ - private function findMapper(Collection $mappers, NamedType $type, Attributes $attributes, Serializer $serializer): ?MapperMethod + private function findMapper(array $mappers, NamedType $type, Attributes $attributes, Serializer $serializer): ?MapperMethod { - return $mappers->first(fn (MapperMethod $method) => $method->accepts($type, $attributes, $serializer)); + return Arr::first($mappers, fn (MapperMethod $method) => $method->accepts($type, $attributes, $serializer)); } } diff --git a/src/TypeAdapter/Primitive/MapperMethods/TypeAdapter/MapperMethodsPrimitiveTypeAdapterFactoryFactory.php b/src/TypeAdapter/Primitive/MapperMethods/TypeAdapter/MapperMethodsPrimitiveTypeAdapterFactoryFactory.php index d39d595..1ce875c 100644 --- a/src/TypeAdapter/Primitive/MapperMethods/TypeAdapter/MapperMethodsPrimitiveTypeAdapterFactoryFactory.php +++ b/src/TypeAdapter/Primitive/MapperMethods/TypeAdapter/MapperMethodsPrimitiveTypeAdapterFactoryFactory.php @@ -25,22 +25,26 @@ public function create(object $adapter): MapperMethodsPrimitiveTypeAdapterFactor Assert::isInstanceOf($reflection, ClassReflection::class); return new MapperMethodsPrimitiveTypeAdapterFactory( - $reflection->methods() + collect($reflection->methods()) ->filter(fn (MethodReflection $method) => $method->attributes()->has(MapTo::class)) + ->values() ->map(fn (MethodReflection $method) => $this->mapperMethodFactory->createTo( $adapter, $method, /* @phpstan-ignore-next-line argument.type */ $method->attributes()->sole(MapTo::class), - )), - $reflection->methods() + )) + ->all(), + collect($reflection->methods()) ->filter(fn (MethodReflection $method) => $method->attributes()->has(MapFrom::class)) + ->values() ->map(fn (MethodReflection $method) => $this->mapperMethodFactory->createFrom( $adapter, $method, /* @phpstan-ignore-next-line argument.type */ $method->attributes()->sole(MapFrom::class) - )), + )) + ->all(), ); } } diff --git a/src/TypeAdapter/Primitive/PhpStandard/ValueEnumMapper.php b/src/TypeAdapter/Primitive/PhpStandard/ValueEnumMapper.php index a41ea10..62448f6 100644 --- a/src/TypeAdapter/Primitive/PhpStandard/ValueEnumMapper.php +++ b/src/TypeAdapter/Primitive/PhpStandard/ValueEnumMapper.php @@ -39,6 +39,7 @@ public function to(ValueEnum $value): string|int #[MapFrom(PrimitiveTypeAdapter::class, new BaseTypeAcceptedByAcceptanceStrategy(ValueEnum::class))] public function from(string|int $value, Type $type): ValueEnum { + /** @var class-string> $enumClass */ $enumClass = $type->name; try { diff --git a/src/TypeAdapter/Primitive/Polymorphic/ClassPolymorphicTypeAdapterFactory.php b/src/TypeAdapter/Primitive/Polymorphic/ClassPolymorphicTypeAdapterFactory.php index 482837b..9038030 100644 --- a/src/TypeAdapter/Primitive/Polymorphic/ClassPolymorphicTypeAdapterFactory.php +++ b/src/TypeAdapter/Primitive/Polymorphic/ClassPolymorphicTypeAdapterFactory.php @@ -35,6 +35,9 @@ public static function for(string $parentClassName, string $serializedTypeNameFi return new ClassPolymorphicTypeAdapterFactoryBuilder($parentClassName, $serializedTypeNameField); } + /** + * @return PolymorphicTypeAdapter|null + */ public function create(string $typeAdapterType, Type $type, Attributes $attributes, Serializer $serializer): ?PolymorphicTypeAdapter { if ( diff --git a/src/TypeAdapter/Primitive/PrimitiveTypeAdapter.php b/src/TypeAdapter/Primitive/PrimitiveTypeAdapter.php index d708186..8a08c14 100644 --- a/src/TypeAdapter/Primitive/PrimitiveTypeAdapter.php +++ b/src/TypeAdapter/Primitive/PrimitiveTypeAdapter.php @@ -9,6 +9,4 @@ * * @extends TypeAdapter */ -interface PrimitiveTypeAdapter extends TypeAdapter -{ -} +interface PrimitiveTypeAdapter extends TypeAdapter {} diff --git a/src/TypeAdapter/TypeAdapterFactory.php b/src/TypeAdapter/TypeAdapterFactory.php index c7f76bb..8fb4fa9 100644 --- a/src/TypeAdapter/TypeAdapterFactory.php +++ b/src/TypeAdapter/TypeAdapterFactory.php @@ -15,7 +15,7 @@ interface TypeAdapterFactory /** * @param class-string> $typeAdapterType * - * @return T|null + * @return TypeAdapter|null */ public function create(string $typeAdapterType, Type $type, Attributes $attributes, Serializer $serializer): ?TypeAdapter; } diff --git a/tests/Integration/JsonSerializationTest.php b/tests/Integration/JsonSerializationTest.php index b975e74..5991e39 100644 --- a/tests/Integration/JsonSerializationTest.php +++ b/tests/Integration/JsonSerializationTest.php @@ -3,8 +3,8 @@ namespace Tests\Integration; use Carbon\CarbonImmutable; +use DateMalformedStringException; use DateTime; -use Exception; use GoodPhp\Reflection\Type\Combinatorial\UnionType; use GoodPhp\Reflection\Type\NamedType; use GoodPhp\Reflection\Type\PrimitiveType; @@ -23,6 +23,7 @@ use GoodPhp\Serialization\TypeAdapter\Primitive\ClassProperties\PropertyMappingException; use GoodPhp\Serialization\TypeAdapter\Primitive\Polymorphic\ClassPolymorphicTypeAdapterFactory; use Illuminate\Support\Collection; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use Tests\Stubs\BackedEnumStub; use Tests\Stubs\ClassStub; @@ -36,9 +37,7 @@ class JsonSerializationTest extends TestCase { - /** - * @dataProvider serializesProvider - */ + #[DataProvider('serializesProvider')] public function testSerializes(string|Type $type, mixed $data, string $expectedSerialized): void { $adapter = $this->serializer()->adapter(JsonTypeAdapter::class, $type); @@ -149,13 +148,10 @@ public static function serializesProvider(): iterable ]; yield 'Collection of DateTime' => [ - new NamedType( - Collection::class, - new Collection([ - PrimitiveType::integer(), - new NamedType(DateTime::class), - ]) - ), + new NamedType(Collection::class, [ + PrimitiveType::integer(), + new NamedType(DateTime::class), + ]), new Collection([new DateTime('2020-01-01 00:00:00')]), <<<'JSON' [ @@ -165,12 +161,9 @@ public static function serializesProvider(): iterable ]; yield 'ClassStub with all fields' => [ - new NamedType( - ClassStub::class, - new Collection([ - new NamedType(DateTime::class), - ]) - ), + new NamedType(ClassStub::class, [ + new NamedType(DateTime::class), + ]), new ClassStub( 1, new NestedStub(), @@ -218,9 +211,9 @@ public static function serializesProvider(): iterable yield 'ClassStub with empty optional and null nullable' => [ new NamedType( ClassStub::class, - new Collection([ + [ new NamedType(DateTime::class), - ]) + ] ), new ClassStub( 1, @@ -249,9 +242,7 @@ public static function serializesProvider(): iterable ]; } - /** - * @dataProvider deserializesProvider - */ + #[DataProvider('deserializesProvider')] public function testDeserializes(string|Type $type, mixed $expectedData, string $serialized): void { $adapter = $this->serializer()->adapter(JsonTypeAdapter::class, $type); @@ -372,10 +363,10 @@ public static function deserializesProvider(): iterable yield 'Collection of DateTime' => [ new NamedType( Collection::class, - new Collection([ + [ PrimitiveType::integer(), new NamedType(DateTime::class), - ]) + ] ), new Collection([new DateTime('2020-01-01 00:00:00')]), <<<'JSON' @@ -388,9 +379,9 @@ public static function deserializesProvider(): iterable yield 'ClassStub with all fields' => [ new NamedType( ClassStub::class, - new Collection([ + [ new NamedType(DateTime::class), - ]) + ] ), new ClassStub( 1, @@ -439,9 +430,9 @@ public static function deserializesProvider(): iterable yield 'ClassStub with empty optional and null nullable' => [ new NamedType( ClassStub::class, - new Collection([ + [ new NamedType(DateTime::class), - ]) + ] ), new ClassStub( 1, @@ -468,12 +459,9 @@ public static function deserializesProvider(): iterable ]; yield 'ClassStub with the least default fields' => [ - new NamedType( - ClassStub::class, - new Collection([ - new NamedType(DateTime::class), - ]) - ), + new NamedType(ClassStub::class, [ + new NamedType(DateTime::class), + ]), new ClassStub( 1, new NestedStub(), @@ -517,9 +505,7 @@ public static function deserializesProvider(): iterable ]; } - /** - * @dataProvider deserializesWithAnExceptionProvider - */ + #[DataProvider('deserializesWithAnExceptionProvider')] public function testDeserializesWithAnException(Throwable $expectedException, string|Type $type, string $serialized): void { $adapter = $this->serializer()->adapter(JsonTypeAdapter::class, $type); @@ -583,16 +569,18 @@ public static function deserializesWithAnExceptionProvider(): iterable JSON, ]; - yield 'DateTime' => [ - new Exception('Failed to parse time string (2020 dasd) at position 5 (d): The timezone could not be found in the database'), - DateTime::class, - <<<'JSON' - "2020 dasd" - JSON, - ]; + if (version_compare(PHP_VERSION, '8.3', '>=')) { + yield 'DateTime' => [ + new DateMalformedStringException('Failed to parse time string (2020 dasd) at position 5 (d): The timezone could not be found in the database'), + DateTime::class, + <<<'JSON' + "2020 dasd" + JSON, + ]; + } yield 'backed enum type' => [ - new UnexpectedTypeException(true, new UnionType(new Collection([PrimitiveType::string(), PrimitiveType::integer()]))), + new UnexpectedTypeException(true, new UnionType([PrimitiveType::string(), PrimitiveType::integer()])), BackedEnumStub::class, <<<'JSON' true @@ -608,7 +596,7 @@ public static function deserializesWithAnExceptionProvider(): iterable ]; yield 'value enum type' => [ - new UnexpectedTypeException(true, new UnionType(new Collection([PrimitiveType::string(), PrimitiveType::integer()]))), + new UnexpectedTypeException(true, new UnionType([PrimitiveType::string(), PrimitiveType::integer()])), ValueEnumStub::class, <<<'JSON' true @@ -623,15 +611,17 @@ public static function deserializesWithAnExceptionProvider(): iterable JSON, ]; - yield 'array of DateTime #1' => [ - new CollectionItemMappingException(0, new Exception('Failed to parse time string (2020 dasd) at position 5 (d): The timezone could not be found in the database')), - PrimitiveType::array( - new NamedType(DateTime::class) - ), - <<<'JSON' - ["2020 dasd"] - JSON, - ]; + if (version_compare(PHP_VERSION, '8.3', '>=')) { + yield 'array of DateTime #1' => [ + new CollectionItemMappingException(0, new DateMalformedStringException('Failed to parse time string (2020 dasd) at position 5 (d): The timezone could not be found in the database')), + PrimitiveType::array( + new NamedType(DateTime::class) + ), + <<<'JSON' + ["2020 dasd"] + JSON, + ]; + } yield 'array of DateTime #2' => [ new CollectionItemMappingException(1, new UnexpectedTypeException(null, PrimitiveType::string())), @@ -660,10 +650,10 @@ public static function deserializesWithAnExceptionProvider(): iterable new CollectionItemMappingException(0, new UnexpectedTypeException(null, PrimitiveType::string())), new NamedType( Collection::class, - new Collection([ + [ PrimitiveType::integer(), new NamedType(DateTime::class), - ]) + ] ), <<<'JSON' [null] @@ -677,10 +667,10 @@ public static function deserializesWithAnExceptionProvider(): iterable ]), new NamedType( Collection::class, - new Collection([ + [ PrimitiveType::integer(), new NamedType(DateTime::class), - ]) + ] ), <<<'JSON' [null, null] @@ -691,9 +681,9 @@ public static function deserializesWithAnExceptionProvider(): iterable new PropertyMappingException('primitive', new UnexpectedTypeException('1', PrimitiveType::integer())), new NamedType( ClassStub::class, - new Collection([ + [ new NamedType(DateTime::class), - ]) + ] ), <<<'JSON' { @@ -711,9 +701,9 @@ public static function deserializesWithAnExceptionProvider(): iterable new PropertyMappingException('nested.Field', new UnexpectedTypeException(123, PrimitiveType::string())), new NamedType( ClassStub::class, - new Collection([ + [ new NamedType(DateTime::class), - ]) + ] ), <<<'JSON' { diff --git a/tests/Stubs/ClassStub.php b/tests/Stubs/ClassStub.php index 9e1959e..c767574 100644 --- a/tests/Stubs/ClassStub.php +++ b/tests/Stubs/ClassStub.php @@ -21,7 +21,7 @@ public function __construct( public NestedStub $nested, #[SerializedName('date')] public mixed $generic, - public int|null|MissingValue $optional, + public int|MissingValue|null $optional, public ?int $nullable, public int|MissingValue $nonNullOptional, #[Flatten] diff --git a/tests/Stubs/Polymorphic/Change.php b/tests/Stubs/Polymorphic/Change.php index 10135e2..b1fe820 100644 --- a/tests/Stubs/Polymorphic/Change.php +++ b/tests/Stubs/Polymorphic/Change.php @@ -2,6 +2,4 @@ namespace Tests\Stubs\Polymorphic; -interface Change -{ -} +interface Change {}