Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Symfony 7.1 #5

Merged
merged 13 commits into from
Jun 6, 2024
9 changes: 5 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ jobs:
fail-fast: false
matrix:
odoo: [13, 14, 15, 16, 17]
symfony: ["^5.4", "^6.4"]
symfony: ["^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
Expand Down Expand Up @@ -147,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
Expand Down
18 changes: 9 additions & 9 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@
"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": "^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",
"phpdocumentor/reflection-docblock": "^5.4",
"prometee/php-class-generator": "^1.0",
"doctrine/annotations": "^1.13|^2.0",
"webmozart/assert": "^1"
},
"require-dev": {
Expand All @@ -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": {
Expand All @@ -60,7 +59,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/" }
Expand Down
16 changes: 7 additions & 9 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,16 @@ 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\./'
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 6.4
-
message: '/Parameter #1 \$onFulfilled of method Http\\Promise\\Promise<mixed>::then\(\) expects \(callable\(mixed\): Psr\\Http\\Message\\ResponseInterface\)\|null, Closure\(Psr\\Http\\Message\\ResponseInterface\): Psr\\Http\\Message\\ResponseInterface given\./'
message: '/Method Symfony\\Component\\Serializer\\Normalizer\\AbstractObjectNormalizer::supportsDenormalization\(\) invoked with 4 parameters, 2-3 required\./'
count: 1
path: src/HttPlug/Plugin/OdooApiErrorPlugin.php
# Symfony 5.4
- '/Method FluxSE\\OdooApiClient\\Serializer\\(Json|Xml)Rpc\\(Json|Xml)RpcSerializerHelper::deserializeResponseBody\(\) should return T of object but returns mixed\./'
path: src/Serializer/OdooNormalizer.php
-
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
37 changes: 0 additions & 37 deletions psalm.xml

This file was deleted.

4 changes: 2 additions & 2 deletions src/Manager/ModelManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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(),
Expand Down
73 changes: 73 additions & 0 deletions src/PropertyAccess/OdooPropertyAccessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace FluxSE\OdooApiClient\PropertyAccess;

use FluxSE\OdooApiClient\Model\OdooRelation;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyAccess\PropertyPathInterface;

final class OdooPropertyAccessor implements PropertyAccessorInterface
{
public function __construct(private PropertyAccessorInterface $decoratedPropertyAccessor)
{
}

public function setValue(object|array &$objectOrArray, PropertyPathInterface|string $propertyPath, mixed $value): void
{
/**
* 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
*/

$this->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);
}
}
89 changes: 46 additions & 43 deletions src/Serializer/Factory/SerializerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,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;
Expand All @@ -17,12 +17,13 @@
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\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
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;
Expand All @@ -31,6 +32,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
Expand Down Expand Up @@ -82,60 +84,61 @@ 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(
$classMetadataFactory,
new CamelCaseToSnakeCaseNameConverter()
);

$odooPropertyAccessor = $this->setupPropertyAccessor();

return new OdooNormalizer(
$classMetadataFactory,
$metadataAwareNameConverter,
null,
new PropertyInfoExtractor(
[
new ReflectionExtractor(),
new SerializerExtractor($classMetadataFactory),
],
[
new PhpDocExtractor(),
new ReflectionExtractor(),
],
[
new PhpDocExtractor(),
],
new ObjectNormalizer(
$classMetadataFactory,
$metadataAwareNameConverter,
$odooPropertyAccessor,
new PropertyInfoExtractor(
[
new ReflectionExtractor(),
new SerializerExtractor($classMetadataFactory),
],
[
new PhpDocExtractor(),
new ReflectionExtractor(),
],
[
new PhpDocExtractor(),
],
[
new ReflectionExtractor(),
],
[
new ReflectionExtractor(),
]
),
null,
null,
[
new ReflectionExtractor(),
],
[
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;
}
]
)
);
}

public function setupPropertyAccessor(): PropertyAccessorInterface
{
$propertyAccessor = PropertyAccess::createPropertyAccessor();
return new OdooPropertyAccessor($propertyAccessor);
}

public function getDateFormat(): string
{
return $this->dateFormat;
Expand Down
6 changes: 6 additions & 0 deletions src/Serializer/Factory/SerializerFactoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -13,4 +15,8 @@ public function create(): Serializer;
public function setupNormalizers(): array;

public function setupEncoders(): array;

public function setupObjectNormalizer(): OdooNormalizer;

public function setupPropertyAccessor(): PropertyAccessorInterface;
}
2 changes: 1 addition & 1 deletion src/Serializer/JsonRpc/JsonRpcDecoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
Expand Down
Loading
Loading