diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ba3378b..99a7fb9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- php-version: ['7.3', '7.4', '8.0', '8.1', '8.2']
+ php-version: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
steps:
- name: Check out code into the workspace
uses: actions/checkout@v2
diff --git a/composer.json b/composer.json
index c198e0c..d9e2be7 100644
--- a/composer.json
+++ b/composer.json
@@ -16,7 +16,7 @@
}
],
"require": {
- "php": ">=7.3.0",
+ "php": ">=7.3",
"ext-json": "*",
"psr/log": "^1|^2|^3",
"psr/http-client": "^1.0",
@@ -26,9 +26,9 @@
"php-http/message-factory": "^1.0",
"php-http/discovery": "^1.13",
"doctrine/annotations": "^1.13|^2.0",
- "liip/serializer": "2.2.*",
+ "liip/serializer": "2.2.* || 2.6.*",
"php-http/httplug": "^2.2",
- "civicrm/composer-compile-plugin": "^0.18.0",
+ "civicrm/composer-compile-plugin": "^0.20",
"symfony/console": "^4.0|^5.0|^6.0",
"psr/event-dispatcher": "^1.0",
"neur0toxine/psr.http-client-implementation.php-http-curl": "*",
@@ -67,7 +67,7 @@
"phpunit": "./vendor/bin/phpunit -c phpunit.xml.dist --coverage-text",
"phpunit-ci": "@php -dpcov.enabled=1 -dpcov.directory=. -dpcov.exclude=\"~vendor~\" ./vendor/bin/phpunit --teamcity -c phpunit.xml.dist",
"phpmd": "./vendor/bin/phpmd src text ./phpmd.xml",
- "phpcs": "./vendor/bin/phpcs -p src --runtime-set testVersion 7.3-8.2 && ./vendor/bin/phpcs -p tests --runtime-set testVersion 7.3-8.2 --warning-severity=0",
+ "phpcs": "./vendor/bin/phpcs -p src --runtime-set testVersion 7.3-8.3 && ./vendor/bin/phpcs -p tests --runtime-set testVersion 7.3-8.3 --warning-severity=0",
"phpstan": "./vendor/bin/phpstan analyse -c phpstan.neon src --memory-limit=-1",
"phpstan-dockerized-ci": "docker run --rm -it -w=/app -v ${PWD}:/app oskarstark/phpstan-ga:1.0.1 analyse src -c phpstan.neon --memory-limit=1G --no-progress",
"lint:fix": "./vendor/bin/phpcbf src",
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index d12440d..a10b535 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -10,4 +10,8 @@
src/
tests/
+
+ src/Component/Serializer/Generator/*
+ src/Component/Serializer/Parser/*
+ src/Component/Serializer/ArraySupportDecorator.php
diff --git a/phpmd.xml b/phpmd.xml
index 3cd0206..0e11758 100644
--- a/phpmd.xml
+++ b/phpmd.xml
@@ -44,4 +44,6 @@
tests/*
+ src/Component/Serializer/Generator/*
+ src/Component/Serializer/Parser/*
diff --git a/phpstan-baseline-serializer.neon b/phpstan-baseline-serializer.neon
new file mode 100644
index 0000000..eaf39fe
--- /dev/null
+++ b/phpstan-baseline-serializer.neon
@@ -0,0 +1,246 @@
+parameters:
+ ignoreErrors:
+ -
+ message: "#^Parameter \\#1 \\$config of static method Liip\\\\Serializer\\\\Configuration\\\\GeneratorConfiguration\\:\\:createFomArray\\(\\) expects array\\{default_group_combinations\\?\\: array\\\\>\\|null, default_versions\\?\\: array\\\\|null, classes\\?\\: array\\\\>\\|null, options\\?\\: array\\\\}, array\\{default_group_combinations\\: array\\{\\}, default_versions\\: array\\{\\}, classes\\: non\\-empty\\-array\\\\} given\\.$#"
+ count: 1
+ path: src/Component/ModelsGenerator.php
+
+ -
+ message: "#^Parameter \\#3 \\$classesToGenerate of class RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Generator\\\\DeserializerGenerator constructor expects array\\, array\\ given\\.$#"
+ count: 1
+ path: src/Component/ModelsGenerator.php
+
+ -
+ message: "#^Parameter \\#2 \\$method of method Liip\\\\Serializer\\\\Template\\\\Deserialization\\:\\:renderSetter\\(\\) expects string, string\\|null given\\.$#"
+ count: 1
+ path: src/Component/Serializer/Generator/DeserializerGenerator.php
+
+ -
+ message: "#^Parameter \\#4 \\$stack of method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Generator\\\\DeserializerGenerator\\:\\:generateCodeForClass\\(\\) expects array\\\\>, array given\\.$#"
+ count: 2
+ path: src/Component/Serializer/Generator/DeserializerGenerator.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Generator\\\\SerializerGenerator\\:\\:buildSerializerFunctionName\\(\\) should return string but returns string\\|null\\.$#"
+ count: 1
+ path: src/Component/Serializer/Generator/SerializerGenerator.php
+
+ -
+ message: "#^Parameter \\#2 \\$method of method Liip\\\\Serializer\\\\Template\\\\Serialization\\:\\:renderGetter\\(\\) expects string, string\\|null given\\.$#"
+ count: 1
+ path: src/Component/Serializer/Generator/SerializerGenerator.php
+
+ -
+ message: "#^Parameter \\#3 \\$serializerGroups of method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Generator\\\\SerializerGenerator\\:\\:generateCodeForClass\\(\\) expects array\\, array given\\.$#"
+ count: 4
+ path: src/Component/Serializer/Generator/SerializerGenerator.php
+
+ -
+ message: "#^Class RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Lexer extends generic class Doctrine\\\\Common\\\\Lexer\\\\AbstractLexer but does not specify its types\\: T, V$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Lexer.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Lexer\\:\\:getType\\(\\) has parameter \\$value with no type specified\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Lexer.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Lexer\\:\\:parse\\(\\) has no return type specified\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Lexer.php
+
+ -
+ message: "#^Parameter \\#1 \\$haystack of function stripos expects string, float\\|int\\|string given\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Lexer.php
+
+ -
+ message: "#^Parameter \\#1 \\$haystack of function strpos expects string, float\\|int\\|string given\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Lexer.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:getConstant\\(\\) should return string but returns string\\|false\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:parse\\(\\) return type has no value type specified in iterable type array\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:parse\\(\\) should return array but returns mixed\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:visitArrayType\\(\\) return type has no value type specified in iterable type array\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:visitCompoundType\\(\\) return type has no value type specified in iterable type array\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:visitSimpleType\\(\\) never returns string so it can be removed from the return type\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\ParserInterface\\:\\:parse\\(\\) return type has no value type specified in iterable type array\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/ParserInterface.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:gatherClassAnnotations\\(\\) has parameter \\$reflectionClass with generic class ReflectionClass but does not specify its types\\: T$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSParser.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:getMethodName\\(\\) has parameter \\$annotations with no value type specified in iterable type array\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSParser.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:getProperty\\(\\) has parameter \\$annotations with no value type specified in iterable type array\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSParser.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:getReturnType\\(\\) has parameter \\$reflClass with generic class ReflectionClass but does not specify its types\\: T$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSParser.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:getSerializedName\\(\\) has parameter \\$annotations with no value type specified in iterable type array\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSParser.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:isPostDeserializeMethod\\(\\) has parameter \\$annotations with no value type specified in iterable type array\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSParser.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:isVirtualProperty\\(\\) has parameter \\$annotations with no value type specified in iterable type array\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSParser.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:parseClass\\(\\) has parameter \\$reflClass with generic class ReflectionClass but does not specify its types\\: T$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSParser.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:parseMethods\\(\\) has parameter \\$reflClass with generic class ReflectionClass but does not specify its types\\: T$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSParser.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:parseProperties\\(\\) has parameter \\$reflClass with generic class ReflectionClass but does not specify its types\\: T$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSParser.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:parsePropertyAnnotations\\(\\) has parameter \\$annotations with no value type specified in iterable type array\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSParser.php
+
+ -
+ message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\\\|T of object, string given\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSParser.php
+
+ -
+ message: "#^Class Doctrine\\\\Common\\\\Collections\\\\ArrayCollection not found\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSTypeParser.php
+
+ -
+ message: "#^Class Doctrine\\\\Common\\\\Collections\\\\Collection not found\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSTypeParser.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSTypeParser\\:\\:parseType\\(\\) has parameter \\$typeInfo with no value type specified in iterable type array\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSTypeParser.php
+
+ -
+ message: "#^Parameter \\#4 \\$traversableClass of class Liip\\\\MetadataParser\\\\Metadata\\\\PropertyTypeIterable constructor expects class\\-string\\\\|null, string\\|null given\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSTypeParser.php
+
+ -
+ message: "#^Cannot access property \\$value on RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Token\\|null\\.$#"
+ count: 2
+ path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
+
+ -
+ message: "#^Parameter \\#1 \\$string of function strlen expects string, int\\|string given\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
+
+ -
+ message: "#^Parameter \\#1 \\$value of method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:getConstant\\(\\) expects int, int\\\\|int\\<4, 8\\>\\|int\\<11, max\\>\\|string\\|null given\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
+
+ -
+ message: "#^Parameter \\#1 \\$value of method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:getConstant\\(\\) expects int, int\\|string\\|null given\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
+
+ -
+ message: "#^Property RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:\\$token with generic class RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Token does not specify its types\\: T, V$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
+
+ -
+ message: "#^Access to an undefined property object\\:\\:\\$position\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Token.php
+
+ -
+ message: "#^Access to an undefined property object\\:\\:\\$type\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Token.php
+
+ -
+ message: "#^Access to an undefined property object\\:\\:\\$value\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Token.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Token\\:\\:fromArray\\(\\) has parameter \\$source with no value type specified in iterable type array\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Token.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Token\\:\\:fromArray\\(\\) return type with generic class RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Token does not specify its types\\: T, V$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Token.php
+
+ -
+ message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Token\\:\\:fromObject\\(\\) return type with generic class RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Token does not specify its types\\: T, V$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Token.php
+
+ -
+ message: "#^Property RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Token\\\\:\\:\\$type \\(\\(T of int\\|string\\)\\|null\\) does not accept int\\|string\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSCore/Type/Token.php
+
+ -
+ message: "#^Cannot call method getParameters\\(\\) on ReflectionMethod\\|null\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSTypeParser.php
+
+ -
+ message: "#^Property RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSTypeParser\\:\\:\\$useArrayDateFormat has no type specified\\.$#"
+ count: 1
+ path: src/Component/Serializer/Parser/JMSTypeParser.php
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index 971ac59..d4b5787 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -1,4 +1,6 @@
parameters:
+ excludePaths:
+ - src/Component/Serializer/ArraySupportDecorator.php
ignoreErrors:
-
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Builder\\\\ClientBuilder\\:\\:buildHandlersChain\\(\\) through static\\:\\:\\.$#"
@@ -210,36 +212,6 @@ parameters:
count: 2
path: src/Component/Serializer/ArraySupportDecorator.php
- -
- message: "#^Unsafe access to private constant RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Generator\\\\DeserializerGenerator\\:\\:FILENAME_PREFIX through static\\:\\:\\.$#"
- count: 1
- path: src/Component/Serializer/Generator/DeserializerGenerator.php
-
- -
- message: "#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#"
- count: 1
- path: src/Component/Serializer/Generator/SerializerGenerator.php
-
- -
- message: "#^Cannot call method getClassName\\(\\) on mixed\\.$#"
- count: 1
- path: src/Component/Serializer/Generator/SerializerGenerator.php
-
- -
- message: "#^Cannot call method getGroups\\(\\) on mixed\\.$#"
- count: 3
- path: src/Component/Serializer/Generator/SerializerGenerator.php
-
- -
- message: "#^Cannot call method getVersions\\(\\) on mixed\\.$#"
- count: 1
- path: src/Component/Serializer/Generator/SerializerGenerator.php
-
- -
- message: "#^Unsafe access to private constant RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Generator\\\\SerializerGenerator\\:\\:FILENAME_PREFIX through static\\:\\:\\.$#"
- count: 1
- path: src/Component/Serializer/Generator/SerializerGenerator.php
-
-
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\ModelsChecksumGenerator\\:\\:getStoredChecksums\\(\\) should return array\\ but returns mixed\\.$#"
count: 1
@@ -250,41 +222,6 @@ parameters:
count: 4
path: src/Component/Serializer/ModelsChecksumGenerator.php
- -
- message: "#^Cannot cast mixed to float\\.$#"
- count: 1
- path: src/Component/Serializer/Parser/BaseJMSParser.php
-
- -
- message: "#^Cannot cast mixed to int\\.$#"
- count: 1
- path: src/Component/Serializer/Parser/BaseJMSParser.php
-
- -
- message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\BaseJMSParser\\:\\:parse\\(\\) should return array but returns mixed\\.$#"
- count: 1
- path: src/Component/Serializer/Parser/BaseJMSParser.php
-
- -
- message: "#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#"
- count: 2
- path: src/Component/Serializer/Parser/BaseJMSParser.php
-
- -
- message: "#^Class RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSLexer extends generic class Doctrine\\\\Common\\\\Lexer\\\\AbstractLexer but does not specify its types\\: T, V$#"
- count: 1
- path: src/Component/Serializer/Parser/JMSLexer.php
-
- -
- message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSLexer\\:\\:parse\\(\\) should return int\\|string\\|null but returns int\\|string\\|UnitEnum\\|null\\.$#"
- count: 1
- path: src/Component/Serializer/Parser/JMSLexer.php
-
- -
- message: "#^Parameter \\#1 \\$object of function get_class expects object, mixed given\\.$#"
- count: 2
- path: src/Component/Serializer/Parser/JMSParser.php
-
-
message: "#^Parameter \\#2 \\$string2 of function strncmp expects string, class\\-string\\|false given\\.$#"
count: 1
@@ -295,36 +232,6 @@ parameters:
count: 1
path: src/Component/Serializer/Parser/JMSParser.php
- -
- message: "#^Parameter \\#1 \\$className of class Liip\\\\MetadataParser\\\\Metadata\\\\PropertyTypeClass constructor expects string, mixed given\\.$#"
- count: 1
- path: src/Component/Serializer/Parser/JMSTypeParser.php
-
- -
- message: "#^Parameter \\#1 \\$className of static method Liip\\\\MetadataParser\\\\Metadata\\\\PropertyTypeDateTime\\:\\:fromDateTimeClass\\(\\) expects string, mixed given\\.$#"
- count: 2
- path: src/Component/Serializer/Parser/JMSTypeParser.php
-
- -
- message: "#^Parameter \\#1 \\$typeName of class Liip\\\\MetadataParser\\\\Metadata\\\\PropertyTypePrimitive constructor expects string, mixed given\\.$#"
- count: 1
- path: src/Component/Serializer/Parser/JMSTypeParser.php
-
- -
- message: "#^Parameter \\#1 \\$typeName of static method Liip\\\\MetadataParser\\\\Metadata\\\\PropertyTypeDateTime\\:\\:isTypeDateTime\\(\\) expects string, mixed given\\.$#"
- count: 2
- path: src/Component/Serializer/Parser/JMSTypeParser.php
-
- -
- message: "#^Parameter \\#1 \\$typeName of static method Liip\\\\MetadataParser\\\\Metadata\\\\PropertyTypePrimitive\\:\\:isTypePrimitive\\(\\) expects string, mixed given\\.$#"
- count: 1
- path: src/Component/Serializer/Parser/JMSTypeParser.php
-
- -
- message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, mixed given\\.$#"
- count: 1
- path: src/Component/Serializer/Parser/JMSTypeParser.php
-
-
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Component\\\\Transformer\\\\DateTimeTransformer\\:\\:createFromFormat\\(\\) through static\\:\\:\\.$#"
count: 3
diff --git a/phpstan.neon b/phpstan.neon
index d0feec5..ade0673 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -1,4 +1,5 @@
includes:
+ - phpstan-baseline-serializer.neon
- phpstan-baseline.neon # TODO: This should be removed eventually.
parameters:
diff --git a/src/Command/GenerateModelsCommand.php b/src/Command/GenerateModelsCommand.php
index 64d70c5..18b9c1b 100644
--- a/src/Command/GenerateModelsCommand.php
+++ b/src/Command/GenerateModelsCommand.php
@@ -13,6 +13,7 @@ use RetailCrm\Api\Component\ModelsGenerator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Class GenerateModelsCommand
@@ -82,7 +83,15 @@ class GenerateModelsCommand extends AbstractModelsProcessorCommand
$output->writeln('');
}
- $generator->generate();
+ try {
+ $generator->generate();
+ } catch (\Throwable $throwable) {
+ $styled = new SymfonyStyle($input, $output);
+ $styled->error($throwable->getMessage());
+ $styled->writeln($throwable->getTraceAsString());
+
+ return -1;
+ }
$output->writeln(sprintf(
' ✓ Done, generated code for %d models.>',
diff --git a/src/Component/ModelsGenerator.php b/src/Component/ModelsGenerator.php
index 7e76185..8235326 100644
--- a/src/Component/ModelsGenerator.php
+++ b/src/Component/ModelsGenerator.php
@@ -11,12 +11,12 @@ namespace RetailCrm\Api\Component;
use Doctrine\Common\Annotations\AnnotationReader;
use Liip\MetadataParser\Builder;
+use Liip\MetadataParser\ModelParser\RawMetadata\PropertyCollection;
use Liip\MetadataParser\Parser;
use Liip\MetadataParser\RecursionChecker;
use Liip\Serializer\Configuration\GeneratorConfiguration;
use Liip\Serializer\Template\Deserialization;
use Liip\Serializer\Template\Serialization;
-use RetailCrm\Api\Component\Utils;
use RetailCrm\Api\Component\Serializer\Generator\DeserializerGenerator;
use RetailCrm\Api\Component\Serializer\Generator\SerializerGenerator;
use RetailCrm\Api\Component\Serializer\ModelsChecksumGenerator;
@@ -177,6 +177,7 @@ class ModelsGenerator
$configurationArray['classes'][$class] = [];
}
+ PropertyCollection::useIdenticalNamingStrategy();
$configuration = GeneratorConfiguration::createFomArray($configurationArray);
$parsers = [new JMSParser(new AnnotationReader())];
$builder = new Builder(new Parser($parsers), new RecursionChecker(null, []));
diff --git a/src/Component/Serializer/ArraySupportDecorator.php b/src/Component/Serializer/ArraySupportDecorator.php
index 0e97d0e..c3c33b5 100644
--- a/src/Component/Serializer/ArraySupportDecorator.php
+++ b/src/Component/Serializer/ArraySupportDecorator.php
@@ -16,212 +16,424 @@ use Liip\Serializer\Exception\UnsupportedFormatException;
use Liip\Serializer\SerializerInterface;
use Pnz\JsonException\Json;
-/**
- * Class ArraySupportDecorator
- *
- * @category ArraySupportDecorator
- * @package RetailCrm\Api\Component\Serializer
- */
-class ArraySupportDecorator implements SerializerInterface
-{
- /** @var \Liip\Serializer\SerializerInterface */
- private $serializer;
-
+if (PHP_VERSION_ID >= 80000) {
/**
- * ArraySupportDecorator constructor.
+ * Class ArraySupportDecorator
*
- * @param \Liip\Serializer\SerializerInterface $serializer
+ * @category ArraySupportDecorator
+ * @package RetailCrm\Api\Component\Serializer
*/
- public function __construct(SerializerInterface $serializer)
+ class ArraySupportDecorator implements SerializerInterface
{
- $this->serializer = $serializer;
- }
+ /** @var \Liip\Serializer\SerializerInterface */
+ private $serializer;
- /**
- * @inheritDoc
- * @throws \JsonException
- */
- public function serialize($data, string $format, ?Context $context = null): string
- {
- if ('json' !== $format) {
- throw new UnsupportedFormatException('Liip serializer only supports JSON for now');
+ /**
+ * ArraySupportDecorator constructor.
+ *
+ * @param \Liip\Serializer\SerializerInterface $serializer
+ */
+ public function __construct(SerializerInterface $serializer)
+ {
+ $this->serializer = $serializer;
}
- if (is_array($data)) {
- try {
- return Json::encode($this->encodeArray($data, $context), JSON_UNESCAPED_SLASHES);
- } catch (JsonException $exception) {
- throw new Exception(
- sprintf(
- 'Failed to JSON encode data for %s. This is not supposed to happen.',
- // @phpstan-ignore-next-line
- is_object($data) ? get_class($data) : gettype($data)
- ),
- 0,
- $exception
- );
- }
- }
-
- return $this->serializer->serialize($data, $format, $context);
- }
-
- /**
- * @inheritDoc
- */
- public function deserialize(string $data, string $type, string $format, ?Context $context = null)
- {
- if ('json' !== $format) {
- throw new UnsupportedFormatException('Liip serializer only supports JSON for now');
- }
-
- if (static::isArrayType($type)) {
- try {
- $array = Json::decode($data, true);
- } catch (JsonException $exception) {
- throw new Exception('Failed to JSON decode data. This is not supposed to happen.', 0, $exception);
+ /**
+ * @inheritDoc
+ * @throws \JsonException
+ */
+ public function serialize($data, string $format, ?Context $context = null): string
+ {
+ if ('json' !== $format) {
+ throw new UnsupportedFormatException('Liip serializer only supports JSON for now');
}
- return $this->serializer->fromArray($this->decodeArray($array, $type, $context), $type, $context);
- }
-
- return $this->serializer->deserialize($data, $type, $format, $context);
- }
-
- /**
- * @inheritDoc
- *
- * @return array
- */
- public function toArray($data, ?Context $context = null): array
- {
- if (is_array($data)) {
- return $this->encodeArray($data, $context);
- }
-
- return $this->serializer->toArray($data, $context);
- }
-
- /**
- * @inheritDoc
- *
- * @param array $data
- *
- * @return array|object
- */
- public function fromArray(array $data, string $type, ?Context $context = null)
- {
- if (static::isArrayType($type)) {
- return $this->decodeArray($data, $type, $context);
- }
-
- return $this->serializer->fromArray($data, $type, $context);
- }
-
- /**
- * Encodes array of objects into simple multidimensional array.
- *
- * @param mixed[] $data
- * @param \Liip\Serializer\Context|null $context
- *
- * @return mixed[]
- * @throws \Liip\Serializer\Exception\Exception
- * @throws \Liip\Serializer\Exception\UnsupportedTypeException
- */
- private function encodeArray(array $data, ?Context $context = null): array
- {
- $result = [];
-
- foreach ($data as $key => $value) {
- switch (gettype($value)) {
- case 'array':
- $result[$key] = $this->encodeArray($value, $context);
- break;
- case 'object':
- $result[$key] = $this->serializer->toArray($value, $context);
- break;
- default:
- $result[$key] = $value;
- break;
+ if (is_array($data)) {
+ try {
+ return Json::encode($this->encodeArray($data, $context), JSON_UNESCAPED_SLASHES);
+ } catch (JsonException $exception) {
+ throw new Exception(
+ sprintf(
+ 'Failed to JSON encode data for %s. This is not supposed to happen.',
+ // @phpstan-ignore-next-line
+ is_object($data) ? get_class($data) : gettype($data)
+ ),
+ 0,
+ $exception
+ );
+ }
}
+
+ return $this->serializer->serialize($data, $format, $context);
}
- return $result;
- }
+ /**
+ * @inheritDoc
+ */
+ public function deserialize(string $data, string $type, string $format, ?Context $context = null): mixed
+ {
+ if ('json' !== $format) {
+ throw new UnsupportedFormatException('Liip serializer only supports JSON for now');
+ }
- /**
- * Decodes array of arrays to array of objects.
- *
- * @param mixed[] $data
- * @param string $type
- * @param \Liip\Serializer\Context|null $context
- *
- * @return array
- * @throws \Liip\Serializer\Exception\Exception
- * @throws \Liip\Serializer\Exception\UnsupportedTypeException
- */
- private function decodeArray(array $data, string $type, ?Context $context = null): array
- {
- $result = [];
- $subtype = static::getArrayValueType($type);
-
- if (class_exists($subtype)) {
- foreach ($data as $key => $item) {
- if (is_array($item)) {
- $result[$key] = $this->decodeArray($item, $subtype, $context);
- continue;
+ if (static::isArrayType($type)) {
+ try {
+ $array = Json::decode($data, true);
+ } catch (JsonException $exception) {
+ throw new Exception('Failed to JSON decode data. This is not supposed to happen.', 0, $exception);
}
- $result[$key] = $item;
+ return $this->serializer->fromArray($this->decodeArray($array, $type, $context), $type, $context);
+ }
+
+ return $this->serializer->deserialize($data, $type, $format, $context);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @return array
+ */
+ public function toArray($data, ?Context $context = null): array
+ {
+ if (is_array($data)) {
+ return $this->encodeArray($data, $context);
+ }
+
+ return $this->serializer->toArray($data, $context);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param array $data
+ *
+ * @return array|object
+ */
+ public function fromArray(array $data, string $type, ?Context $context = null): mixed
+ {
+ if (static::isArrayType($type)) {
+ return $this->decodeArray($data, $type, $context);
+ }
+
+ return $this->serializer->fromArray($data, $type, $context);
+ }
+
+ /**
+ * Encodes array of objects into simple multidimensional array.
+ *
+ * @param mixed[] $data
+ * @param \Liip\Serializer\Context|null $context
+ *
+ * @return mixed[]
+ * @throws \Liip\Serializer\Exception\Exception
+ * @throws \Liip\Serializer\Exception\UnsupportedTypeException
+ */
+ private function encodeArray(array $data, ?Context $context = null): array
+ {
+ $result = [];
+
+ foreach ($data as $key => $value) {
+ switch (gettype($value)) {
+ case 'array':
+ $result[$key] = $this->encodeArray($value, $context);
+ break;
+ case 'object':
+ $result[$key] = $this->serializer->toArray($value, $context);
+ break;
+ default:
+ $result[$key] = $value;
+ break;
+ }
}
return $result;
}
- return $data;
- }
+ /**
+ * Decodes array of arrays to array of objects.
+ *
+ * @param mixed[] $data
+ * @param string $type
+ * @param \Liip\Serializer\Context|null $context
+ *
+ * @return array
+ * @throws \Liip\Serializer\Exception\Exception
+ * @throws \Liip\Serializer\Exception\UnsupportedTypeException
+ */
+ private function decodeArray(array $data, string $type, ?Context $context = null): array
+ {
+ $result = [];
+ $subtype = static::getArrayValueType($type);
- /**
- * Returns true if provided type is an array.
- *
- * @param string $type
- *
- * @return bool
- */
- private static function isArrayType(string $type): bool
- {
- return false !== strpos($type, 'array');
- }
+ if (class_exists($subtype)) {
+ foreach ($data as $key => $item) {
+ if (is_array($item)) {
+ $result[$key] = $this->decodeArray($item, $subtype, $context);
+ continue;
+ }
- /**
- * Returns array value type from types like 'array' or 'array'.
- *
- * @param string $type
- *
- * @return string
- */
- private static function getArrayValueType(string $type): string
- {
- $matches = [];
+ $result[$key] = $item;
+ }
- preg_match_all(
- '/array(\s+)?\<([\w\|\\\\]+)\s+\,\s+([\w\|\\\\]+)\>/m',
- $type,
- $matches,
- PREG_SET_ORDER,
- 0
- );
+ return $result;
+ }
- if (count($matches) > 0) {
- return $matches[count($matches) - 1];
+ return $data;
}
- preg_match_all('/array(\s+)?\<([\w\|\\\\]+)\>/m', $type, $matches, PREG_SET_ORDER, 0);
-
- if (count($matches) > 0) {
- return $matches[count($matches) - 1];
+ /**
+ * Returns true if provided type is an array.
+ *
+ * @param string $type
+ *
+ * @return bool
+ */
+ private static function isArrayType(string $type): bool
+ {
+ return false !== strpos($type, 'array');
}
- return 'mixed';
+ /**
+ * Returns array value type from types like 'array' or 'array'.
+ *
+ * @param string $type
+ *
+ * @return string
+ */
+ private static function getArrayValueType(string $type): string
+ {
+ $matches = [];
+
+ preg_match_all(
+ '/array(\s+)?\<([\w\|\\\\]+)\s+\,\s+([\w\|\\\\]+)\>/m',
+ $type,
+ $matches,
+ PREG_SET_ORDER,
+ 0
+ );
+
+ if (count($matches) > 0) {
+ return $matches[count($matches) - 1];
+ }
+
+ preg_match_all('/array(\s+)?\<([\w\|\\\\]+)\>/m', $type, $matches, PREG_SET_ORDER, 0);
+
+ if (count($matches) > 0) {
+ return $matches[count($matches) - 1];
+ }
+
+ return 'mixed';
+ }
+ }
+} else {
+ /**
+ * Class ArraySupportDecorator
+ *
+ * @category ArraySupportDecorator
+ * @package RetailCrm\Api\Component\Serializer
+ */
+ class ArraySupportDecorator implements SerializerInterface
+ {
+ /** @var \Liip\Serializer\SerializerInterface */
+ private $serializer;
+
+ /**
+ * ArraySupportDecorator constructor.
+ *
+ * @param \Liip\Serializer\SerializerInterface $serializer
+ */
+ public function __construct(SerializerInterface $serializer)
+ {
+ $this->serializer = $serializer;
+ }
+
+ /**
+ * @inheritDoc
+ * @throws \JsonException
+ */
+ public function serialize($data, string $format, ?Context $context = null): string
+ {
+ if ('json' !== $format) {
+ throw new UnsupportedFormatException('Liip serializer only supports JSON for now');
+ }
+
+ if (is_array($data)) {
+ try {
+ return Json::encode($this->encodeArray($data, $context), JSON_UNESCAPED_SLASHES);
+ } catch (JsonException $exception) {
+ throw new Exception(
+ sprintf(
+ 'Failed to JSON encode data for %s. This is not supposed to happen.',
+ // @phpstan-ignore-next-line
+ is_object($data) ? get_class($data) : gettype($data)
+ ),
+ 0,
+ $exception
+ );
+ }
+ }
+
+ return $this->serializer->serialize($data, $format, $context);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function deserialize(string $data, string $type, string $format, ?Context $context = null)
+ {
+ if ('json' !== $format) {
+ throw new UnsupportedFormatException('Liip serializer only supports JSON for now');
+ }
+
+ if (static::isArrayType($type)) {
+ try {
+ $array = Json::decode($data, true);
+ } catch (JsonException $exception) {
+ throw new Exception('Failed to JSON decode data. This is not supposed to happen.', 0, $exception);
+ }
+
+ return $this->serializer->fromArray($this->decodeArray($array, $type, $context), $type, $context);
+ }
+
+ return $this->serializer->deserialize($data, $type, $format, $context);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @return array
+ */
+ public function toArray($data, ?Context $context = null): array
+ {
+ if (is_array($data)) {
+ return $this->encodeArray($data, $context);
+ }
+
+ return $this->serializer->toArray($data, $context);
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @param array $data
+ *
+ * @return array|object
+ */
+ public function fromArray(array $data, string $type, ?Context $context = null)
+ {
+ if (static::isArrayType($type)) {
+ return $this->decodeArray($data, $type, $context);
+ }
+
+ return $this->serializer->fromArray($data, $type, $context);
+ }
+
+ /**
+ * Encodes array of objects into simple multidimensional array.
+ *
+ * @param mixed[] $data
+ * @param \Liip\Serializer\Context|null $context
+ *
+ * @return mixed[]
+ * @throws \Liip\Serializer\Exception\Exception
+ * @throws \Liip\Serializer\Exception\UnsupportedTypeException
+ */
+ private function encodeArray(array $data, ?Context $context = null): array
+ {
+ $result = [];
+
+ foreach ($data as $key => $value) {
+ switch (gettype($value)) {
+ case 'array':
+ $result[$key] = $this->encodeArray($value, $context);
+ break;
+ case 'object':
+ $result[$key] = $this->serializer->toArray($value, $context);
+ break;
+ default:
+ $result[$key] = $value;
+ break;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Decodes array of arrays to array of objects.
+ *
+ * @param mixed[] $data
+ * @param string $type
+ * @param \Liip\Serializer\Context|null $context
+ *
+ * @return array
+ * @throws \Liip\Serializer\Exception\Exception
+ * @throws \Liip\Serializer\Exception\UnsupportedTypeException
+ */
+ private function decodeArray(array $data, string $type, ?Context $context = null): array
+ {
+ $result = [];
+ $subtype = static::getArrayValueType($type);
+
+ if (class_exists($subtype)) {
+ foreach ($data as $key => $item) {
+ if (is_array($item)) {
+ $result[$key] = $this->decodeArray($item, $subtype, $context);
+ continue;
+ }
+
+ $result[$key] = $item;
+ }
+
+ return $result;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Returns true if provided type is an array.
+ *
+ * @param string $type
+ *
+ * @return bool
+ */
+ private static function isArrayType(string $type): bool
+ {
+ return false !== strpos($type, 'array');
+ }
+
+ /**
+ * Returns array value type from types like 'array' or 'array'.
+ *
+ * @param string $type
+ *
+ * @return string
+ */
+ private static function getArrayValueType(string $type): string
+ {
+ $matches = [];
+
+ preg_match_all(
+ '/array(\s+)?\<([\w\|\\\\]+)\s+\,\s+([\w\|\\\\]+)\>/m',
+ $type,
+ $matches,
+ PREG_SET_ORDER,
+ 0
+ );
+
+ if (count($matches) > 0) {
+ return $matches[count($matches) - 1];
+ }
+
+ preg_match_all('/array(\s+)?\<([\w\|\\\\]+)\>/m', $type, $matches, PREG_SET_ORDER, 0);
+
+ if (count($matches) > 0) {
+ return $matches[count($matches) - 1];
+ }
+
+ return 'mixed';
+ }
}
}
diff --git a/src/Component/Serializer/Generator/DeserializerGenerator.php b/src/Component/Serializer/Generator/DeserializerGenerator.php
index eb46c7d..7eca46a 100644
--- a/src/Component/Serializer/Generator/DeserializerGenerator.php
+++ b/src/Component/Serializer/Generator/DeserializerGenerator.php
@@ -1,11 +1,6 @@
- * @author Pavel Kovalenko
- * @see https://github.com/liip/serializer
- * @internal
- *
- * @SuppressWarnings(PHPMD)
- */
-class DeserializerGenerator
+final class DeserializerGenerator
{
private const FILENAME_PREFIX = 'deserialize';
- /**
- * @var Deserialization
- */
- private $templating;
-
- /**
- * @var \RetailCrm\Api\Component\Serializer\Template\CustomDeserialization
- */
- private $customTemplating;
-
- /**
- * @var Filesystem
- */
+ /** @var \Symfony\Component\Filesystem\Filesystem */
private $filesystem;
- /**
- * @var Builder
- */
+ /** @var \Liip\Serializer\Configuration\GeneratorConfiguration */
+ private $configuration;
+
+ /** @var \Liip\Serializer\Template\Deserialization */
+ private $templating;
+
+ /** @var \RetailCrm\Api\Component\Serializer\Template\CustomDeserialization */
+ private $customTemplating;
+
+ /** @var string */
+ private $cacheDirectory;
+
+ /** @var \Liip\MetadataParser\Builder */
private $metadataBuilder;
/**
- * This is a list of fqn classnames
- *
- * I.e.
- *
- * [
- * Product::class,
- * ];
- *
- * @var string[]
- */
- private $classesToGenerate;
-
- /**
- * @var string
- */
- private $cacheDirectory;
-
- /**
- * @param \Liip\Serializer\Template\Deserialization $templating
- * @param \RetailCrm\Api\Component\Serializer\Template\CustomDeserialization $customTemplating
- * @param string[] $classesToGenerate
- * @param string $cacheDirectory
+ * @param list $classesToGenerate This is a list of FQCN classnames
*/
public function __construct(
Deserialization $templating,
CustomDeserialization $customTemplating,
array $classesToGenerate,
- string $cacheDirectory
+ string $cacheDirectory,
+ GeneratorConfiguration $configuration = null
) {
+ $this->cacheDirectory = $cacheDirectory;
$this->templating = $templating;
$this->customTemplating = $customTemplating;
- $this->classesToGenerate = $classesToGenerate;
- $this->cacheDirectory = $cacheDirectory;
$this->filesystem = new Filesystem();
+ $this->configuration = $this->createGeneratorConfiguration($configuration, $classesToGenerate);
}
- /**
- * @param string $className
- *
- * @return string
- */
public static function buildDeserializerFunctionName(string $className): string
{
- return static::FILENAME_PREFIX . '_' . str_replace('\\', '_', $className);
+ return self::FILENAME_PREFIX.'_'.str_replace('\\', '_', $className);
}
- /**
- * @param \Liip\MetadataParser\Builder $metadataBuilder
- *
- * @throws \Exception
- */
public function generate(Builder $metadataBuilder): void
{
$this->metadataBuilder = $metadataBuilder;
-
$this->filesystem->mkdir($this->cacheDirectory);
- foreach ($this->classesToGenerate as $className) {
+ /** @var ClassToGenerate $classToGenerate */
+ foreach ($this->configuration as $classToGenerate) {
// we do not use the oldest version reducer here and hope for the best
// otherwise we end up with generated property names for accessor methods
- $classMetadata = $metadataBuilder->build($className, [
+ $classMetadata = $metadataBuilder->build($classToGenerate->getClassName(), [
new TakeBestReducer(),
]);
$this->writeFile($classMetadata);
}
}
- /**
- * @param \Liip\MetadataParser\Metadata\ClassMetadata $classMetadata
- *
- * @throws \Exception
- */
private function writeFile(ClassMetadata $classMetadata): void
{
- if (count($classMetadata->getConstructorParameters())) {
- throw new RuntimeException(sprintf(
- 'We currently do not support deserializing when the root class has a non-empty constructor. Class %s',
- $classMetadata->getClassName()
- ));
+ if (\count($classMetadata->getConstructorParameters())) {
+ throw new \Exception(sprintf('We currently do not support deserializing when the root class has a non-empty constructor. Class %s', $classMetadata->getClassName()));
}
- $functionName = static::buildDeserializerFunctionName($classMetadata->getClassName());
+ $functionName = self::buildDeserializerFunctionName($classMetadata->getClassName());
$arrayPath = new ArrayPath('jsonData');
$code = $this->templating->renderFunction(
@@ -162,13 +105,7 @@ class DeserializerGenerator
}
/**
- * @param \Liip\MetadataParser\Metadata\ClassMetadata $classMetadata
- * @param \Liip\Serializer\Path\ArrayPath $arrayPath
- * @param \Liip\Serializer\Path\ModelPath $modelPath
- * @param mixed[] $stack
- *
- * @return string
- * @throws \Exception
+ * @param array $stack
*/
private function generateCodeForClass(
ClassMetadata $classMetadata,
@@ -179,9 +116,9 @@ class DeserializerGenerator
$stack[$classMetadata->getClassName()] = ($stack[$classMetadata->getClassName()] ?? 0) + 1;
$constructorArgumentNames = [];
+ $overwrittenNames = [];
$initCode = '';
$code = '';
-
foreach ($classMetadata->getProperties() as $propertyMetadata) {
$propertyArrayPath = $arrayPath->withFieldName($propertyMetadata->getSerializedName());
@@ -189,6 +126,9 @@ class DeserializerGenerator
$argument = $classMetadata->getConstructorParameter($propertyMetadata->getName());
$default = var_export($argument->isRequired() ? null : $argument->getDefaultValue(), true);
$tempVariable = ModelPath::tempVariable([(string) $modelPath, $propertyMetadata->getName()]);
+ if (\array_key_exists($propertyMetadata->getName(), $constructorArgumentNames)) {
+ $overwrittenNames[$propertyMetadata->getName()] = true;
+ }
$constructorArgumentNames[$propertyMetadata->getName()] = (string) $tempVariable;
$initCode .= $this->templating->renderArgument(
@@ -206,26 +146,21 @@ class DeserializerGenerator
}
$constructorArguments = [];
-
foreach ($classMetadata->getConstructorParameters() as $definition) {
- if (array_key_exists($definition->getName(), $constructorArgumentNames)) {
+ if (\array_key_exists($definition->getName(), $constructorArgumentNames)) {
$constructorArguments[] = $constructorArgumentNames[$definition->getName()];
continue;
}
-
if ($definition->isRequired()) {
- throw new RuntimeException(sprintf(
- 'Unknown constructor argument "%s" in "%s(%s)"',
- $definition->getName(),
- $classMetadata->getClassName(),
- implode(', ', array_keys($constructorArgumentNames))
- ));
+ $msg = sprintf('Unknown constructor argument "%s". Class %s only has properties that tell how to handle %s.', $definition->getName(), $classMetadata->getClassName(), implode(', ', array_keys($constructorArgumentNames)));
+ if ($overwrittenNames) {
+ $msg .= sprintf(' Multiple definitions for fields %s seen - the last one overwrites previous ones.', implode(', ', array_keys($overwrittenNames)));
+ }
+ throw new \Exception($msg);
}
-
$constructorArguments[] = var_export($definition->getDefaultValue(), true);
}
-
- if (count($constructorArgumentNames) > 0) {
+ if (\count($constructorArgumentNames) > 0) {
$code .= $this->templating->renderUnset(array_values($constructorArgumentNames));
}
@@ -233,13 +168,7 @@ class DeserializerGenerator
return $this->generateCustomerInterface($classMetadata, $arrayPath, $modelPath, $initCode, $stack);
}
- return $this->templating->renderClass(
- (string) $modelPath,
- $classMetadata->getClassName(),
- $constructorArguments,
- $code,
- $initCode
- );
+ return $this->templating->renderClass((string) $modelPath, $classMetadata->getClassName(), $constructorArguments, $code, $initCode);
}
/**
@@ -282,13 +211,7 @@ class DeserializerGenerator
}
/**
- * @param \Liip\MetadataParser\Metadata\PropertyMetadata $propertyMetadata
- * @param \Liip\Serializer\Path\ArrayPath $arrayPath
- * @param \Liip\Serializer\Path\ModelPath $modelPath
- * @param mixed[] $stack
- *
- * @return string
- * @throws \Exception
+ * @param array $stack
*/
private function generateCodeForProperty(
PropertyMetadata $propertyMetadata,
@@ -300,16 +223,16 @@ class DeserializerGenerator
return '';
}
+ if (Recursion::hasMaxDepthReached($propertyMetadata, $stack)) {
+ return '';
+ }
+
if ($propertyMetadata->getAccessor()->hasSetterMethod()) {
$tempVariable = ModelPath::tempVariable([(string) $modelPath, $propertyMetadata->getName()]);
$code = $this->generateCodeForField($propertyMetadata, $arrayPath, $tempVariable, $stack);
$code .= $this->templating->renderConditional(
(string) $tempVariable,
- $this->templating->renderSetter(
- (string) $modelPath,
- (string) $propertyMetadata->getAccessor()->getSetterMethod(),
- (string) $tempVariable
- )
+ $this->templating->renderSetter((string) $modelPath, $propertyMetadata->getAccessor()->getSetterMethod(), (string) $tempVariable)
);
$code .= $this->templating->renderUnset([(string) $tempVariable]);
@@ -322,13 +245,7 @@ class DeserializerGenerator
}
/**
- * @param \Liip\MetadataParser\Metadata\PropertyMetadata $propertyMetadata
- * @param \Liip\Serializer\Path\ArrayPath $arrayPath
- * @param \Liip\Serializer\Path\ModelPath $modelPath
- * @param mixed[] $stack
- *
- * @return string
- * @throws \Exception
+ * @param array $stack
*/
private function generateCodeForField(
PropertyMetadata $propertyMetadata,
@@ -342,14 +259,17 @@ class DeserializerGenerator
);
}
+ private function isArrayTraversable(PropertyTypeArray $array): bool
+ {
+ if (method_exists($array, 'isCollection')) {
+ return $array->isCollection();
+ }
+
+ return $array->isTraversable();
+ }
+
/**
- * @param \Liip\MetadataParser\Metadata\PropertyMetadata $propertyMetadata
- * @param \Liip\Serializer\Path\ArrayPath $arrayPath
- * @param \Liip\Serializer\Path\ModelPath $modelPropertyPath
- * @param mixed[] $stack
- *
- * @return string
- * @throws \Exception
+ * @param array $stack
*/
private function generateInnerCodeForFieldType(
PropertyMetadata $propertyMetadata,
@@ -359,64 +279,50 @@ class DeserializerGenerator
): string {
$type = $propertyMetadata->getType();
- if ($type instanceof PropertyTypeArray) {
- if ($type->getSubType() instanceof PropertyTypePrimitive) {
- // for arrays of scalars, copy the field even when its an empty array
- return $this->templating->renderAssignJsonDataToField((string) $modelPropertyPath, (string) $arrayPath);
- }
-
- // either array or hashmap with second param the type of values
- // the index works the same whether its numeric or hashmap
- return $this->generateCodeForArray($type, $arrayPath, $modelPropertyPath, $stack);
- }
-
switch ($type) {
+ case $type instanceof PropertyTypeArray:
+ if ($this->isArrayTraversable($type)) {
+ return $this->generateCodeForArrayCollection($propertyMetadata, $type, $arrayPath, $modelPropertyPath, $stack);
+ }
+
+ return $this->generateCodeForArray($type, $arrayPath, $modelPropertyPath, $stack);
+
case $type instanceof PropertyTypeDateTime:
- if (null !== $type->getZone()) {
- throw new RuntimeException('Timezone support is not implemented');
+ if (method_exists($type, 'getDeserializeFormat')) {
+ $format = $type->getDeserializeFormat();
+
+ if (null !== $format) {
+ return $this->templating->renderAssignDateTimeFromFormat($type->isImmutable(), (string) $modelPropertyPath, (string) $arrayPath, $format, $type->getZone());
+ }
+
+ return $this->templating->renderAssignDateTimeToField($type->isImmutable(), (string) $modelPropertyPath, (string) $arrayPath);
}
- $format = $type->getDeserializeFormat() ?: $type->getFormat();
-
- if (null !== $format) {
- return $this->templating->renderAssignDateTimeFromFormat(
- $type->isImmutable(),
- (string) $modelPropertyPath,
- (string) $arrayPath,
- $format
- );
+ $formats = $type->getDeserializeFormats() ?: (\is_string($type->getFormat()) ? [$type->getFormat()] : $type->getFormat());
+ if (null !== $formats) {
+ return $this->templating->renderAssignDateTimeFromFormat($type->isImmutable(), (string) $modelPropertyPath, (string) $arrayPath, $formats, $type->getZone());
}
- return $this->templating->renderAssignDateTimeToField(
- $type->isImmutable(),
- (string) $modelPropertyPath,
- (string) $arrayPath
- );
+ return $this->templating->renderAssignDateTimeToField($type->isImmutable(), (string) $modelPropertyPath, (string) $arrayPath);
+
case $type instanceof PropertyTypePrimitive && 'float' === $type->getTypeName():
- return $this->templating->renderAssignJsonDataToFieldWithCasting(
- (string) $modelPropertyPath,
- (string) $arrayPath,
- 'float'
- );
+ return $this->templating->renderAssignJsonDataToFieldWithCasting((string) $modelPropertyPath, (string) $arrayPath, 'float');
+
case $type instanceof PropertyTypePrimitive:
case $type instanceof PropertyTypeUnknown:
case $type instanceof PropertyTypeMixed:
return $this->templating->renderAssignJsonDataToField((string) $modelPropertyPath, (string) $arrayPath);
+
case $type instanceof PropertyTypeClass:
return $this->generateCodeForClass($type->getClassMetadata(), $arrayPath, $modelPropertyPath, $stack);
+
default:
- throw new RuntimeException('Unexpected type ' . get_class($type) . ' at ' . $modelPropertyPath);
+ throw new \Exception('Unexpected type '. get_class($type) .' at '.$modelPropertyPath);
}
}
/**
- * @param \Liip\MetadataParser\Metadata\PropertyTypeArray $type
- * @param \Liip\Serializer\Path\ArrayPath $arrayPath
- * @param \Liip\Serializer\Path\ModelPath $modelPath
- * @param mixed[] $stack
- *
- * @return string
- * @throws \Exception
+ * @param array $stack
*/
private function generateCodeForArray(
PropertyTypeArray $type,
@@ -424,6 +330,11 @@ class DeserializerGenerator
ModelPath $modelPath,
array $stack
): string {
+ if ($type->getSubType() instanceof PropertyTypePrimitive) {
+ // for arrays of scalars, copy the field even when its an empty array
+ return $this->templating->renderAssignJsonDataToField((string) $modelPath, (string) $arrayPath);
+ }
+
$index = ModelPath::indexVariable((string) $arrayPath);
$arrayPropertyPath = $arrayPath->withVariable((string) $index);
$modelPropertyPath = $modelPath->withArray((string) $index);
@@ -433,22 +344,16 @@ class DeserializerGenerator
case $subType instanceof PropertyTypeArray:
$innerCode = $this->generateCodeForArray($subType, $arrayPropertyPath, $modelPropertyPath, $stack);
break;
+
case $subType instanceof PropertyTypeClass:
- $innerCode = $this->generateCodeForClass(
- $subType->getClassMetadata(),
- $arrayPropertyPath,
- $modelPropertyPath,
- $stack
- );
+ $innerCode = $this->generateCodeForClass($subType->getClassMetadata(), $arrayPropertyPath, $modelPropertyPath, $stack);
break;
+
case $subType instanceof PropertyTypeUnknown:
- $innerCode = $this->templating->renderAssignJsonDataToField(
- $modelPropertyPath,
- $arrayPropertyPath
- );
- break;
+ return $this->templating->renderAssignJsonDataToField((string) $modelPath, (string) $arrayPath);
+
default:
- throw new RuntimeException('Unexpected array subtype ' . get_class($subType));
+ throw new \Exception('Unexpected array subtype '. get_class($subType));
}
if ('' === $innerCode) {
@@ -460,4 +365,42 @@ class DeserializerGenerator
return $code;
}
+
+ /**
+ * @param array $stack
+ */
+ private function generateCodeForArrayCollection(
+ PropertyMetadata $propertyMetadata,
+ PropertyTypeArray $type,
+ ArrayPath $arrayPath,
+ ModelPath $modelPath,
+ array $stack
+ ): string {
+ $tmpVariable = ModelPath::tempVariable([(string) $modelPath, $propertyMetadata->getName()]);
+ $innerCode = $this->generateCodeForArray($type, $arrayPath, $tmpVariable, $stack);
+
+ if ('' === $innerCode) {
+ return '';
+ }
+
+ return $innerCode.$this->templating->renderArrayCollection((string) $modelPath, (string) $tmpVariable);
+ }
+
+ /**
+ * @param list $classesToGenerate
+ */
+ private function createGeneratorConfiguration(
+ ?GeneratorConfiguration $configuration,
+ array $classesToGenerate
+ ): GeneratorConfiguration {
+ if (null === $configuration) {
+ $configuration = new GeneratorConfiguration([], []);
+ }
+
+ foreach ($classesToGenerate as $className) {
+ $configuration->addClassToGenerate(new ClassToGenerate($configuration, $className));
+ }
+
+ return $configuration;
+ }
}
diff --git a/src/Component/Serializer/Generator/Recursion.php b/src/Component/Serializer/Generator/Recursion.php
new file mode 100644
index 0000000..a54e3a4
--- /dev/null
+++ b/src/Component/Serializer/Generator/Recursion.php
@@ -0,0 +1,57 @@
+ $stack
+ */
+ public static function check(string $className, array $stack, string $modelPath): bool
+ {
+ if (\array_key_exists($className, $stack) && $stack[$className] > 1) {
+ throw new \Exception(sprintf('recursion for %s at %s', key($stack), $modelPath));
+ }
+
+ return false;
+ }
+
+ /**
+ * @param array $stack
+ */
+ public static function hasMaxDepthReached(PropertyMetadata $propertyMetadata, array $stack): bool
+ {
+ if (null === $propertyMetadata->getMaxDepth()) {
+ return false;
+ }
+
+ $className = self::getClassNameFromProperty($propertyMetadata);
+ if (null === $className) {
+ return false;
+ }
+
+ $classStackCount = $stack[$className] ?? 0;
+
+ return $classStackCount > $propertyMetadata->getMaxDepth();
+ }
+
+ private static function getClassNameFromProperty(PropertyMetadata $propertyMetadata): ?string
+ {
+ $type = $propertyMetadata->getType();
+ if ($type instanceof PropertyTypeArray) {
+ $type = $type->getLeafType();
+ }
+
+ if (!($type instanceof PropertyTypeClass)) {
+ return null;
+ }
+
+ return $type->getClassName();
+ }
+}
diff --git a/src/Component/Serializer/Generator/SerializerGenerator.php b/src/Component/Serializer/Generator/SerializerGenerator.php
index d83b55e..dd1b5f5 100644
--- a/src/Component/Serializer/Generator/SerializerGenerator.php
+++ b/src/Component/Serializer/Generator/SerializerGenerator.php
@@ -1,17 +1,9 @@
- * @author Pavel Kovalenko
- * @see https://github.com/liip/serializer
- * @internal
- *
- * @SuppressWarnings(PHPMD)
- */
-class SerializerGenerator
+final class SerializerGenerator
{
private const FILENAME_PREFIX = 'serialize';
- /**
- * @var Serialization
- */
- private $templating;
-
- /**
- * @var \RetailCrm\Api\Component\Serializer\Template\CustomSerialization
- */
- private $customTemplating;
-
- /**
- * @var Builder
- */
- private $metadataBuilder;
-
- /**
- * @var GeneratorConfiguration
- */
- private $configuration;
-
- /**
- * @var string
- */
- private $cacheDirectory;
-
- /**
- * @var Filesystem
- */
+ /** @var \Symfony\Component\Filesystem\Filesystem */
private $filesystem;
- /**
- * SerializerGenerator constructor.
- *
- * @param \Liip\Serializer\Template\Serialization $templating
- * @param \RetailCrm\Api\Component\Serializer\Template\CustomSerialization $customTemplating
- * @param \Liip\Serializer\Configuration\GeneratorConfiguration $configuration
- * @param string $cacheDirectory
- */
+ /** @var \Liip\Serializer\Template\Serialization */
+ private $templating;
+
+ /** @var \Liip\Serializer\Configuration\GeneratorConfiguration */
+ private $configuration;
+
+ /** @var string */
+ private $cacheDirectory;
+
+ /** @var \RetailCrm\Api\Component\Serializer\Template\CustomSerialization */
+ private $customTemplating;
+
+ /** @var \Liip\MetadataParser\Builder */
+ private $metadataBuilder;
+
public function __construct(
Serialization $templating,
CustomSerialization $customTemplating,
GeneratorConfiguration $configuration,
string $cacheDirectory
) {
- $this->templating = $templating;
+ $this->cacheDirectory = $cacheDirectory;
+ $this->configuration = $configuration;
+ $this->templating = $templating;
$this->customTemplating = $customTemplating;
- $this->configuration = $configuration;
- $this->cacheDirectory = $cacheDirectory;
-
$this->filesystem = new Filesystem();
}
/**
- * @param string $className
- * @param string|null $apiVersion
- * @param array $serializerGroups
- *
- * @return string
+ * @param list $serializerGroups
*/
- public static function buildSerializerFunctionName(
- string $className,
- ?string $apiVersion,
- array $serializerGroups
- ): string {
- $functionName = static::FILENAME_PREFIX . '_' . $className;
-
- if (count($serializerGroups)) {
- $functionName .= '_' . implode('_', $serializerGroups);
+ public static function buildSerializerFunctionName(string $className, ?string $apiVersion, array $serializerGroups): string
+ {
+ $functionName = self::FILENAME_PREFIX.'_'.$className;
+ if (\count($serializerGroups)) {
+ $functionName .= '_'.implode('_', $serializerGroups);
}
-
if (null !== $apiVersion) {
- $functionName .= '_' . $apiVersion;
+ $functionName .= '_'.$apiVersion;
}
- return (string) preg_replace('/[^a-zA-Z0-9_]/', '_', $functionName);
+ return preg_replace('/[^a-zA-Z0-9_]/', '_', $functionName);
}
- /**
- * @param \Liip\MetadataParser\Builder $metadataBuilder
- */
public function generate(Builder $metadataBuilder): void
{
$this->metadataBuilder = $metadataBuilder;
-
$this->filesystem->mkdir($this->cacheDirectory);
foreach ($this->configuration as $classToGenerate) {
foreach ($classToGenerate as $groupCombination) {
$className = $classToGenerate->getClassName();
-
foreach ($groupCombination->getVersions() as $version) {
+ $groups = $groupCombination->getGroups();
if ('' === $version) {
- $metadata = $metadataBuilder->build($className, [
- new PreferredReducer(),
- new TakeBestReducer(),
- ]);
-
- $this->writeFile($className, null, $groupCombination->getGroups(), $metadata);
+ if ([] === $groups) {
+ $metadata = $metadataBuilder->build($className, [
+ new PreferredReducer(),
+ new TakeBestReducer(),
+ ]);
+ $this->writeFile($className, null, [], $metadata);
+ } else {
+ $metadata = $metadataBuilder->build($className, [
+ new GroupReducer($groups),
+ new PreferredReducer(),
+ new TakeBestReducer(),
+ ]);
+ $this->writeFile($className, null, $groups, $metadata);
+ }
} else {
$metadata = $metadataBuilder->build($className, [
new VersionReducer($version),
- new GroupReducer($groupCombination->getGroups()),
+ new GroupReducer($groups),
new TakeBestReducer(),
]);
-
- $this->writeFile($className, $version, $groupCombination->getGroups(), $metadata);
+ $this->writeFile($className, $version, $groups, $metadata);
}
}
}
@@ -168,10 +119,7 @@ class SerializerGenerator
}
/**
- * @param string $className
- * @param string|null $apiVersion
- * @param array $serializerGroups
- * @param \Liip\MetadataParser\Metadata\ClassMetadata $classMetadata
+ * @param list $serializerGroups
*/
private function writeFile(
string $className,
@@ -179,8 +127,7 @@ class SerializerGenerator
array $serializerGroups,
ClassMetadata $classMetadata
): void {
- sort($serializerGroups);
- $functionName = static::buildSerializerFunctionName($className, $apiVersion, $serializerGroups);
+ $functionName = self::buildSerializerFunctionName($className, $apiVersion, $serializerGroups);
$code = $this->templating->renderFunction(
$functionName,
@@ -192,15 +139,8 @@ class SerializerGenerator
}
/**
- * @param \Liip\MetadataParser\Metadata\ClassMetadata $classMetadata
- * @param string|null $apiVersion
- * @param array $serializerGroups
- * @param string $arrayPath
- * @param string $modelPath
- * @param array $stack
- *
- * @return string
- * @throws \Exception
+ * @param list $serializerGroups
+ * @param array $stack
*/
private function generateCodeForClass(
ClassMetadata $classMetadata,
@@ -226,17 +166,10 @@ class SerializerGenerator
}
$stack[$classMetadata->getClassName()] = ($stack[$classMetadata->getClassName()] ?? 0) + 1;
- $code = '';
+ $code = '';
foreach ($classMetadata->getProperties() as $propertyMetadata) {
- $code .= $this->generateCodeForField(
- $propertyMetadata,
- $apiVersion,
- $serializerGroups,
- $arrayPath,
- $modelPath,
- $stack
- );
+ $code .= $this->generateCodeForField($propertyMetadata, $apiVersion, $serializerGroups, $arrayPath, $modelPath, $stack);
}
return $this->templating->renderClass($arrayPath, $code);
@@ -334,15 +267,8 @@ class SerializerGenerator
}
/**
- * @param \Liip\MetadataParser\Metadata\PropertyMetadata $propertyMetadata
- * @param string|null $apiVersion
- * @param array $serializerGroups
- * @param string $arrayPath
- * @param string $modelPath
- * @param array $stack
- *
- * @return string
- * @throws \Exception
+ * @param list $serializerGroups
+ * @param array $stack
*/
private function generateCodeForField(
PropertyMetadata $propertyMetadata,
@@ -352,61 +278,34 @@ class SerializerGenerator
string $modelPath,
array $stack
): string {
- $modelPropertyPath = $modelPath . '->' . $propertyMetadata->getName();
- $fieldPath = $arrayPath . '["' . $propertyMetadata->getSerializedName() . '"]';
+ if (Recursion::hasMaxDepthReached($propertyMetadata, $stack)) {
+ return '';
+ }
+
+ $modelPropertyPath = $modelPath.'->'.$propertyMetadata->getName();
+ $fieldPath = $arrayPath.'["'.$propertyMetadata->getSerializedName().'"]';
if ($propertyMetadata->getAccessor()->hasGetterMethod()) {
- $tempVariable = str_replace(['->', '[', ']', '$'], '', $modelPath) . ucfirst($propertyMetadata->getName());
+ $tempVariable = str_replace(['->', '[', ']', '$'], '', $modelPath).ucfirst($propertyMetadata->getName());
return $this->templating->renderConditional(
- $this->templating->renderTempVariable(
- $tempVariable,
- $this->templating->renderGetter(
- $modelPath,
- (string) $propertyMetadata->getAccessor()->getGetterMethod()
- )
- ),
- $this->generateCodeForFieldType(
- $propertyMetadata->getType(),
- $apiVersion,
- $serializerGroups,
- $fieldPath,
- '$' . $tempVariable,
- $stack
- )
+ $this->templating->renderTempVariable($tempVariable, $this->templating->renderGetter($modelPath, $propertyMetadata->getAccessor()->getGetterMethod())),
+ $this->generateCodeForFieldType($propertyMetadata->getType(), $apiVersion, $serializerGroups, $fieldPath, '$'.$tempVariable, $stack)
);
}
if (!$propertyMetadata->isPublic()) {
- throw new RuntimeException(sprintf(
- 'Property %s is not public and no getter has been defined. Stack %s',
- $modelPropertyPath,
- var_export($stack, true)
- ));
+ throw new \Exception(sprintf('Property %s is not public and no getter has been defined. Stack %s', $modelPropertyPath, var_export($stack, true)));
}
return $this->templating->renderConditional(
$modelPropertyPath,
- $this->generateCodeForFieldType(
- $propertyMetadata->getType(),
- $apiVersion,
- $serializerGroups,
- $fieldPath,
- $modelPropertyPath,
- $stack
- )
+ $this->generateCodeForFieldType($propertyMetadata->getType(), $apiVersion, $serializerGroups, $fieldPath, $modelPropertyPath, $stack)
);
}
/**
- * @param \Liip\MetadataParser\Metadata\PropertyType $type
- * @param string|null $apiVersion
- * @param array $serializerGroups
- * @param string $fieldPath
- * @param string $modelPropertyPath
- * @param array $stack
- *
- * @return string
- * @throws \Exception
+ * @param list $serializerGroups
+ * @param array $stack
*/
private function generateCodeForFieldType(
PropertyType $type,
@@ -416,31 +315,9 @@ class SerializerGenerator
string $modelPropertyPath,
array $stack
): string {
- if ($type instanceof PropertyTypeArray) {
- if ($type->getSubType() instanceof PropertyTypePrimitive) {
- // for arrays of scalars, copy the field even when its an empty array
- return $this->templating->renderAssign($fieldPath, $modelPropertyPath);
- }
-
- // either array or hashmap with second param the type of values
- // the index works the same whether its numeric or hashmap
- return $this->generateCodeForArray(
- $type,
- $apiVersion,
- $serializerGroups,
- $fieldPath,
- $modelPropertyPath,
- $stack
- );
- }
-
switch ($type) {
case $type instanceof PropertyTypeDateTime:
- if (null !== $type->getZone()) {
- throw new \RuntimeException('Timezone support is not implemented');
- }
-
- $dateFormat = $type->getFormat() ?: DateTime::ATOM;
+ $dateFormat = $type->getFormat() ?: \DateTimeInterface::ISO8601;
return $this->templating->renderAssign(
$fieldPath,
@@ -454,30 +331,19 @@ class SerializerGenerator
return $this->templating->renderAssign($fieldPath, $modelPropertyPath);
case $type instanceof PropertyTypeClass:
- return $this->generateCodeForClass(
- $type->getClassMetadata(),
- $apiVersion,
- $serializerGroups,
- $fieldPath,
- $modelPropertyPath,
- $stack
- );
+ return $this->generateCodeForClass($type->getClassMetadata(), $apiVersion, $serializerGroups, $fieldPath, $modelPropertyPath, $stack);
+
+ case $type instanceof PropertyTypeArray:
+ return $this->generateCodeForArray($type, $apiVersion, $serializerGroups, $fieldPath, $modelPropertyPath, $stack);
default:
- throw new RuntimeException('Unexpected type ' . \get_class($type) . ' at ' . $modelPropertyPath);
+ throw new \Exception('Unexpected type '. get_class($type) .' at '.$modelPropertyPath);
}
}
/**
- * @param \Liip\MetadataParser\Metadata\PropertyTypeArray $type
- * @param string|null $apiVersion
- * @param array $serializerGroups
- * @param string $arrayPath
- * @param string $modelPath
- * @param array $stack
- *
- * @return string
- * @throws \Exception
+ * @param list $serializerGroups
+ * @param array $stack
*/
private function generateCodeForArray(
PropertyTypeArray $type,
@@ -487,35 +353,25 @@ class SerializerGenerator
string $modelPath,
array $stack
): string {
- $index = '$index' . \mb_strlen($arrayPath);
+ $index = '$index'.mb_strlen($arrayPath);
$subType = $type->getSubType();
switch ($subType) {
- case $subType instanceof PropertyTypeArray:
- $innerCode = $this->generateCodeForArray(
- $subType,
- $apiVersion,
- $serializerGroups,
- $arrayPath . '[' . $index . ']',
- $modelPath . '[' . $index . ']',
- $stack
- );
- break;
- case $subType instanceof PropertyTypeClass:
- $innerCode = $this->generateCodeForClass(
- $subType->getClassMetadata(),
- $apiVersion,
- $serializerGroups,
- $arrayPath . '[' . $index . ']',
- $modelPath . '[' . $index . ']',
- $stack
- );
- break;
+ case $subType instanceof PropertyTypePrimitive:
+ case $subType instanceof PropertyTypeArray && self::isArrayForPrimitive($subType):
case $subType instanceof PropertyTypeUnknown:
- $innerCode = $this->templating->renderAssign($arrayPath, $modelPath);
+ return $this->templating->renderArrayAssign($arrayPath, $modelPath);
+
+ case $subType instanceof PropertyTypeArray:
+ $innerCode = $this->generateCodeForArray($subType, $apiVersion, $serializerGroups, $arrayPath.'['.$index.']', $modelPath.'['.$index.']', $stack);
break;
+
+ case $subType instanceof PropertyTypeClass:
+ $innerCode = $this->generateCodeForClass($subType->getClassMetadata(), $apiVersion, $serializerGroups, $arrayPath.'['.$index.']', $modelPath.'['.$index.']', $stack);
+ break;
+
default:
- throw new RuntimeException('Unexpected array subtype ' . get_class($subType));
+ throw new \Exception('Unexpected array subtype '. get_class($subType));
}
if ('' === $innerCode) {
@@ -532,4 +388,16 @@ class SerializerGenerator
return $this->templating->renderLoopArray($arrayPath, $modelPath, $index, $innerCode);
}
+
+ private static function isArrayForPrimitive(PropertyTypeArray $type): bool
+ {
+ do {
+ $type = $type->getSubType();
+ if ($type instanceof PropertyTypePrimitive) {
+ return true;
+ }
+ } while ($type instanceof PropertyTypeArray);
+
+ return false;
+ }
}
diff --git a/src/Component/Serializer/Parser/BaseJMSParser.php b/src/Component/Serializer/Parser/BaseJMSParser.php
deleted file mode 100644
index 8f484d8..0000000
--- a/src/Component/Serializer/Parser/BaseJMSParser.php
+++ /dev/null
@@ -1,197 +0,0 @@
-
- * @internal
- */
-final class BaseJMSParser
-{
- /**
- * @var \RetailCrm\Api\Component\Serializer\Parser\JMSLexer
- */
- private $lexer;
-
- /**
- * @var bool
- */
- private $root = true;
-
- /**
- * @param string $string
- *
- * @return array|mixed[]
- */
- public function parse(string $string): array
- {
- $this->lexer = new JMSLexer();
- $this->lexer->setInput($string);
- $this->lexer->moveNext();
-
- return $this->visit();
- }
-
- /**
- * @return mixed
- *
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- */
- private function visit()
- {
- $this->lexer->moveNext();
-
- if (!$this->lexer->token) {
- throw new SyntaxError(
- 'Syntax error, unexpected end of stream'
- );
- }
-
- if (JMSLexer::T_FLOAT === $this->lexer->token['type']) {
- return (float)$this->lexer->token['value'];
- } elseif (JMSLexer::T_INTEGER === $this->lexer->token['type']) {
- return (int)$this->lexer->token['value'];
- } elseif (JMSLexer::T_NULL === $this->lexer->token['type']) {
- return null;
- } elseif (JMSLexer::T_STRING === $this->lexer->token['type']) {
- return $this->lexer->token['value'];
- } elseif (JMSLexer::T_IDENTIFIER === $this->lexer->token['type']) {
- if ($this->lexer->isNextToken(JMSLexer::T_TYPE_START)) {
- return $this->visitCompoundType();
- } elseif ($this->lexer->isNextToken(JMSLexer::T_ARRAY_START)) {
- return $this->visitArrayType();
- }
-
- return $this->visitSimpleType();
- } elseif (!$this->root && JMSLexer::T_ARRAY_START === $this->lexer->token['type']) {
- return $this->visitArrayType();
- }
-
- throw new SyntaxError(sprintf(
- 'Syntax error, unexpected "%s" (%s)',
- $this->lexer->token['value'],
- // @phpstan-ignore-next-line
- $this->getConstant($this->lexer->token['type'])
- ));
- }
-
- /**
- * @return mixed[]
- */
- private function visitSimpleType(): array
- {
- $value = $this->lexer->token['value']; // @phpstan-ignore-line
-
- return ['name' => $value, 'params' => []];
- }
-
- /**
- * @return array
- */
- private function visitCompoundType(): array
- {
- $this->root = false;
- $name = $this->lexer->token['value']; // @phpstan-ignore-line
- $this->match(JMSLexer::T_TYPE_START);
-
- $params = [];
- if (!$this->lexer->isNextToken(JMSLexer::T_TYPE_END)) {
- while (true) {
- $params[] = $this->visit();
-
- if ($this->lexer->isNextToken(JMSLexer::T_TYPE_END)) {
- break;
- }
-
- $this->match(JMSLexer::T_COMMA);
- }
- }
-
- $this->match(JMSLexer::T_TYPE_END);
-
- return [
- 'name' => $name,
- 'params' => $params,
- ];
- }
-
- /**
- * @return array
- */
- private function visitArrayType(): array
- {
- /*
- * Here we should call $this->match(JMSLexer::T_ARRAY_START); to make it clean
- * but the token has already been consumed by moveNext() in visit()
- */
- $params = [];
-
- if (!$this->lexer->isNextToken(JMSLexer::T_ARRAY_END)) {
- while (true) {
- $params[] = $this->visit();
-
- if ($this->lexer->isNextToken(JMSLexer::T_ARRAY_END)) {
- break;
- }
-
- $this->match(JMSLexer::T_COMMA);
- }
- }
-
- $this->match(JMSLexer::T_ARRAY_END);
-
- return $params;
- }
-
- /**
- * @param int $token
- */
- private function match(int $token): void
- {
- if (!$this->lexer->lookahead) {
- throw new SyntaxError(
- sprintf('Syntax error, unexpected end of stream, expected %s', $this->getConstant($token))
- );
- }
-
- if ($this->lexer->lookahead['type'] === $token) {
- $this->lexer->moveNext();
-
- return;
- }
-
- throw new SyntaxError(sprintf(
- 'Syntax error, unexpected "%s" (%s), expected was %s',
- $this->lexer->lookahead['value'],
- $this->getConstant($this->lexer->lookahead['type']), // @phpstan-ignore-line
- $this->getConstant($token)
- ));
- }
-
- /**
- * @param int $value
- *
- * @return string
- */
- private function getConstant(int $value): string
- {
- $oClass = new ReflectionClass(JMSLexer::class);
-
- return (string) array_search($value, $oClass->getConstants());
- }
-}
diff --git a/src/Component/Serializer/Parser/JMSCore/Exception/Exception.php b/src/Component/Serializer/Parser/JMSCore/Exception/Exception.php
new file mode 100644
index 0000000..11f7919
--- /dev/null
+++ b/src/Component/Serializer/Parser/JMSCore/Exception/Exception.php
@@ -0,0 +1,14 @@
+
+ */
+interface Exception extends \Throwable
+{
+}
diff --git a/src/Component/Serializer/Parser/JMSCore/Type/Exception/Exception.php b/src/Component/Serializer/Parser/JMSCore/Type/Exception/Exception.php
new file mode 100644
index 0000000..236ebf0
--- /dev/null
+++ b/src/Component/Serializer/Parser/JMSCore/Type/Exception/Exception.php
@@ -0,0 +1,11 @@
+getType($type);
- } catch (Throwable $e) {
+ } catch (\Throwable $e) {
throw new SyntaxError($e->getMessage(), 0, $e);
}
}
- /**
- * @return string[]
- */
protected function getCatchablePatterns(): array
{
return [
@@ -63,18 +47,15 @@ final class JMSLexer extends AbstractLexer
];
}
- /**
- * @return string[]
- */
protected function getNonCatchablePatterns(): array
{
return ['\s+'];
}
/**
- * {{@inheritDoc}}
+ * {@inheritDoc}
*
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @return int|string|null
*/
protected function getType(&$value)
{
diff --git a/src/Component/Serializer/Parser/JMSCore/Type/Parser.php b/src/Component/Serializer/Parser/JMSCore/Type/Parser.php
new file mode 100644
index 0000000..4ad29f1
--- /dev/null
+++ b/src/Component/Serializer/Parser/JMSCore/Type/Parser.php
@@ -0,0 +1,220 @@
+input = $type;
+
+ $this->lexer = new Lexer();
+ $this->lexer->setInput($type);
+ $this->lexer->moveNext();
+
+ return $this->visit();
+ }
+
+ /**
+ * @return mixed
+ */
+ private function visit(bool $fetchingParam = false)
+ {
+ $this->lexer->moveNext();
+
+ if (!$this->lexer->token) {
+ throw new SyntaxError(
+ 'Syntax error, unexpected end of stream',
+ );
+ }
+
+ if (is_array($this->lexer->token)) {
+ $this->token = Token::fromArray($this->lexer->token);
+ } else {
+ $this->token = Token::fromObject($this->lexer->token);
+ }
+
+ /*
+ * There is a difference in the behavior of the tokenizer depending on the version of the lexer. This affects
+ * how the lexer interprets date formats for compound date types. In our case it has a huge impact because
+ * we're using `DateTime<'Y-m-d H:i:s'>` in the API. Newer lexer versions will treat the whole format
+ * (`Y-m-d H:i:s`) as a token of string type. Older lexer versions will treat each symbol of the format as
+ * a single token, and this will inevitably break type parsing during model compilation (it will say something
+ * about unexpected token).
+ *
+ * The condition below is used to work around this, albeit in a very hacky way. The token it tries to detect is
+ * actually a single quote ('). After detecting this token and determining that this time parser has been
+ * called recursively for a compound type, it will try to determine the parameter length and extract it as
+ * a single token. In other words, we're trying to mimic newer lexer behavior while using an older version.
+ *
+ * This implementation is very limited, as it may fail abruptly with more complex compound types, also it
+ * may fail to extract the type correctly if it has different whitespace symbols / multiple whitespace symbols.
+ * It was tested only with the compound date type mentioned above.
+ *
+ * A special fallback parser would be a better solution, but it would take much more time and effort to create.
+ */
+ if ("" === $this->token->value && $fetchingParam) {
+ $len = 0;
+ $this->lexer->moveNext();
+
+ while (true) {
+ if (is_array($this->lexer->token)) {
+ $this->token = Token::fromArray($this->lexer->token);
+ } else {
+ $this->token = Token::fromObject($this->lexer->token);
+ }
+
+ if ("" === $this->token->value) {
+ $len++;
+ break;
+ }
+
+ $len += strlen($this->token->value);
+ $this->lexer->moveNext();
+ }
+
+ return substr($this->input, 9, $len + substr_count($this->input, ' '));
+ }
+
+ if (Lexer::T_FLOAT === $this->token->type) {
+ return floatval($this->token->value);
+ } elseif (Lexer::T_INTEGER === $this->token->type) {
+ return intval($this->token->value);
+ } elseif (Lexer::T_NULL === $this->token->type) {
+ return null;
+ } elseif (Lexer::T_STRING === $this->token->type) {
+ return $this->token->value;
+ } elseif (Lexer::T_IDENTIFIER === $this->token->type) {
+ if ($this->lexer->isNextToken(Lexer::T_TYPE_START)) {
+ return $this->visitCompoundType();
+ } elseif ($this->lexer->isNextToken(Lexer::T_ARRAY_START)) {
+ return $this->visitArrayType();
+ }
+
+ return $this->visitSimpleType();
+ } elseif (!$this->root && Lexer::T_ARRAY_START === $this->token->type) {
+ return $this->visitArrayType();
+ }
+
+ throw new SyntaxError(sprintf(
+ 'Syntax error, unexpected "%s" (%s)',
+ $this->token->value,
+ $this->getConstant($this->token->type),
+ ));
+ }
+
+ /**
+ * @return string|mixed[]
+ */
+ private function visitSimpleType()
+ {
+ $value = $this->token->value;
+
+ return ['name' => $value, 'params' => []];
+ }
+
+ private function visitCompoundType(): array
+ {
+ $this->root = false;
+ $name = $this->token->value;
+ $this->match(Lexer::T_TYPE_START);
+
+ $params = [];
+ if (!$this->lexer->isNextToken(Lexer::T_TYPE_END)) {
+ while (true) {
+ $params[] = $this->visit(true);
+
+ if ($this->lexer->isNextToken(Lexer::T_TYPE_END)) {
+ break;
+ }
+
+ $this->match(Lexer::T_COMMA);
+ }
+ }
+
+ $this->match(Lexer::T_TYPE_END);
+
+ return [
+ 'name' => $name,
+ 'params' => $params,
+ ];
+ }
+
+ private function visitArrayType(): array
+ {
+ /*
+ * Here we should call $this->match(Lexer::T_ARRAY_START); to make it clean
+ * but the token has already been consumed by moveNext() in visit()
+ */
+
+ $params = [];
+ if (!$this->lexer->isNextToken(Lexer::T_ARRAY_END)) {
+ while (true) {
+ $params[] = $this->visit();
+ if ($this->lexer->isNextToken(Lexer::T_ARRAY_END)) {
+ break;
+ }
+
+ $this->match(Lexer::T_COMMA);
+ }
+ }
+
+ $this->match(Lexer::T_ARRAY_END);
+
+ return $params;
+ }
+
+ private function match(int $token): void
+ {
+ if (!$this->lexer->lookahead) {
+ throw new SyntaxError(
+ sprintf('Syntax error, unexpected end of stream, expected %s', $this->getConstant($token)),
+ );
+ }
+
+ if (is_array($this->lexer->lookahead)) {
+ $lookahead = Token::fromArray($this->lexer->lookahead);
+ } else {
+ $lookahead = Token::fromObject($this->lexer->lookahead);
+ }
+
+ if ($lookahead->type === $token) {
+ $this->lexer->moveNext();
+
+ return;
+ }
+
+ throw new SyntaxError(sprintf(
+ 'Syntax error, unexpected "%s" (%s), expected was %s',
+ $lookahead->value,
+ $this->getConstant($lookahead->type),
+ $this->getConstant($token),
+ ));
+ }
+
+ private function getConstant(int $value): string
+ {
+ $oClass = new \ReflectionClass(Lexer::class);
+
+ return array_search($value, $oClass->getConstants());
+ }
+}
diff --git a/src/Component/Serializer/Parser/JMSCore/Type/ParserInterface.php b/src/Component/Serializer/Parser/JMSCore/Type/ParserInterface.php
new file mode 100644
index 0000000..32e56ab
--- /dev/null
+++ b/src/Component/Serializer/Parser/JMSCore/Type/ParserInterface.php
@@ -0,0 +1,10 @@
+value, $source->type, $source->position);
+ }
+
+ /**
+ * The string value of the token in the input string
+ *
+ * @readonly
+ * @var string|int
+ */
+ public $value;
+
+ /**
+ * The type of the token (identifier, numeric, string, input parameter, none)
+ *
+ * @readonly
+ * @var T|null
+ */
+ public $type;
+
+ /**
+ * The position of the token in the input string
+ *
+ * @var int
+ *
+ * @readonly
+ */
+ public $position;
+
+ /**
+ * @param string|int $value
+ * @param string|int $type
+ */
+ public function __construct($value, $type, int $position)
+ {
+ $this->value = $value;
+ $this->type = $type;
+ $this->position = $position;
+ }
+
+ /** @param T ...$types */
+ public function isA(...$types): bool
+ {
+ return in_array($this->type, $types, true);
+ }
+}
diff --git a/src/Component/Serializer/Parser/JMSParser.php b/src/Component/Serializer/Parser/JMSParser.php
index 9034218..cb2e0a8 100644
--- a/src/Component/Serializer/Parser/JMSParser.php
+++ b/src/Component/Serializer/Parser/JMSParser.php
@@ -1,21 +1,18 @@
- * @author Pavel Kovalenko
- * @see https://github.com/liip/metadata-parser
* @internal
- *
- * @SuppressWarnings(PHPMD)
*/
class JMSParser implements ModelParserInterface
{
private const ACCESS_ORDER_CUSTOM = 'custom';
- /**
- * @var Reader
- */
+ /** @var \Doctrine\Common\Annotations\Reader */
private $annotationsReader;
- /**
- * @var PhpTypeParser
- */
+ /** @var \Liip\MetadataParser\TypeParser\PhpTypeParser */
private $phpTypeParser;
- /**
- * @var JMSTypeParser
- */
- private $jmsTypeParser;
+ /** @var \RetailCrm\Api\Component\Serializer\Parser\JMSTypeParser */
+ protected $jmsTypeParser;
- /**
- * JMSParser constructor.
- *
- * @param \Doctrine\Common\Annotations\Reader $annotationsReader
- */
public function __construct(Reader $annotationsReader)
{
$this->annotationsReader = $annotationsReader;
@@ -82,124 +57,99 @@ class JMSParser implements ModelParserInterface
$this->jmsTypeParser = new JMSTypeParser();
}
- /**
- * @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata
- */
public function parse(RawClassMetadata $classMetadata): void
{
try {
- $refClass = new ReflectionClass($classMetadata->getClassName()); // @phpstan-ignore-line
- } catch (ReflectionException $exception) {
- throw ParseException::classNotFound($classMetadata->getClassName(), $exception);
+ $reflClass = new \ReflectionClass($classMetadata->getClassName());
+ } catch (\ReflectionException $e) {
+ throw ParseException::classNotFound($classMetadata->getClassName(), $e);
}
- $this->parseProperties($refClass, $classMetadata);
- $this->parseMethods($refClass, $classMetadata);
- $this->parseClass($refClass, $classMetadata);
+ try {
+ $this->parseProperties($reflClass, $classMetadata);
+ $this->parseMethods($reflClass, $classMetadata);
+ $this->parseClass($reflClass, $classMetadata);
+ } catch (SyntaxError $exception) {
+ throw new ParseException($exception->getMessage(), $exception->getCode(), $exception);
+ }
}
- /**
- * @param \ReflectionClass $refClass
- * @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata
- *
- * @phpstan-ignore-next-line
- */
- private function parseProperties(ReflectionClass $refClass, RawClassMetadata $classMetadata): void
+ private function parseProperties(\ReflectionClass $reflClass, RawClassMetadata $classMetadata): void
{
- if ($refParentClass = $refClass->getParentClass()) {
- $this->parseProperties($refParentClass, $classMetadata);
+ if ($reflParentClass = $reflClass->getParentClass()) {
+ $this->parseProperties($reflParentClass, $classMetadata);
}
- foreach ($refClass->getProperties() as $refProperty) {
+ foreach ($reflClass->getProperties() as $reflProperty) {
try {
- $annotations = $this->annotationsReader->getPropertyAnnotations($refProperty);
- } catch (AnnotationException $exception) {
- throw ParseException::propertyError((string) $classMetadata, $refProperty->getName(), $exception);
+ $annotations = $this->annotationsReader->getPropertyAnnotations($reflProperty);
+ } catch (AnnotationException $e) {
+ throw ParseException::propertyError((string) $classMetadata, $reflProperty->getName(), $e);
}
- $property = $this->getProperty($classMetadata, $refProperty, $annotations);
+ $property = $this->getProperty($classMetadata, $reflProperty, $annotations);
$this->parsePropertyAnnotations($classMetadata, $property, $annotations);
}
}
- /**
- * @param \ReflectionClass $refClass
- * @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata
- *
- * @phpstan-ignore-next-line
- */
- private function parseMethods(ReflectionClass $refClass, RawClassMetadata $classMetadata): void
+ private function parseMethods(\ReflectionClass $reflClass, RawClassMetadata $classMetadata): void
{
- if ($refParentClass = $refClass->getParentClass()) {
- $this->parseMethods($refParentClass, $classMetadata);
+ if ($reflParentClass = $reflClass->getParentClass()) {
+ $this->parseMethods($reflParentClass, $classMetadata);
}
- foreach ($refClass->getMethods() as $refMethod) {
- if (false === $refMethod->getDocComment()) {
- continue;
- }
-
+ foreach ($reflClass->getMethods() as $reflMethod) {
try {
- $annotations = $this->annotationsReader->getMethodAnnotations($refMethod);
- } catch (AnnotationException $exception) {
- throw ParseException::propertyError((string) $classMetadata, $refMethod->getName(), $exception);
+ $annotations = $this->annotationsReader->getMethodAnnotations($reflMethod);
+ } catch (AnnotationException $e) {
+ throw ParseException::propertyError((string) $classMetadata, $reflMethod->getName(), $e);
}
if ($this->isVirtualProperty($annotations)) {
- if (!$refMethod->isPublic()) {
- throw ParseException::nonPublicMethod((string) $classMetadata, $refMethod->getName());
+ if (!$reflMethod->isPublic()) {
+ throw ParseException::nonPublicMethod((string) $classMetadata, $reflMethod->getName());
}
- $methodName = $this->getMethodName($annotations, $refMethod);
+ $methodName = $this->getMethodName($annotations, $reflMethod);
$name = $this->getSerializedName($annotations) ?: $methodName;
$property = new PropertyVariationMetadata($methodName, true, true);
$classMetadata->addPropertyVariation($name, $property);
- $property->setType($this->getReturnType($property, $refMethod, $refClass));
- $property->setAccessor(new PropertyAccessor($refMethod->getName(), null));
+ $property->setType($this->getReturnType($property, $reflMethod, $reflClass));
+ $property->setAccessor(new PropertyAccessor($reflMethod->getName(), null));
$this->parsePropertyAnnotations($classMetadata, $property, $annotations);
}
if ($this->isPostDeserializeMethod($annotations)) {
- if (!$refMethod->isPublic()) {
- throw ParseException::nonPublicMethod((string) $classMetadata, $refMethod->getName());
+ if (!$reflMethod->isPublic()) {
+ throw ParseException::nonPublicMethod((string) $classMetadata, $reflMethod->getName());
}
- $classMetadata->addPostDeserializeMethod($refMethod->getName());
+ $classMetadata->addPostDeserializeMethod($reflMethod->getName());
}
}
}
- /**
- * @param \ReflectionClass $refClass
- * @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata
- *
- * @phpstan-ignore-next-line
- */
- private function parseClass(ReflectionClass $refClass, RawClassMetadata $classMetadata): void
+ private function parseClass(\ReflectionClass $reflClass, RawClassMetadata $classMetadata): void
{
try {
- $annotations = $this->gatherClassAnnotations($refClass);
+ $annotations = $this->gatherClassAnnotations($reflClass);
} catch (AnnotationException $e) {
- throw ParseException::classError($refClass->getName(), $e);
+ throw ParseException::classError($reflClass->getName(), $e);
}
foreach ($annotations as $annotation) {
switch (true) {
case $annotation instanceof AccessorOrder:
if (self::ACCESS_ORDER_CUSTOM !== $annotation->order) {
- throw ParseException::unsupportedClassAnnotation(
- (string) $classMetadata,
- 'AccessorOrder::' . $annotation->order
- );
+ throw ParseException::unsupportedClassAnnotation((string) $classMetadata, 'AccessorOrder::'.$annotation->order);
}
- // usort is not stable for the same result. we want to preserve order of
- // the fields that are not explicitly mentioned
+ // usort is not stable for the same result. we want to preserve order of the fields that are not explicitly mentioned
$order = [];
- $init = count($annotation->custom);
+ $init = \count($annotation->custom);
foreach ($classMetadata->getPropertyCollections() as $property) {
$position = $property->getPosition($annotation->custom);
if (null === $position) {
@@ -208,21 +158,17 @@ class JMSParser implements ModelParserInterface
$order[$property->getSerializedName()] = $position;
}
- $classMetadata->sortProperties(static function (
- PropertyCollection $propA,
- PropertyCollection $propB
- ) use ($order): int {
+ $classMetadata->sortProperties(static function (PropertyCollection $propA, PropertyCollection $propB) use ($order): int {
return $order[$propA->getSerializedName()] <=> $order[$propB->getSerializedName()];
});
break;
+
case $annotation instanceof ExclusionPolicy:
if (ExclusionPolicy::NONE !== $annotation->policy) {
- throw ParseException::unsupportedClassAnnotation(
- (string) $classMetadata,
- 'ExclusionPolicy::' . $annotation->policy
- );
+ throw ParseException::unsupportedClassAnnotation((string) $classMetadata, 'ExclusionPolicy::'.$annotation->policy);
}
break;
+
default:
if (
0 === strncmp(
@@ -232,10 +178,7 @@ class JMSParser implements ModelParserInterface
)
) {
// if there are annotations we can safely ignore, we need to explicitly ignore them
- throw ParseException::unsupportedClassAnnotation(
- (string) $classMetadata,
- get_class($annotation)
- );
+ throw ParseException::unsupportedClassAnnotation((string) $classMetadata, \get_class($annotation));
}
}
}
@@ -244,51 +187,36 @@ class JMSParser implements ModelParserInterface
/**
* Find the annotations we care about by looking through all ancestors of $reflectionClass.
*
- * @param \ReflectionClass $reflectionClass
- *
* @return object[] Hashmap of annotation class => annotation object
- * @throws \Doctrine\Common\Annotations\AnnotationException
*
- * @phpstan-ignore-next-line
+ * @throws AnnotationException
*/
- private function gatherClassAnnotations(ReflectionClass $reflectionClass): array
+ private function gatherClassAnnotations(\ReflectionClass $reflectionClass): array
{
$map = [];
-
if ($parent = $reflectionClass->getParentClass()) {
$map = $this->gatherClassAnnotations($parent);
}
-
$annotations = $this->annotationsReader->getClassAnnotations($reflectionClass);
-
foreach ($annotations as $annotation) {
- $map[get_class($annotation)] = $annotation;
+ $map[\get_class($annotation)] = $annotation;
}
return $map;
}
- /**
- * @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata
- * @param \Liip\MetadataParser\ModelParser\RawMetadata\PropertyVariationMetadata $property
- * @param array $annotations
- */
- private function parsePropertyAnnotations(
- RawClassMetadata $classMetadata,
- PropertyVariationMetadata $property,
- array $annotations
- ): void {
+ private function parsePropertyAnnotations(RawClassMetadata $classMetadata, PropertyVariationMetadata $property, array $annotations): void
+ {
foreach ($annotations as $annotation) {
switch (true) {
case $annotation instanceof Type:
+ if (null === $annotation->name) {
+ throw ParseException::propertyTypeNameNull((string) $classMetadata, (string) $property);
+ }
try {
$type = $this->jmsTypeParser->parse($annotation->name);
- } catch (InvalidTypeException $exception) {
- throw ParseException::propertyTypeError(
- (string) $classMetadata,
- (string) $property,
- $exception
- );
+ } catch (InvalidTypeException $e) {
+ throw ParseException::propertyTypeError((string) $classMetadata, (string) $property, $e);
}
if ($property->getType() instanceof PropertyTypeUnknown) {
@@ -296,52 +224,49 @@ class JMSParser implements ModelParserInterface
} else {
try {
$property->setType($property->getType()->merge($type));
- } catch (UnexpectedValueException $exception) {
- throw ParseException::propertyTypeConflict(
- (string) $classMetadata,
- (string) $property,
- (string) $property->getType(),
- (string) $type,
- $exception
- );
+ } catch (\UnexpectedValueException $e) {
+ throw ParseException::propertyTypeConflict((string) $classMetadata, (string) $property, (string) $property->getType(), (string) $type, $e);
}
}
break;
+
case $annotation instanceof Exclude:
if (null !== $annotation->if) {
- throw ParseException::unsupportedPropertyAnnotation(
- (string) $classMetadata,
- (string) $property,
- 'Exclude::if'
- );
+ throw ParseException::unsupportedPropertyAnnotation((string) $classMetadata, (string) $property, 'Exclude::if');
}
$classMetadata->removePropertyVariation((string) $property);
break;
+
case $annotation instanceof Groups:
$property->setGroups($annotation->groups);
break;
+
case $annotation instanceof Accessor:
$property->setAccessor(new PropertyAccessor($annotation->getter, $annotation->setter));
break;
+
case $annotation instanceof Since:
$property->setVersionRange($property->getVersionRange()->withSince($annotation->version));
break;
+
case $annotation instanceof Until:
$property->setVersionRange($property->getVersionRange()->withUntil($annotation->version));
break;
- case $annotation instanceof SerializedName:
- // we handle this separately
+
+ case $annotation instanceof MaxDepth:
+ $property->setMaxDepth($annotation->depth);
+ break;
+
case $annotation instanceof VirtualProperty:
// we handle this separately
+ case $annotation instanceof SerializedName:
+ // we handle this separately
break;
+
default:
- if (0 === strncmp('JMS\Serializer\\', get_class($annotation), mb_strlen('JMS\Serializer\\'))) {
+ if (0 === strncmp('JMS\Serializer\\', \get_class($annotation), mb_strlen('JMS\Serializer\\'))) {
// if there are annotations we can safely ignore, we need to explicitly ignore them
- throw ParseException::unsupportedPropertyAnnotation(
- (string) $classMetadata,
- (string) $property,
- get_class($annotation)
- );
+ throw ParseException::unsupportedPropertyAnnotation((string) $classMetadata, (string) $property, \get_class($annotation));
}
break;
}
@@ -352,96 +277,38 @@ class JMSParser implements ModelParserInterface
* Returns the property metadata for the specified property.
*
* If the property already exists on the class metadata this is returned.
- * If the property has a serialized name that overrides the name of an existing property,
- * it will be renamed and merged.
- *
- * @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata
- * @param \ReflectionProperty $refProperty
- * @param array $annotations
- *
- * @return \Liip\MetadataParser\ModelParser\RawMetadata\PropertyVariationMetadata
- * @throws \ReflectionException
+ * If the property has a serialized name that overrides the name of an existing property, it will be renamed and merged.
*/
- private function getProperty(
- RawClassMetadata $classMetadata,
- ReflectionProperty $refProperty,
- array $annotations
- ): PropertyVariationMetadata {
- $defaultName = PropertyCollection::serializedName($refProperty->getName());
+ private function getProperty(RawClassMetadata $classMetadata, \ReflectionProperty $reflProperty, array $annotations): PropertyVariationMetadata
+ {
+ $defaultName = PropertyCollection::serializedName($reflProperty->getName());
$name = $this->getSerializedName($annotations) ?: $defaultName;
-
- if ($classMetadata->hasPropertyVariation($refProperty->getName())) {
- $property = $classMetadata->getPropertyVariation($refProperty->getName());
-
+ if ($classMetadata->hasPropertyVariation($reflProperty->getName())) {
+ $property = $classMetadata->getPropertyVariation($reflProperty->getName());
if ($defaultName !== $name && $classMetadata->hasPropertyCollection($defaultName)) {
- $classMetadata->removePropertyVariation($defaultName);
- $this->addPropertyVariation($defaultName, $name, $property, $classMetadata);
+ $classMetadata->renameProperty($defaultName, $name);
}
} else {
- $property = PropertyVariationMetadata::fromReflection($refProperty);
- $this->addPropertyVariation($defaultName, $name, $property, $classMetadata);
+ $property = PropertyVariationMetadata::fromReflection($reflProperty);
+ $classMetadata->addPropertyVariation($name, $property);
}
return $property;
}
- /**
- * This workaround helps to avoid unnecessary camelCase to snake_case conversion while
- * using default property metadata classes. This allows us to produce code we expect
- * without rewriting the whole metadata parsing library.
- *
- * @param string $defaultName
- * @param string $name
- * @param \Liip\MetadataParser\ModelParser\RawMetadata\PropertyVariationMetadata $property
- * @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata
- *
- * @throws \ReflectionException
- */
- private function addPropertyVariation(
- string $defaultName,
- string $name,
- PropertyVariationMetadata $property,
- RawClassMetadata $classMetadata
- ): void {
- if ($classMetadata->hasPropertyCollection($defaultName)) {
- $prop = $classMetadata->getPropertyCollection($defaultName);
- } else {
- $prop = new PropertyCollection($name);
- $classMetadata->addPropertyCollection($prop);
- }
-
- $propName = new ReflectionProperty(get_class($prop), 'serializedName');
- $propName->setAccessible(true);
- $propName->setValue($prop, $name);
-
- $prop->addVariation($property);
- }
-
- /**
- * @param \Liip\MetadataParser\ModelParser\RawMetadata\PropertyVariationMetadata $property
- * @param \ReflectionMethod $refMethod
- * @param \ReflectionClass $refClass
- *
- * @return \Liip\MetadataParser\Metadata\PropertyType
- *
- * @phpstan-ignore-next-line
- */
- private function getReturnType(
- PropertyVariationMetadata $property,
- ReflectionMethod $refMethod,
- ReflectionClass $refClass
- ): PropertyType {
+ private function getReturnType(PropertyVariationMetadata $property, \ReflectionMethod $reflMethod, \ReflectionClass $reflClass): PropertyType
+ {
$type = new PropertyTypeUnknown(true);
- $refType = $refMethod->getReturnType();
- if (null !== $refType) {
- $type = $this->phpTypeParser->parseReflectionType($refType);
+ $reflType = $reflMethod->getReturnType();
+ if (null !== $reflType) {
+ $type = $this->phpTypeParser->parseReflectionType($reflType);
}
try {
- $docBlockType = $this->getReturnTypeOfMethod($refMethod, $refClass);
- } catch (InvalidTypeException $exception) {
- throw ParseException::propertyTypeError($refClass->getName(), (string) $property, $exception);
+ $docBlockType = $this->getReturnTypeOfMethod($reflMethod);
+ } catch (InvalidTypeException $e) {
+ throw ParseException::propertyTypeError($reflClass->getName(), (string) $property, $e);
}
if (null === $docBlockType) {
@@ -450,47 +317,27 @@ class JMSParser implements ModelParserInterface
try {
return $type->merge($docBlockType);
- } catch (UnexpectedValueException $exception) {
- throw ParseException::propertyTypeConflict(
- $refClass->getName(),
- (string) $property,
- (string) $type,
- (string) $docBlockType,
- $exception
- );
+ } catch (\UnexpectedValueException $e) {
+ throw ParseException::propertyTypeConflict($reflClass->getName(), (string) $property, (string) $type, (string) $docBlockType, $e);
}
}
- /**
- * @param \ReflectionMethod $refMethod
- * @param \ReflectionClass $refClass
- *
- * @return \Liip\MetadataParser\Metadata\PropertyType|null
- *
- * @phpstan-ignore-next-line
- */
- private function getReturnTypeOfMethod(ReflectionMethod $refMethod, ReflectionClass $refClass): ?PropertyType
+ private function getReturnTypeOfMethod(\ReflectionMethod $reflMethod): ?PropertyType
{
- $docComment = $refMethod->getDocComment();
-
+ $docComment = $reflMethod->getDocComment();
if (false === $docComment) {
return null;
}
foreach (explode("\n", $docComment) as $line) {
if (1 === preg_match('/@return ([^ ]+)/', $line, $matches)) {
- return $this->phpTypeParser->parseAnnotationType($matches[1], $refClass);
+ return $this->phpTypeParser->parseAnnotationType($matches[1], $reflMethod->getDeclaringClass());
}
}
return null;
}
- /**
- * @param array $annotations
- *
- * @return string|null
- */
private function getSerializedName(array $annotations): ?string
{
foreach ($annotations as $annotation) {
@@ -502,11 +349,6 @@ class JMSParser implements ModelParserInterface
return null;
}
- /**
- * @param array $annotations
- *
- * @return bool
- */
private function isVirtualProperty(array $annotations): bool
{
foreach ($annotations as $annotation) {
@@ -518,11 +360,6 @@ class JMSParser implements ModelParserInterface
return false;
}
- /**
- * @param array $annotations
- *
- * @return bool
- */
private function isPostDeserializeMethod(array $annotations): bool
{
foreach ($annotations as $annotation) {
@@ -534,16 +371,9 @@ class JMSParser implements ModelParserInterface
return false;
}
- /**
- * @param array $annotations
- * @param \ReflectionMethod $refMethod
- *
- * @return string
- */
- private function getMethodName(array $annotations, ReflectionMethod $refMethod): string
+ private function getMethodName(array $annotations, \ReflectionMethod $reflMethod): string
{
- $name = $refMethod->getName();
-
+ $name = $reflMethod->getName();
foreach ($annotations as $annotation) {
if ($annotation instanceof VirtualProperty && null !== $annotation->name) {
$name = $annotation->name;
diff --git a/src/Component/Serializer/Parser/JMSTypeParser.php b/src/Component/Serializer/Parser/JMSTypeParser.php
index ecb86f9..1b8681b 100644
--- a/src/Component/Serializer/Parser/JMSTypeParser.php
+++ b/src/Component/Serializer/Parser/JMSTypeParser.php
@@ -1,59 +1,42 @@
- * @author Pavel Kovalenko
- * @see https://github.com/liip/metadata-parser
- * @internal
- *
- * @SuppressWarnings(PHPMD)
- */
-class JMSTypeParser
+final class JMSTypeParser
{
private const TYPE_ARRAY = 'array';
+ private const TYPE_ARRAY_COLLECTION = 'ArrayCollection';
+ private const TYPE_DATETIME_INTERFACE = 'DateTimeInterface';
- /**
- * @var \RetailCrm\Api\Component\Serializer\Parser\BaseJMSParser
- */
+ /** @var \RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type\Parser */
private $jmsTypeParser;
- /**
- * JMSTypeParser constructor.
- */
+ private $useArrayDateFormat;
+
public function __construct()
{
- $this->jmsTypeParser = new BaseJMSParser();
+ $this->jmsTypeParser = new Parser();
+ $this->useArrayDateFormat = null === (new \ReflectionClass(DateTimeOptions::class))
+ ->getConstructor()->getParameters()[2]->getType();
}
- /**
- * @param string $rawType
- *
- * @return \Liip\MetadataParser\Metadata\PropertyType
- */
public function parse(string $rawType): PropertyType
{
if ('' === $rawType) {
@@ -63,12 +46,6 @@ class JMSTypeParser
return $this->parseType($this->jmsTypeParser->parse($rawType));
}
- /**
- * @param array $typeInfo
- * @param bool $isSubType
- *
- * @return \Liip\MetadataParser\Metadata\PropertyType
- */
private function parseType(array $typeInfo, bool $isSubType = false): PropertyType
{
$typeInfo = array_merge(
@@ -84,13 +61,12 @@ class JMSTypeParser
if (0 === \count($typeInfo['params'])) {
if (self::TYPE_ARRAY === $typeInfo['name']) {
- return new PropertyTypeArray(new PropertyTypeUnknown(false), false, $nullable);
+ return self::iterableType(new PropertyTypeUnknown(false), false, $nullable);
}
if (PropertyTypePrimitive::isTypePrimitive($typeInfo['name'])) {
return new PropertyTypePrimitive($typeInfo['name'], $nullable);
}
-
if (PropertyTypeDateTime::isTypeDateTime($typeInfo['name'])) {
return PropertyTypeDateTime::fromDateTimeClass($typeInfo['name'], $nullable);
}
@@ -102,41 +78,62 @@ class JMSTypeParser
return new PropertyTypeClass($typeInfo['name'], $nullable);
}
- if (self::TYPE_ARRAY === $typeInfo['name']) {
+ $collectionClass = $this->getCollectionClass($typeInfo['name']);
+ if (self::TYPE_ARRAY === $typeInfo['name'] || $collectionClass) {
if (1 === \count($typeInfo['params'])) {
- return new PropertyTypeArray(
- $this->parseType($typeInfo['params'][0], true),
- false,
- $nullable
- );
+ return self::iterableType($this->parseType($typeInfo['params'][0], true), false, $nullable, $collectionClass);
}
if (2 === \count($typeInfo['params'])) {
- return new PropertyTypeArray(
- $this->parseType($typeInfo['params'][1], true),
- true,
- $nullable
- );
+ return self::iterableType($this->parseType($typeInfo['params'][1], true), true, $nullable, $collectionClass);
}
- throw new InvalidTypeException(sprintf(
- 'JMS property type array can\'t have more than 2 parameters (%s)',
- var_export($typeInfo, true)
- ));
+ throw new InvalidTypeException(sprintf('JMS property type array can\'t have more than 2 parameters (%s)', var_export($typeInfo, true)));
}
- if (PropertyTypeDateTime::isTypeDateTime($typeInfo['name'])) {
+ if (PropertyTypeDateTime::isTypeDateTime($typeInfo['name']) || (self::TYPE_DATETIME_INTERFACE === $typeInfo['name'])) {
// the case of datetime without params is already handled above, we know we have params
+ $serializeFormat = $typeInfo['params'][0] ?: null;
+ // {@link \JMS\Serializer\Handler\DateHandler} of jms/serializer defaults to using the serialization format as a deserialization format if none was supplied...
+ $deserializeFormats = ($typeInfo['params'][2] ?? null) ?: $serializeFormat;
+ // ... and always converts single strings to arrays
+ $deserializeFormats = \is_string($deserializeFormats) ? [$deserializeFormats] : $deserializeFormats;
+ // Jms defaults to DateTime when given DateTimeInterface despite the documentation saying DateTimeImmutable, {@see \JMS\Serializer\Handler\DateHandler} in jms/serializer
+ $className = (self::TYPE_DATETIME_INTERFACE === $typeInfo['name']) ? \DateTime::class : $typeInfo['name'];
+
return PropertyTypeDateTime::fromDateTimeClass(
- $typeInfo['name'],
+ $className,
$nullable,
new DateTimeOptions(
- $typeInfo['params'][0] ?: null,
+ $serializeFormat,
($typeInfo['params'][1] ?? null) ?: null,
- ($typeInfo['params'][2] ?? null) ?: null
+ $this->useArrayDateFormat ? $deserializeFormats : $deserializeFormats[0],
)
);
}
throw new InvalidTypeException(sprintf('Unknown JMS property found (%s)', var_export($typeInfo, true)));
}
+
+ private function getCollectionClass(string $name): ?string
+ {
+ switch ($name) {
+ case self::TYPE_ARRAY_COLLECTION:
+ return ArrayCollection::class;
+ default:
+ return is_a($name, Collection::class, true) ? $name : null;
+ }
+ }
+
+ private static function iterableType(
+ PropertyType $subType,
+ bool $hashmap,
+ bool $nullable,
+ ?string $collectionClass = null
+ ): AbstractPropertyType {
+ if (class_exists('Liip\MetadataParser\Metadata\PropertyTypeIterable')) {
+ return new PropertyTypeIterable($subType, $hashmap, $nullable, $collectionClass);
+ }
+
+ return new PropertyTypeArray($subType, $hashmap, $nullable, $collectionClass !== null);
+ }
}
diff --git a/src/Model/Entity/References/Currency.php b/src/Model/Entity/References/Currency.php
index 96abbe2..596852e 100644
--- a/src/Model/Entity/References/Currency.php
+++ b/src/Model/Entity/References/Currency.php
@@ -16,6 +16,8 @@ use RetailCrm\Api\Component\Serializer\Annotation as JMS;
*
* @category Currency
* @package RetailCrm\Api\Model\Entity\References
+ *
+ * @SuppressWarnings(PHPMD.LongVariable)
*/
class Currency
{
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index 771a104..e2fe6ef 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -18,7 +18,10 @@ if (!is_file($autoloadFile = __DIR__ . '/../vendor/autoload.php')) {
$loader = require $autoloadFile;
$loader->add('RetailCrm\\TestUtils', __DIR__ . '/tests/utils');
$loader->add('RetailCrm\\Tests', __DIR__ . '/src');
-AnnotationRegistry::registerLoader('class_exists');
+
+if (method_exists(AnnotationRegistry::class, 'registerLoader')) {
+ AnnotationRegistry::registerLoader('class_exists');
+}
if (file_exists(__DIR__ . '/../.env')) {
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');