From 6813838029b253abff8638df826cbd3923149eba Mon Sep 17 00:00:00 2001 From: sakulb Date: Fri, 21 Feb 2025 15:57:45 +0100 Subject: [PATCH 1/9] Add support for PHP8.4 property hooks. --- src/Metadata/Metadata.php | 1 + src/Metadata/MetadataFactory.php | 45 +++++++++++++++++++++----------- src/Service/JsonDeserializer.php | 2 +- src/Service/JsonSerializer.php | 2 +- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/Metadata/Metadata.php b/src/Metadata/Metadata.php index b10e7bc..536f5d1 100644 --- a/src/Metadata/Metadata.php +++ b/src/Metadata/Metadata.php @@ -24,6 +24,7 @@ public function __construct( public ?string $persistedName = null, public ?array $discriminatorMap = null, public ?array $orderBy = null, + public bool $getterSetterStrategy = true, ) { } } diff --git a/src/Metadata/MetadataFactory.php b/src/Metadata/MetadataFactory.php index 0b575de..3ad3b78 100644 --- a/src/Metadata/MetadataFactory.php +++ b/src/Metadata/MetadataFactory.php @@ -15,6 +15,7 @@ use ReflectionUnionType; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\PropertyInfo\Type; +use const PHP_VERSION; final class MetadataFactory { @@ -171,7 +172,7 @@ private function getMethodMetadata(ReflectionMethod $method, Serialize $attribut $attribute->handler, $this->resolveCustomType($attribute), $attribute->strategy, - orderBy: $attribute->orderBy + orderBy: $attribute->orderBy, ); } @@ -201,21 +202,34 @@ private function getPropertyMetadata(ReflectionProperty $property, Serialize $at } } } - $getter = $getterPrefix . ucfirst($property->getName()); - $declaringClass = $property->getDeclaringClass(); - if (false === $declaringClass->hasMethod($getter)) { - // fallback to "get" prefix - $getterFallback = 'get' . ucfirst($property->getName()); - if (false === $declaringClass->hasMethod($getterFallback)) { - throw new SerializerException('Getter method ' . $getter . ' or ' . $getterFallback . ' not found in ' . $declaringClass->getName() . '.'); + $getter = $setter = null; + $getterSetterStrategy = true; + if (version_compare(PHP_VERSION, '8.4.0', '>=') && $property->hasHooks()) { + $getterSetterStrategy = false; + if ($property->hasHook(\PropertyHookType::Get)) { + $getter = $property->getName(); + } + if ($property->hasHook(\PropertyHookType::Set)) { + $setter = $property->getName(); } - - $getter = $getterFallback; } - $setter = 'set' . ucfirst($property->getName()); - if (false === $declaringClass->hasMethod($setter)) { - // setter is required for deserialization only - $setter = null; + if ($getterSetterStrategy) { + $getter = $getterPrefix . ucfirst($property->getName()); + $declaringClass = $property->getDeclaringClass(); + if (false === $declaringClass->hasMethod($getter)) { + // fallback to "get" prefix + $getterFallback = 'get' . ucfirst($property->getName()); + if (false === $declaringClass->hasMethod($getterFallback)) { + throw new SerializerException('Getter method ' . $getter . ' or ' . $getterFallback . ' not found in ' . $declaringClass->getName() . '.'); + } + + $getter = $getterFallback; + } + $setter = 'set' . ucfirst($property->getName()); + if (false === $declaringClass->hasMethod($setter)) { + // setter is required for deserialization only + $setter = null; + } } return new Metadata( @@ -229,7 +243,8 @@ private function getPropertyMetadata(ReflectionProperty $property, Serialize $at $attribute->strategy, $attribute->persistedName, $attribute->discriminatorMap, - orderBy: $attribute->orderBy + orderBy: $attribute->orderBy, + getterSetterStrategy: $getterSetterStrategy, ); } diff --git a/src/Service/JsonDeserializer.php b/src/Service/JsonDeserializer.php index 2ac15f8..ac12225 100644 --- a/src/Service/JsonDeserializer.php +++ b/src/Service/JsonDeserializer.php @@ -93,7 +93,7 @@ private function arrayToObject(array $data, string $className): object } try { - $object->{$metadata->setter}($value); + $metadata->getterSetterStrategy ? $object->{$metadata->setter}($value) : $object->{$metadata->property} = $value; } catch (Throwable $exception) { throw new SerializerException('Unable to deserialize "' . $name . '". Check type.', 0, $exception); } diff --git a/src/Service/JsonSerializer.php b/src/Service/JsonSerializer.php index 6e4c19c..bc85a0a 100644 --- a/src/Service/JsonSerializer.php +++ b/src/Service/JsonSerializer.php @@ -78,7 +78,7 @@ private function objectToArray(object $data, SerializationContext $context): arr { $output = []; foreach ($this->metadataRegistry->get($data::class)->getAll() as $name => $metadata) { - $value = $data->{$metadata->getter}(); + $value = $metadata->getterSetterStrategy ? $data->{$metadata->getter}() : $data->{$metadata->property}; if (null === $value && !$context->shouldSerializeNull()) { continue; From 6b3605e70881ff36926cb94dad1a627a3d3efe5f Mon Sep 17 00:00:00 2001 From: sakulb Date: Fri, 26 Jul 2024 11:19:11 +0200 Subject: [PATCH 2/9] Sanitize cache keys. --- src/Metadata/MetadataRegistry.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Metadata/MetadataRegistry.php b/src/Metadata/MetadataRegistry.php index 4935b15..5e10d39 100644 --- a/src/Metadata/MetadataRegistry.php +++ b/src/Metadata/MetadataRegistry.php @@ -39,7 +39,7 @@ public function get(string $className): ClassMetadata } if (false === isset($this->metadata[$className])) { try { - $cachedItem = $this->appCache->getItem(self::CACHE_PREFIX . $className); + $cachedItem = $this->appCache->getItem($this->getCacheKey($className)); if ($cachedItem->isHit()) { $this->metadata[$className] = $cachedItem->get(); @@ -55,4 +55,9 @@ public function get(string $className): ClassMetadata return $this->metadata[$className]; } + + private function getCacheKey(string $className): string + { + return self::CACHE_PREFIX . str_replace('\\', '_', $className); + } } From af09a22588aa26e22fe28c1d2fec4f15981d8a00 Mon Sep 17 00:00:00 2001 From: sakulb Date: Fri, 21 Jun 2024 12:34:18 +0200 Subject: [PATCH 3/9] Serialize nested arrays of objects into proper types. --- src/Handler/Handlers/ObjectHandler.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Handler/Handlers/ObjectHandler.php b/src/Handler/Handlers/ObjectHandler.php index b525e72..066e165 100644 --- a/src/Handler/Handlers/ObjectHandler.php +++ b/src/Handler/Handlers/ObjectHandler.php @@ -79,6 +79,14 @@ public function deserialize(mixed $value, Metadata $metadata): object|iterable return $collection; } if (Type::BUILTIN_TYPE_ARRAY === $metadata->type) { + if ($metadata->customType || $metadata->discriminatorMap) { + $array = []; + foreach ($value as $key => $item) { + $array[$key] = $this->jsonDeserializer->fromArray($item, $this->getDeserializeCustomType($item, $metadata) ?? $metadata->type); + } + + return $array; + } return $value; } From 9fbc3aee794061fbcf11212f02cf28fa3d8bbfcf Mon Sep 17 00:00:00 2001 From: Ronald Marfoldi Date: Tue, 25 Feb 2025 09:43:59 +0100 Subject: [PATCH 4/9] Add PHP 8.4 to ci --- .github/workflows/php.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index bb62ffe..21d593a 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -11,9 +11,11 @@ jobs: matrix: include: - php-version: 8.2 - docker-image: 'anzusystems/php:3.0.0-php82-cli' + docker-image: 'anzusystems/php:4.0.0-php82-cli' - php-version: 8.3 - docker-image: 'anzusystems/php:3.0.0-php83-cli' + docker-image: 'anzusystems/php:4.0.0-php83-cli' + - php-version: 8.4 + docker-image: 'anzusystems/php:4.0.0-php84-cli' services: mysql: From 6bd723b2ce2adaf8d3f25d885d322532f183b263 Mon Sep 17 00:00:00 2001 From: Ronald Marfoldi Date: Tue, 25 Feb 2025 10:01:40 +0100 Subject: [PATCH 5/9] Fixes --- Dockerfile | 2 +- composer.json | 2 +- psalm.xml | 1 + src/Handler/Handlers/ObjectHandler.php | 2 ++ src/Metadata/MetadataFactory.php | 4 ++-- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index c16b04b..e4229e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM anzusystems/php:3.0.0-php83-cli +FROM anzusystems/php:4.0.0-php84-cli # ### Basic arguments and variables ARG DOCKER_USER_ID diff --git a/composer.json b/composer.json index b9b7a0d..c14220a 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "symfony/test-pack": "^1.1", "symfony/uid": "^6.3|^7.0", "symplify/easy-coding-standard": "^12.0", - "vimeo/psalm": "^5.16" + "vimeo/psalm": "^6.8" }, "suggest": { "doctrine/orm": "Enable EntityIdHandler." diff --git a/psalm.xml b/psalm.xml index 8131f92..39eb56d 100644 --- a/psalm.xml +++ b/psalm.xml @@ -24,6 +24,7 @@ + diff --git a/src/Handler/Handlers/ObjectHandler.php b/src/Handler/Handlers/ObjectHandler.php index 066e165..1c2129e 100644 --- a/src/Handler/Handlers/ObjectHandler.php +++ b/src/Handler/Handlers/ObjectHandler.php @@ -82,11 +82,13 @@ public function deserialize(mixed $value, Metadata $metadata): object|iterable if ($metadata->customType || $metadata->discriminatorMap) { $array = []; foreach ($value as $key => $item) { + /** @psalm-suppress ArgumentTypeCoercion */ $array[$key] = $this->jsonDeserializer->fromArray($item, $this->getDeserializeCustomType($item, $metadata) ?? $metadata->type); } return $array; } + return $value; } diff --git a/src/Metadata/MetadataFactory.php b/src/Metadata/MetadataFactory.php index 3ad3b78..dd9f814 100644 --- a/src/Metadata/MetadataFactory.php +++ b/src/Metadata/MetadataFactory.php @@ -131,7 +131,7 @@ private function buildMethodMetadata(ReflectionClass $reflection): array /** @var Serialize $attribute */ $attribute = $attributes[0]->newInstance(); $dataName = $attribute->serializedName - ?? lcfirst(preg_replace('~^[get|is]*(.+)~', '$1', $method->getName())) + ?? lcfirst((string) preg_replace('~^[get|is]*(.+)~', '$1', $method->getName())) ; $metadata[$dataName] = $this->getMethodMetadata($method, $attribute); } @@ -235,7 +235,7 @@ private function getPropertyMetadata(ReflectionProperty $property, Serialize $at return new Metadata( $type, (bool) $propertyType?->allowsNull(), - $getter, + (string) $getter, $property->getName(), $setter, $attribute->handler, From a84141b579a945a32050e3c373e91758017a0fe1 Mon Sep 17 00:00:00 2001 From: Ronald Marfoldi Date: Tue, 25 Feb 2025 10:03:22 +0100 Subject: [PATCH 6/9] Fixes --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 21d593a..a44e4ff 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -11,7 +11,7 @@ jobs: matrix: include: - php-version: 8.2 - docker-image: 'anzusystems/php:4.0.0-php82-cli' + docker-image: 'anzusystems/php:3.0.0-php82-cli' - php-version: 8.3 docker-image: 'anzusystems/php:4.0.0-php83-cli' - php-version: 8.4 From 8946fe12a3eebaf9957560135e134bd511ae339c Mon Sep 17 00:00:00 2001 From: Ronald Marfoldi Date: Tue, 25 Feb 2025 10:06:02 +0100 Subject: [PATCH 7/9] Fixes --- .github/workflows/php.yml | 2 -- composer.json | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index a44e4ff..724609b 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -10,8 +10,6 @@ jobs: strategy: matrix: include: - - php-version: 8.2 - docker-image: 'anzusystems/php:3.0.0-php82-cli' - php-version: 8.3 docker-image: 'anzusystems/php:4.0.0-php83-cli' - php-version: 8.4 diff --git a/composer.json b/composer.json index c14220a..9045b3d 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ } ], "require": { - "php": ">=8.2", + "php": ">=8.3", "ext-json": "*", "doctrine/common": "^3.3", "symfony/property-info": "^6.3|^7.0" From 4ee3b8f633b10c3b5bbcd1c76aab229fb40250f5 Mon Sep 17 00:00:00 2001 From: Ronald Marfoldi Date: Tue, 25 Feb 2025 10:09:14 +0100 Subject: [PATCH 8/9] Fixes --- psalm.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/psalm.xml b/psalm.xml index 39eb56d..4621292 100644 --- a/psalm.xml +++ b/psalm.xml @@ -7,6 +7,7 @@ usePhpDocMethodsWithoutMagicCall="true" allowStringToStandInForClass="false" memoizeMethodCallResults="true" + phpVersion="8.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" From 0ca6536cf1622fbfa0504d031180ebb2bf0b9535 Mon Sep 17 00:00:00 2001 From: Ronald Marfoldi Date: Tue, 25 Feb 2025 10:13:19 +0100 Subject: [PATCH 9/9] Fixes --- psalm.xml | 14 -------------- src/Metadata/MetadataFactory.php | 2 ++ 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/psalm.xml b/psalm.xml index 4621292..9f1c9bf 100644 --- a/psalm.xml +++ b/psalm.xml @@ -28,20 +28,6 @@ - - - - - - - - - - - - - - diff --git a/src/Metadata/MetadataFactory.php b/src/Metadata/MetadataFactory.php index dd9f814..1854804 100644 --- a/src/Metadata/MetadataFactory.php +++ b/src/Metadata/MetadataFactory.php @@ -206,9 +206,11 @@ private function getPropertyMetadata(ReflectionProperty $property, Serialize $at $getterSetterStrategy = true; if (version_compare(PHP_VERSION, '8.4.0', '>=') && $property->hasHooks()) { $getterSetterStrategy = false; + /** @psalm-suppress UndefinedClass */ if ($property->hasHook(\PropertyHookType::Get)) { $getter = $property->getName(); } + /** @psalm-suppress UndefinedClass */ if ($property->hasHook(\PropertyHookType::Set)) { $setter = $property->getName(); }