From 717d0742b7640c50230febbdcdf434a7730b3d13 Mon Sep 17 00:00:00 2001 From: Matej Malicky Date: Wed, 5 Jun 2024 13:03:41 +0200 Subject: [PATCH 01/13] 4 Support symfony 7.1 - add support for Symfony 7.1 --- composer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index d55ab2b..98001cb 100644 --- a/composer.json +++ b/composer.json @@ -30,12 +30,12 @@ "php-http/client-common": "^2.1", "php-http/discovery": "^1.7", "psr/http-factory-implementation": "^1.0", - "symfony/console": "^5.4|^6.4", - "symfony/serializer": "^5.4|^6.4", - "symfony/property-access": "^5.4|^6.4", - "symfony/property-info": "^5.4|^6.4", - "symfony/string": "^5.4|^6.4", - "symfony/http-kernel": "^5.4|^6.4", + "symfony/console": "^5.4|^6.4|^7.1", + "symfony/serializer": "^5.4|^6.4|^7.1", + "symfony/property-access": "^5.4|^6.4|^7.1", + "symfony/property-info": "^5.4|^6.4|^7.1", + "symfony/string": "^5.4|^6.4|^7.1", + "symfony/http-kernel": "^5.4|^6.4|^7.1", "php-http/logger-plugin": "^1.1", "prometee/php-class-generator": "^1.0", "doctrine/annotations": "^1.13|^2.0", From 42e3472d4dd57d1b24340a7b8047175c97f849fe Mon Sep 17 00:00:00 2001 From: Francis Hilaire Date: Wed, 5 Jun 2024 13:25:24 +0200 Subject: [PATCH 02/13] Add Symfony 7.1 to the test build --- .github/workflows/build.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 550e072..9a0ce34 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,8 +28,12 @@ jobs: fail-fast: false matrix: odoo: [13, 14, 15, 16, 17] - symfony: ["^5.4", "^6.4"] + symfony: ["^5.4", "^6.4", "^7.1"] php: ["8.1", "8.2", "8.3"] # " required to keep the .0 + exclude: + - + symfony: ^7.1 + php: "8.1" services: postgres: image: postgres From b32704cfe575434cb5f3466fc449a958acb1a44b Mon Sep 17 00:00:00 2001 From: Francis Hilaire Date: Wed, 5 Jun 2024 17:00:23 +0200 Subject: [PATCH 03/13] Move constant --- src/Manager/ModelManager.php | 4 ++-- src/Serializer/OdooNormalizer.php | 2 -- src/Serializer/OdooRelationsNormalizer.php | 4 +++- tests/Serializer/OdooNormalizerTest.php | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Manager/ModelManager.php b/src/Manager/ModelManager.php index 9690f64..d5be41b 100644 --- a/src/Manager/ModelManager.php +++ b/src/Manager/ModelManager.php @@ -7,7 +7,7 @@ use FluxSE\OdooApiClient\Model\BaseInterface; use FluxSE\OdooApiClient\Operations\Object\ExecuteKw\Options\OptionsInterface; use FluxSE\OdooApiClient\Operations\Object\ExecuteKw\RecordOperationsInterface; -use FluxSE\OdooApiClient\Serializer\OdooNormalizer; +use FluxSE\OdooApiClient\Serializer\OdooRelationsNormalizer; use LogicException; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; @@ -38,7 +38,7 @@ public function update(BaseInterface $model, ?OptionsInterface $options = null): } $normalizedModel = (array) $this->normalizer->normalize($model, null, [ - OdooNormalizer::NORMALIZE_FOR_UPDATE => true, + OdooRelationsNormalizer::NORMALIZE_FOR_UPDATE => true, ]); return $this->recordOperations->write( $model::getOdooModelName(), diff --git a/src/Serializer/OdooNormalizer.php b/src/Serializer/OdooNormalizer.php index ba4c0dc..1b993e7 100644 --- a/src/Serializer/OdooNormalizer.php +++ b/src/Serializer/OdooNormalizer.php @@ -14,8 +14,6 @@ final class OdooNormalizer extends ObjectNormalizer { - public const NORMALIZE_FOR_UPDATE = 'normalize_for_update'; - public function getSupportedTypes(?string $format): array { return [ diff --git a/src/Serializer/OdooRelationsNormalizer.php b/src/Serializer/OdooRelationsNormalizer.php index ebd66de..40a89d4 100644 --- a/src/Serializer/OdooRelationsNormalizer.php +++ b/src/Serializer/OdooRelationsNormalizer.php @@ -15,6 +15,8 @@ final class OdooRelationsNormalizer implements NormalizerInterface, NormalizerAw { use NormalizerAwareTrait; + public const NORMALIZE_FOR_UPDATE = 'normalize_for_update'; + public function getSupportedTypes(?string $format): array { return ['native-array' => true]; @@ -26,7 +28,7 @@ public function supportsNormalization($data, string $format = null, array $conte return false; } - $normalizeForUpdate = $context[OdooNormalizer::NORMALIZE_FOR_UPDATE] ?? false; + $normalizeForUpdate = $context[self::NORMALIZE_FOR_UPDATE] ?? false; if (!$normalizeForUpdate) { return false; } diff --git a/tests/Serializer/OdooNormalizerTest.php b/tests/Serializer/OdooNormalizerTest.php index b7426af..6e24f6b 100644 --- a/tests/Serializer/OdooNormalizerTest.php +++ b/tests/Serializer/OdooNormalizerTest.php @@ -4,7 +4,7 @@ use FluxSE\OdooApiClient\Model\OdooRelation; use FluxSE\OdooApiClient\Serializer\Factory\SerializerFactory; -use FluxSE\OdooApiClient\Serializer\OdooNormalizer; +use FluxSE\OdooApiClient\Serializer\OdooRelationsNormalizer; use PHPUnit\Framework\TestCase; use Symfony\Component\Serializer\Serializer; use Tests\FluxSE\OdooApiClient\TestModel\Object\Res\Partner; @@ -33,7 +33,7 @@ public function testNormalizeForUpdate(): void ]); $arr = $this->serializer->normalize($object, null, [ - OdooNormalizer::NORMALIZE_FOR_UPDATE => true, + OdooRelationsNormalizer::NORMALIZE_FOR_UPDATE => true, ]); $this->assertEquals([ @@ -55,7 +55,7 @@ public function testNormalizeForUpdateWithNullData(): void ]); $arr = $this->serializer->normalize($object, null, [ - OdooNormalizer::NORMALIZE_FOR_UPDATE => true, + OdooRelationsNormalizer::NORMALIZE_FOR_UPDATE => true, ]); $this->assertEquals([ From bfeba754d9e1ed708e884e10dd615d288eb1bd02 Mon Sep 17 00:00:00 2001 From: Francis Hilaire Date: Wed, 5 Jun 2024 17:01:40 +0200 Subject: [PATCH 04/13] Signature update --- src/Serializer/JsonRpc/JsonRpcDecoder.php | 2 +- src/Serializer/NullOdooRelationDenormalizer.php | 4 ++-- src/Serializer/NullableDateTimeDenormalizer.php | 4 ++-- src/Serializer/OdooRelationDenormalizer.php | 2 +- src/Serializer/OdooRelationNormalizer.php | 2 +- src/Serializer/OdooRelationSingleDenormalizer.php | 2 +- src/Serializer/OdooRelationsDenormalizer.php | 2 +- src/Serializer/OdooRelationsNormalizer.php | 3 +-- src/Serializer/XmlRpc/XmlRpcDecoder.php | 2 +- 9 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Serializer/JsonRpc/JsonRpcDecoder.php b/src/Serializer/JsonRpc/JsonRpcDecoder.php index 7bc8a0b..3de4e41 100644 --- a/src/Serializer/JsonRpc/JsonRpcDecoder.php +++ b/src/Serializer/JsonRpc/JsonRpcDecoder.php @@ -33,7 +33,7 @@ public function supportsDecoding($format, array $context = []): bool * * @throws JsonException */ - public function decode($data, $format, array $context = []) + public function decode(string $data, string $format, array $context = []): mixed { if ('' === trim($data)) { throw new UnexpectedValueException('Invalid JSON data, it can not be empty.'); diff --git a/src/Serializer/NullOdooRelationDenormalizer.php b/src/Serializer/NullOdooRelationDenormalizer.php index fa14662..9323464 100644 --- a/src/Serializer/NullOdooRelationDenormalizer.php +++ b/src/Serializer/NullOdooRelationDenormalizer.php @@ -18,7 +18,7 @@ public function getSupportedTypes(?string $format): array return [OdooRelation::class => false]; } - public function supportsDenormalization($data, $type, $format = null): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { if ($type !== OdooRelation::class) { return false; @@ -28,7 +28,7 @@ public function supportsDenormalization($data, $type, $format = null): bool return false === $data; } - public function denormalize($data, $type, $format = null, array $context = []) + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { if (false === $data) { return null; diff --git a/src/Serializer/NullableDateTimeDenormalizer.php b/src/Serializer/NullableDateTimeDenormalizer.php index e4781ee..361a8e2 100644 --- a/src/Serializer/NullableDateTimeDenormalizer.php +++ b/src/Serializer/NullableDateTimeDenormalizer.php @@ -21,12 +21,12 @@ public function getSupportedTypes(?string $format): array ]; } - public function denormalize($data, string $type, string $format = null, array $context = []) + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { return null; } - public function supportsDenormalization($data, string $type, string $format = null): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { if (!\is_string($data) || '' === trim($data)) { return $this->dateTimeNormalizer->supportsDenormalization($data, $type, $format); diff --git a/src/Serializer/OdooRelationDenormalizer.php b/src/Serializer/OdooRelationDenormalizer.php index f40f000..f725dc9 100644 --- a/src/Serializer/OdooRelationDenormalizer.php +++ b/src/Serializer/OdooRelationDenormalizer.php @@ -18,7 +18,7 @@ public function getSupportedTypes(?string $format): array return [OdooRelation::class => false]; } - public function supportsDenormalization($data, $type, $format = null): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { if ($type !== OdooRelation::class) { return false; diff --git a/src/Serializer/OdooRelationNormalizer.php b/src/Serializer/OdooRelationNormalizer.php index df1286a..0d67c20 100644 --- a/src/Serializer/OdooRelationNormalizer.php +++ b/src/Serializer/OdooRelationNormalizer.php @@ -20,7 +20,7 @@ public function getSupportedTypes(?string $format): array return [OdooRelation::class => true]; } - public function supportsNormalization($data, $format = null): bool + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { return $data instanceof OdooRelation; } diff --git a/src/Serializer/OdooRelationSingleDenormalizer.php b/src/Serializer/OdooRelationSingleDenormalizer.php index 32b43d8..fa8db53 100644 --- a/src/Serializer/OdooRelationSingleDenormalizer.php +++ b/src/Serializer/OdooRelationSingleDenormalizer.php @@ -18,7 +18,7 @@ public function getSupportedTypes(?string $format): array return [OdooRelation::class => false]; } - public function supportsDenormalization($data, $type, $format = null): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { if ($type !== OdooRelation::class) { return false; diff --git a/src/Serializer/OdooRelationsDenormalizer.php b/src/Serializer/OdooRelationsDenormalizer.php index d324b24..838fb96 100644 --- a/src/Serializer/OdooRelationsDenormalizer.php +++ b/src/Serializer/OdooRelationsDenormalizer.php @@ -18,7 +18,7 @@ public function getSupportedTypes(?string $format): array return [OdooRelation::class => false]; } - public function supportsDenormalization($data, $type, $format = null): bool + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { if ($type !== OdooRelation::class) { return false; diff --git a/src/Serializer/OdooRelationsNormalizer.php b/src/Serializer/OdooRelationsNormalizer.php index 40a89d4..3e9a38c 100644 --- a/src/Serializer/OdooRelationsNormalizer.php +++ b/src/Serializer/OdooRelationsNormalizer.php @@ -6,12 +6,11 @@ use FluxSE\OdooApiClient\Model\OdooRelation; use Symfony\Component\Serializer\Exception\NotNormalizableValueException; -use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -final class OdooRelationsNormalizer implements NormalizerInterface, NormalizerAwareInterface, ContextAwareNormalizerInterface +final class OdooRelationsNormalizer implements NormalizerInterface, NormalizerAwareInterface { use NormalizerAwareTrait; diff --git a/src/Serializer/XmlRpc/XmlRpcDecoder.php b/src/Serializer/XmlRpc/XmlRpcDecoder.php index 439f6ed..ad33556 100644 --- a/src/Serializer/XmlRpc/XmlRpcDecoder.php +++ b/src/Serializer/XmlRpc/XmlRpcDecoder.php @@ -30,7 +30,7 @@ public function supportsDecoding($format, array $context = []): bool /** * @return array|integer|string|boolean */ - public function decode($data, $format, array $context = []) + public function decode(string $data, string $format, array $context = []): mixed { if ('' === trim($data)) { throw new UnexpectedValueException('Invalid XML data, it can not be empty.'); From 69e7b514b9994abca8072e2782001b20d352e008 Mon Sep 17 00:00:00 2001 From: Francis Hilaire Date: Wed, 5 Jun 2024 17:02:03 +0200 Subject: [PATCH 05/13] Fix using final class --- phpstan.neon.dist | 13 ++-- src/PropertyAccess/OdooPropertyAccessor.php | 73 +++++++++++++++++++ src/Serializer/Factory/SerializerFactory.php | 69 ++++++++++-------- src/Serializer/OdooNormalizer.php | 75 +++++++------------- 4 files changed, 144 insertions(+), 86 deletions(-) create mode 100644 src/PropertyAccess/OdooPropertyAccessor.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index bf149d7..933d936 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -12,15 +12,18 @@ parameters: - identifier: missingType.iterableValue - '/Method FluxSE\\OdooApiClient\\Manager\\ModelListManager::(find|findBy|findByIds)\(\) should return (array<|\()T of FluxSE\\OdooApiClient\\Model\\BaseInterface(>|\)\|null) but returns mixed\./' - '/Method FluxSE\\OdooApiClient\\Serializer\\(Json|Xml)Rpc\\(Json|Xml)RpcSerializerHelper::decodeResponseBody\(\) should return array\|bool\|int\|string but returns mixed\./' - - - - message: '/Class FluxSE\\OdooApiClient\\Serializer\\OdooNormalizer extends \@final class Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer\./' - count: 1 - path: src/Serializer/OdooNormalizer.php - message: '/Parameter #1 \$onFulfilled of method Http\\Promise\\Promise::then\(\) expects \(callable\(mixed\): Psr\\Http\\Message\\ResponseInterface\)\|null, Closure\(Psr\\Http\\Message\\ResponseInterface\): Psr\\Http\\Message\\ResponseInterface given\./' count: 1 path: src/HttPlug/Plugin/OdooApiErrorPlugin.php + - + message: '/Instantiated class Symfony\\Component\\Serializer\\Mapping\\Loader\\AnnotationLoader not found\./' + count: 1 + path: src/Serializer/Factory/SerializerFactory.php + - + message: '/Method FluxSE\\OdooApiClient\\Serializer\\OdooNormalizer::normalize\(\) return type with generic class ArrayObject does not specify its types: TKey, TValue/' + count: 1 + path: src/Serializer/OdooNormalizer.php # Symfony 5.4 - '/Method FluxSE\\OdooApiClient\\Serializer\\(Json|Xml)Rpc\\(Json|Xml)RpcSerializerHelper::deserializeResponseBody\(\) should return T of object but returns mixed\./' - diff --git a/src/PropertyAccess/OdooPropertyAccessor.php b/src/PropertyAccess/OdooPropertyAccessor.php new file mode 100644 index 0000000..07a664c --- /dev/null +++ b/src/PropertyAccess/OdooPropertyAccessor.php @@ -0,0 +1,73 @@ +decoratedPropertyAccessor->setValue($objectOrArray, $propertyPath, $value); + + if (false !== $value) { + return; + } + + $newValue = $this->decoratedPropertyAccessor->getValue($objectOrArray, $propertyPath); + + if (false === $newValue) { + return; + } + + // set null instead of false + $this->decoratedPropertyAccessor->setValue($objectOrArray, $propertyPath, null); + } + + public function getValue(object|array $objectOrArray, PropertyPathInterface|string $propertyPath): mixed + { + /** + * Specific case of normalized data returned as null instead of being just transformed + */ + $value = $this->decoratedPropertyAccessor->getValue($objectOrArray, $propertyPath); + + if (false === $value instanceof OdooRelation) { + return $value; + } + + if (null !== $value->getCommand()) { + return $value; + } + + if (null !== $value->getId()) { + return $value; + } + + return null; + } + + public function isWritable(object|array $objectOrArray, PropertyPathInterface|string $propertyPath): bool + { + return $this->decoratedPropertyAccessor->isWritable($objectOrArray, $propertyPath); + } + + public function isReadable(object|array $objectOrArray, PropertyPathInterface|string $propertyPath): bool + { + return $this->decoratedPropertyAccessor->isReadable($objectOrArray, $propertyPath); + } +} diff --git a/src/Serializer/Factory/SerializerFactory.php b/src/Serializer/Factory/SerializerFactory.php index 4973722..5279db8 100644 --- a/src/Serializer/Factory/SerializerFactory.php +++ b/src/Serializer/Factory/SerializerFactory.php @@ -5,6 +5,7 @@ namespace FluxSE\OdooApiClient\Serializer\Factory; use Doctrine\Common\Annotations\AnnotationReader; +use FluxSE\OdooApiClient\PropertyAccess\OdooPropertyAccessor; use FluxSE\OdooApiClient\Serializer\JsonRpc\JsonRpcDecoder; use FluxSE\OdooApiClient\Serializer\JsonRpc\JsonRpcEncoder; use FluxSE\OdooApiClient\Serializer\NullableDateTimeDenormalizer; @@ -17,6 +18,7 @@ use FluxSE\OdooApiClient\Serializer\OdooRelationsNormalizer; use FluxSE\OdooApiClient\Serializer\XmlRpc\XmlRpcDecoder; use FluxSE\OdooApiClient\Serializer\XmlRpc\XmlRpcEncoder; +use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\Extractor\SerializerExtractor; @@ -31,6 +33,7 @@ use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer; use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; final class SerializerFactory implements SerializerFactoryInterface @@ -99,40 +102,44 @@ public function setupObjectNormalizer(): OdooNormalizer new CamelCaseToSnakeCaseNameConverter() ); + $propertyAccessor = PropertyAccess::createPropertyAccessor(); + return new OdooNormalizer( - $classMetadataFactory, - $metadataAwareNameConverter, - null, - new PropertyInfoExtractor( - [ - new ReflectionExtractor(), - new SerializerExtractor($classMetadataFactory), - ], - [ - new PhpDocExtractor(), - new ReflectionExtractor(), - ], - [ - new PhpDocExtractor(), - ], - [ - new ReflectionExtractor(), - ], + new ObjectNormalizer( + $classMetadataFactory, + $metadataAwareNameConverter, + new OdooPropertyAccessor($propertyAccessor), + new PropertyInfoExtractor( + [ + new ReflectionExtractor(), + new SerializerExtractor($classMetadataFactory), + ], + [ + new PhpDocExtractor(), + new ReflectionExtractor(), + ], + [ + new PhpDocExtractor(), + ], + [ + new ReflectionExtractor(), + ], + [ + new ReflectionExtractor(), + ] + ), + null, + null, [ - new ReflectionExtractor(), + // => array to model + AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => true, + // => model to array + AbstractObjectNormalizer::SKIP_NULL_VALUES => true, + AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object) { + return $object->getId() ?? 0; + } ] - ), - null, - null, - [ - // => array to model - AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => true, - // => model to array - AbstractObjectNormalizer::SKIP_NULL_VALUES => true, - AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object) { - return $object->getId() ?? 0; - } - ] + ) ); } diff --git a/src/Serializer/OdooNormalizer.php b/src/Serializer/OdooNormalizer.php index 1b993e7..6716490 100644 --- a/src/Serializer/OdooNormalizer.php +++ b/src/Serializer/OdooNormalizer.php @@ -8,12 +8,18 @@ use FluxSE\OdooApiClient\Api\RequestBodyInterface; use FluxSE\OdooApiClient\Model\BaseInterface; use FluxSE\OdooApiClient\Model\Common\Version; -use FluxSE\OdooApiClient\Model\OdooRelation; -use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; +use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\SerializerAwareInterface; +use Symfony\Component\Serializer\SerializerInterface; -final class OdooNormalizer extends ObjectNormalizer +final class OdooNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface { + public function __construct(private ObjectNormalizer $decoratedObjectNormalizer) + { + } + public function getSupportedTypes(?string $format): array { return [ @@ -24,59 +30,28 @@ public function getSupportedTypes(?string $format): array ]; } - protected function setAttributeValue(object $object, string $attribute, mixed $value, string $format = null, array $context = []): void + public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed { - /** - * Override to set null when original value is false - * - * Because null value is transformed to false by Odoo API - * There is no other way to do it simply with any features given by Symfony Serializer - */ - - parent::setAttributeValue($object, $attribute, $value, $format, $context); - - if (false !== $value) { - return; - } - - try { - $newValue = parent::getAttributeValue($object, $attribute, $format, $context); - } catch (NoSuchPropertyException $e) { - // ignore not found properties like the setAttributeValue above - return; - } - - if (false === $newValue) { - return; - } - - parent::setAttributeValue($object, $attribute, null, $format, $context); + return $this->decoratedObjectNormalizer->denormalize($data, $type, $format, $context); } - protected function getAttributeValue(object $object, string $attribute, string $format = null, array $context = []): mixed + public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool { - /** - * Specific case of normalized data returned as null instead of being just transformed - */ - $value = parent::getAttributeValue($object, $attribute, $format, $context); - - $skipNullValue = $context[self::SKIP_NULL_VALUES] ?? $this->defaultContext[self::SKIP_NULL_VALUES] ?? false; - if (!$skipNullValue) { - return $value; - } - - if (false === $value instanceof OdooRelation) { - return $value; - } + return $this->decoratedObjectNormalizer->supportsDenormalization($data, $type, $format, $context); + } - if (null !== $value->getCommand()) { - return $value; - } + public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null + { + return $this->decoratedObjectNormalizer->normalize($object, $format, $context); + } - if (null !== $value->getId()) { - return $value; - } + public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool + { + return $this->decoratedObjectNormalizer->supportsNormalization($data, $format, $context); + } - return null; + public function setSerializer(SerializerInterface $serializer): void + { + $this->decoratedObjectNormalizer->setSerializer($serializer); } } From 61544ca4a28ac788af5f6303f947d2f316491c8a Mon Sep 17 00:00:00 2001 From: Francis Hilaire Date: Wed, 5 Jun 2024 17:44:32 +0200 Subject: [PATCH 06/13] Uggly fix for missing global functions --- composer.json | 3 ++- src/functions.php | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 src/functions.php diff --git a/composer.json b/composer.json index 98001cb..3c872ef 100644 --- a/composer.json +++ b/composer.json @@ -60,7 +60,8 @@ "bin/odoo-model-classes-generator" ], "autoload": { - "psr-4": { "FluxSE\\OdooApiClient\\": "src/" } + "psr-4": { "FluxSE\\OdooApiClient\\": "src/" }, + "files": ["src/functions.php"] }, "autoload-dev": { "psr-4": { "Tests\\FluxSE\\OdooApiClient\\": "tests/" } diff --git a/src/functions.php b/src/functions.php new file mode 100644 index 0000000..7c1f8b7 --- /dev/null +++ b/src/functions.php @@ -0,0 +1,12 @@ + Date: Wed, 5 Jun 2024 17:45:30 +0200 Subject: [PATCH 07/13] Remove psalm tests --- .github/workflows/build.yml | 3 --- composer.json | 1 - psalm.xml | 37 ------------------------------------- 3 files changed, 41 deletions(-) delete mode 100644 psalm.xml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9a0ce34..ca459c6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -151,9 +151,6 @@ jobs: - name: Run PHPStan run: vendor/bin/phpstan analyse -l max src/ tests/ - - - name: Run Psalm - run: vendor/bin/psalm --show-info=false --output-format=github --php-version=${{ matrix.php }} - name: Run PHPUnit run: vendor/bin/phpunit --colors=always diff --git a/composer.json b/composer.json index 3c872ef..35fc576 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,6 @@ "http-interop/http-factory-guzzle": "^1.0", "symplify/easy-coding-standard": "^12", "phpstan/phpstan": "^1", - "vimeo/psalm": "^5", "rector/rector": "^1.0.4" }, "suggest": { diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index a804732..0000000 --- a/psalm.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 3b1907169fc149128e896701e4b342d3290ce40a Mon Sep 17 00:00:00 2001 From: Francis Hilaire Date: Wed, 5 Jun 2024 17:46:58 +0200 Subject: [PATCH 08/13] Remove Symfony 5.4 support --- .github/workflows/build.yml | 2 +- composer.json | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ca459c6..b3b67c3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,7 +28,7 @@ jobs: fail-fast: false matrix: odoo: [13, 14, 15, 16, 17] - symfony: ["^5.4", "^6.4", "^7.1"] + symfony: ["^6.4", "^7.1"] php: ["8.1", "8.2", "8.3"] # " required to keep the .0 exclude: - diff --git a/composer.json b/composer.json index 35fc576..664eebf 100644 --- a/composer.json +++ b/composer.json @@ -30,12 +30,12 @@ "php-http/client-common": "^2.1", "php-http/discovery": "^1.7", "psr/http-factory-implementation": "^1.0", - "symfony/console": "^5.4|^6.4|^7.1", - "symfony/serializer": "^5.4|^6.4|^7.1", - "symfony/property-access": "^5.4|^6.4|^7.1", - "symfony/property-info": "^5.4|^6.4|^7.1", - "symfony/string": "^5.4|^6.4|^7.1", - "symfony/http-kernel": "^5.4|^6.4|^7.1", + "symfony/console": "^6.4|^7.1", + "symfony/serializer": "^6.4|^7.1", + "symfony/property-access": "^6.4|^7.1", + "symfony/property-info": "^6.4|^7.1", + "symfony/string": "^6.4|^7.1", + "symfony/http-kernel": "^6.4|^7.1", "php-http/logger-plugin": "^1.1", "prometee/php-class-generator": "^1.0", "doctrine/annotations": "^1.13|^2.0", From 739beb3f8fda615267d62cd0f9d59dd9d847dea0 Mon Sep 17 00:00:00 2001 From: Francis Hilaire Date: Wed, 5 Jun 2024 17:48:55 +0200 Subject: [PATCH 09/13] Remove annotation loader --- composer.json | 2 +- phpstan.neon.dist | 4 ---- src/Serializer/Factory/SerializerFactory.php | 13 +------------ 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/composer.json b/composer.json index 664eebf..ad23c8e 100644 --- a/composer.json +++ b/composer.json @@ -37,8 +37,8 @@ "symfony/string": "^6.4|^7.1", "symfony/http-kernel": "^6.4|^7.1", "php-http/logger-plugin": "^1.1", + "phpdocumentor/reflection-docblock": "^5.4", "prometee/php-class-generator": "^1.0", - "doctrine/annotations": "^1.13|^2.0", "webmozart/assert": "^1" }, "require-dev": { diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 933d936..e33e3f0 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -16,10 +16,6 @@ parameters: message: '/Parameter #1 \$onFulfilled of method Http\\Promise\\Promise::then\(\) expects \(callable\(mixed\): Psr\\Http\\Message\\ResponseInterface\)\|null, Closure\(Psr\\Http\\Message\\ResponseInterface\): Psr\\Http\\Message\\ResponseInterface given\./' count: 1 path: src/HttPlug/Plugin/OdooApiErrorPlugin.php - - - message: '/Instantiated class Symfony\\Component\\Serializer\\Mapping\\Loader\\AnnotationLoader not found\./' - count: 1 - path: src/Serializer/Factory/SerializerFactory.php - message: '/Method FluxSE\\OdooApiClient\\Serializer\\OdooNormalizer::normalize\(\) return type with generic class ArrayObject does not specify its types: TKey, TValue/' count: 1 diff --git a/src/Serializer/Factory/SerializerFactory.php b/src/Serializer/Factory/SerializerFactory.php index 5279db8..3d40da6 100644 --- a/src/Serializer/Factory/SerializerFactory.php +++ b/src/Serializer/Factory/SerializerFactory.php @@ -4,7 +4,6 @@ namespace FluxSE\OdooApiClient\Serializer\Factory; -use Doctrine\Common\Annotations\AnnotationReader; use FluxSE\OdooApiClient\PropertyAccess\OdooPropertyAccessor; use FluxSE\OdooApiClient\Serializer\JsonRpc\JsonRpcDecoder; use FluxSE\OdooApiClient\Serializer\JsonRpc\JsonRpcEncoder; @@ -24,7 +23,6 @@ use Symfony\Component\PropertyInfo\Extractor\SerializerExtractor; use Symfony\Component\PropertyInfo\PropertyInfoExtractor; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; -use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; @@ -85,16 +83,7 @@ public function setupEncoders(): array public function setupObjectNormalizer(): OdooNormalizer { - if (class_exists(AttributeLoader::class)) { - /** @var LoaderInterface $loader */ - $loader = new AttributeLoader(); - } else { - /** - * @var LoaderInterface $loader - * @psalm-suppress TooManyArguments - */ - $loader = new AnnotationLoader(new AnnotationReader()); - } + $loader = new AttributeLoader(); $classMetadataFactory = new ClassMetadataFactory($loader); $metadataAwareNameConverter = new MetadataAwareNameConverter( From 6f2a47581253dd48a055b2df3e573f99510e179a Mon Sep 17 00:00:00 2001 From: Francis Hilaire Date: Thu, 6 Jun 2024 14:40:18 +0200 Subject: [PATCH 10/13] Add a test to cover the issue on symfony/serializer --- tests/Serializer/OdooNormalizerTest.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/Serializer/OdooNormalizerTest.php b/tests/Serializer/OdooNormalizerTest.php index 6e24f6b..d1fcea8 100644 --- a/tests/Serializer/OdooNormalizerTest.php +++ b/tests/Serializer/OdooNormalizerTest.php @@ -6,12 +6,15 @@ use FluxSE\OdooApiClient\Serializer\Factory\SerializerFactory; use FluxSE\OdooApiClient\Serializer\OdooRelationsNormalizer; use PHPUnit\Framework\TestCase; +use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\PropertyInfo\PropertyInfoExtractor; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Serializer; +use Tests\FluxSE\OdooApiClient\Serializer\Model\Foo; use Tests\FluxSE\OdooApiClient\TestModel\Object\Res\Partner; class OdooNormalizerTest extends TestCase { - /** @var Serializer */ private Serializer $serializer; protected function setUp(): void @@ -108,4 +111,14 @@ public function testNormalizeWithNullData(): void ], ], $arr); } + + public function testDenormalizeFalsePseudoType(): void + { + // when denormalizing some data into an object where an attribute uses the false pseudo type + /** @var Foo $object */ + $object = $this->serializer->denormalize(['id' => 2], Foo::class); + + // then the attribute that declared false was filled correctly + $this->assertEquals(2, $object->getId()); + } } From 96fbdda40c75425db6061e08d7dad04565a31978 Mon Sep 17 00:00:00 2001 From: Francis Hilaire Date: Thu, 6 Jun 2024 14:40:44 +0200 Subject: [PATCH 11/13] Remove useless 5.4 rules and add 6.4 ones --- phpstan.neon.dist | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index e33e3f0..1f1b69c 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -13,16 +13,15 @@ parameters: - '/Method FluxSE\\OdooApiClient\\Manager\\ModelListManager::(find|findBy|findByIds)\(\) should return (array<|\()T of FluxSE\\OdooApiClient\\Model\\BaseInterface(>|\)\|null) but returns mixed\./' - '/Method FluxSE\\OdooApiClient\\Serializer\\(Json|Xml)Rpc\\(Json|Xml)RpcSerializerHelper::decodeResponseBody\(\) should return array\|bool\|int\|string but returns mixed\./' - - message: '/Parameter #1 \$onFulfilled of method Http\\Promise\\Promise::then\(\) expects \(callable\(mixed\): Psr\\Http\\Message\\ResponseInterface\)\|null, Closure\(Psr\\Http\\Message\\ResponseInterface\): Psr\\Http\\Message\\ResponseInterface given\./' + message: '/Method FluxSE\\OdooApiClient\\Serializer\\OdooNormalizer::normalize\(\) return type with generic class ArrayObject does not specify its types: TKey, TValue/' count: 1 - path: src/HttPlug/Plugin/OdooApiErrorPlugin.php + path: src/Serializer/OdooNormalizer.php + # Symfony 6.4 - - message: '/Method FluxSE\\OdooApiClient\\Serializer\\OdooNormalizer::normalize\(\) return type with generic class ArrayObject does not specify its types: TKey, TValue/' + message: '/Method Symfony\\Component\\Serializer\\Normalizer\\AbstractObjectNormalizer::supportsDenormalization\(\) invoked with 4 parameters, 2-3 required\./' count: 1 path: src/Serializer/OdooNormalizer.php - # Symfony 5.4 - - '/Method FluxSE\\OdooApiClient\\Serializer\\(Json|Xml)Rpc\\(Json|Xml)RpcSerializerHelper::deserializeResponseBody\(\) should return T of object but returns mixed\./' - - message: '/Method FluxSE\\OdooApiClient\\HttPlug\\Plugin\\OdooApiErrorPlugin::handleRequest\(\) has parameter \$(first|next) with generic interface Http\\Promise\\Promise but does not specify its types: T/' - count: 2 - path: src/HttPlug/Plugin/OdooApiErrorPlugin.php + message: '/Method Symfony\\Component\\Serializer\\Normalizer\\AbstractObjectNormalizer::supportsNormalization\(\) invoked with 3 parameters, 1-2 required\./' + count: 1 + path: src/Serializer/OdooNormalizer.php From d285a443e8523700bbea0421a9246f7a2cb2e01f Mon Sep 17 00:00:00 2001 From: Francis Hilaire Date: Thu, 6 Jun 2024 15:14:13 +0200 Subject: [PATCH 12/13] Add tests for the property accessor --- src/Serializer/Factory/SerializerFactory.php | 11 ++++- .../Factory/SerializerFactoryInterface.php | 6 +++ tests/PropertyAccess/Model/Foo.php | 41 +++++++++++++++++ .../OdooPropertyAccessorTest.php | 46 +++++++++++++++++++ 4 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 tests/PropertyAccess/Model/Foo.php create mode 100644 tests/PropertyAccess/OdooPropertyAccessorTest.php diff --git a/src/Serializer/Factory/SerializerFactory.php b/src/Serializer/Factory/SerializerFactory.php index 3d40da6..0bcaccb 100644 --- a/src/Serializer/Factory/SerializerFactory.php +++ b/src/Serializer/Factory/SerializerFactory.php @@ -18,6 +18,7 @@ use FluxSE\OdooApiClient\Serializer\XmlRpc\XmlRpcDecoder; use FluxSE\OdooApiClient\Serializer\XmlRpc\XmlRpcEncoder; use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; use Symfony\Component\PropertyInfo\Extractor\SerializerExtractor; @@ -91,13 +92,13 @@ public function setupObjectNormalizer(): OdooNormalizer new CamelCaseToSnakeCaseNameConverter() ); - $propertyAccessor = PropertyAccess::createPropertyAccessor(); + $odooPropertyAccessor = $this->setupPropertyAccessor(); return new OdooNormalizer( new ObjectNormalizer( $classMetadataFactory, $metadataAwareNameConverter, - new OdooPropertyAccessor($propertyAccessor), + $odooPropertyAccessor, new PropertyInfoExtractor( [ new ReflectionExtractor(), @@ -132,6 +133,12 @@ public function setupObjectNormalizer(): OdooNormalizer ); } + public function setupPropertyAccessor(): PropertyAccessorInterface + { + $propertyAccessor = PropertyAccess::createPropertyAccessor(); + return new OdooPropertyAccessor($propertyAccessor); + } + public function getDateFormat(): string { return $this->dateFormat; diff --git a/src/Serializer/Factory/SerializerFactoryInterface.php b/src/Serializer/Factory/SerializerFactoryInterface.php index ef3ce32..4fa20cb 100644 --- a/src/Serializer/Factory/SerializerFactoryInterface.php +++ b/src/Serializer/Factory/SerializerFactoryInterface.php @@ -4,6 +4,8 @@ namespace FluxSE\OdooApiClient\Serializer\Factory; +use FluxSE\OdooApiClient\Serializer\OdooNormalizer; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; use Symfony\Component\Serializer\Serializer; interface SerializerFactoryInterface @@ -13,4 +15,8 @@ public function create(): Serializer; public function setupNormalizers(): array; public function setupEncoders(): array; + + public function setupObjectNormalizer(): OdooNormalizer; + + public function setupPropertyAccessor(): PropertyAccessorInterface; } diff --git a/tests/PropertyAccess/Model/Foo.php b/tests/PropertyAccess/Model/Foo.php new file mode 100644 index 0000000..fd74711 --- /dev/null +++ b/tests/PropertyAccess/Model/Foo.php @@ -0,0 +1,41 @@ +activity_state; + } + + public function setActivityState(?string $activity_state): void + { + $this->activity_state = $activity_state; + } + + public function getMoveId(): ?OdooRelation + { + return $this->move_id; + } + + public function setMoveId(?OdooRelation $move_id): void + { + $this->move_id = $move_id; + } + + public static function getOdooModelName(): string + { + return 'foo'; + } +} diff --git a/tests/PropertyAccess/OdooPropertyAccessorTest.php b/tests/PropertyAccess/OdooPropertyAccessorTest.php new file mode 100644 index 0000000..68fbbd3 --- /dev/null +++ b/tests/PropertyAccess/OdooPropertyAccessorTest.php @@ -0,0 +1,46 @@ +propertyAccessor = $serializerFactory->setupPropertyAccessor(); + } + + public function testConstruct(): void + { + $this->assertInstanceOf(OdooPropertyAccessor::class, $this->propertyAccessor); + } + + public function testSetValueFalseValueBecomeNull(): void + { + $object = new Foo(); + $object->setActivityState('aString'); + $this->propertyAccessor->setValue($object, 'activityState', false); + + $this->assertEquals(null, $object->getActivityState()); + } + + public function testGetValueOdooRelationValueBecomeNull(): void + { + $object = new Foo(); + $object->setMoveId(new OdooRelation()); + $value = $this->propertyAccessor->getValue($object, 'moveId'); + + $this->assertEquals(new OdooRelation(), $object->getMoveId()); + $this->assertNull($value); + } +} From f626fdbacdb3b08a0346403605840f675f06f041 Mon Sep 17 00:00:00 2001 From: Francis Hilaire Date: Thu, 6 Jun 2024 15:17:21 +0200 Subject: [PATCH 13/13] Add a comment about the serializer issue --- src/functions.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/functions.php b/src/functions.php index 7c1f8b7..7803d32 100644 --- a/src/functions.php +++ b/src/functions.php @@ -2,6 +2,12 @@ declare(strict_types=1); +/** + * Those functions are here to cover an issue on symfony/serializer + * + * @see https://github.com/symfony/symfony/issues/57334 for more details + */ + function is_false(mixed $value): bool { return is_bool($value) && false === (bool) $value;