Compatibility with newer Symfony skeleton versions (with Doctrine 3.x)
This commit is contained in:
commit
fda9208de2
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
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:
|
steps:
|
||||||
- name: Check out code into the workspace
|
- name: Check out code into the workspace
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.3.0",
|
"php": ">=7.3",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"psr/log": "^1|^2|^3",
|
"psr/log": "^1|^2|^3",
|
||||||
"psr/http-client": "^1.0",
|
"psr/http-client": "^1.0",
|
||||||
@ -26,9 +26,9 @@
|
|||||||
"php-http/message-factory": "^1.0",
|
"php-http/message-factory": "^1.0",
|
||||||
"php-http/discovery": "^1.13",
|
"php-http/discovery": "^1.13",
|
||||||
"doctrine/annotations": "^1.13|^2.0",
|
"doctrine/annotations": "^1.13|^2.0",
|
||||||
"liip/serializer": "2.2.*",
|
"liip/serializer": "2.2.* || 2.6.*",
|
||||||
"php-http/httplug": "^2.2",
|
"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",
|
"symfony/console": "^4.0|^5.0|^6.0",
|
||||||
"psr/event-dispatcher": "^1.0",
|
"psr/event-dispatcher": "^1.0",
|
||||||
"neur0toxine/psr.http-client-implementation.php-http-curl": "*",
|
"neur0toxine/psr.http-client-implementation.php-http-curl": "*",
|
||||||
@ -67,7 +67,7 @@
|
|||||||
"phpunit": "./vendor/bin/phpunit -c phpunit.xml.dist --coverage-text",
|
"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",
|
"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",
|
"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": "./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",
|
"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",
|
"lint:fix": "./vendor/bin/phpcbf src",
|
||||||
|
@ -10,4 +10,8 @@
|
|||||||
|
|
||||||
<file>src/</file>
|
<file>src/</file>
|
||||||
<file>tests/</file>
|
<file>tests/</file>
|
||||||
|
|
||||||
|
<exclude-pattern>src/Component/Serializer/Generator/*</exclude-pattern>
|
||||||
|
<exclude-pattern>src/Component/Serializer/Parser/*</exclude-pattern>
|
||||||
|
<exclude-pattern>src/Component/Serializer/ArraySupportDecorator.php</exclude-pattern>
|
||||||
</ruleset>
|
</ruleset>
|
||||||
|
@ -44,4 +44,6 @@
|
|||||||
</rule>
|
</rule>
|
||||||
|
|
||||||
<exclude-pattern>tests/*</exclude-pattern>
|
<exclude-pattern>tests/*</exclude-pattern>
|
||||||
|
<exclude-pattern>src/Component/Serializer/Generator/*</exclude-pattern>
|
||||||
|
<exclude-pattern>src/Component/Serializer/Parser/*</exclude-pattern>
|
||||||
</ruleset>
|
</ruleset>
|
||||||
|
246
phpstan-baseline-serializer.neon
Normal file
246
phpstan-baseline-serializer.neon
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
parameters:
|
||||||
|
ignoreErrors:
|
||||||
|
-
|
||||||
|
message: "#^Parameter \\#1 \\$config of static method Liip\\\\Serializer\\\\Configuration\\\\GeneratorConfiguration\\:\\:createFomArray\\(\\) expects array\\{default_group_combinations\\?\\: array\\<int, array\\<int, string\\>\\>\\|null, default_versions\\?\\: array\\<int, string\\>\\|null, classes\\?\\: array\\<class\\-string, array\\<string, mixed\\>\\>\\|null, options\\?\\: array\\<string, mixed\\>\\}, array\\{default_group_combinations\\: array\\{\\}, default_versions\\: array\\{\\}, classes\\: non\\-empty\\-array\\<string, array\\{\\}\\>\\} given\\.$#"
|
||||||
|
count: 1
|
||||||
|
path: src/Component/ModelsGenerator.php
|
||||||
|
|
||||||
|
-
|
||||||
|
message: "#^Parameter \\#3 \\$classesToGenerate of class RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Generator\\\\DeserializerGenerator constructor expects array\\<int, class\\-string\\>, array\\<string\\> 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\\<string, int\\<1, max\\>\\>, 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\\<int, string\\>, 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\\>\\|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\\<Traversable\\>\\|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\\<min, 0\\>\\|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\\<T of int\\|string,V of int\\|string\\>\\:\\:\\$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
|
@ -1,4 +1,6 @@
|
|||||||
parameters:
|
parameters:
|
||||||
|
excludePaths:
|
||||||
|
- src/Component/Serializer/ArraySupportDecorator.php
|
||||||
ignoreErrors:
|
ignoreErrors:
|
||||||
-
|
-
|
||||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Builder\\\\ClientBuilder\\:\\:buildHandlersChain\\(\\) through static\\:\\:\\.$#"
|
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Builder\\\\ClientBuilder\\:\\:buildHandlersChain\\(\\) through static\\:\\:\\.$#"
|
||||||
@ -210,36 +212,6 @@ parameters:
|
|||||||
count: 2
|
count: 2
|
||||||
path: src/Component/Serializer/ArraySupportDecorator.php
|
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\\<string, string\\> but returns mixed\\.$#"
|
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\ModelsChecksumGenerator\\:\\:getStoredChecksums\\(\\) should return array\\<string, string\\> but returns mixed\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@ -250,41 +222,6 @@ parameters:
|
|||||||
count: 4
|
count: 4
|
||||||
path: src/Component/Serializer/ModelsChecksumGenerator.php
|
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\\.$#"
|
message: "#^Parameter \\#2 \\$string2 of function strncmp expects string, class\\-string\\|false given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@ -295,36 +232,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Component/Serializer/Parser/JMSParser.php
|
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\\:\\:\\.$#"
|
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Component\\\\Transformer\\\\DateTimeTransformer\\:\\:createFromFormat\\(\\) through static\\:\\:\\.$#"
|
||||||
count: 3
|
count: 3
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
includes:
|
includes:
|
||||||
|
- phpstan-baseline-serializer.neon
|
||||||
- phpstan-baseline.neon # TODO: This should be removed eventually.
|
- phpstan-baseline.neon # TODO: This should be removed eventually.
|
||||||
|
|
||||||
parameters:
|
parameters:
|
||||||
|
@ -13,6 +13,7 @@ use RetailCrm\Api\Component\ModelsGenerator;
|
|||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class GenerateModelsCommand
|
* Class GenerateModelsCommand
|
||||||
@ -82,7 +83,15 @@ class GenerateModelsCommand extends AbstractModelsProcessorCommand
|
|||||||
$output->writeln('');
|
$output->writeln('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
$generator->generate();
|
$generator->generate();
|
||||||
|
} catch (\Throwable $throwable) {
|
||||||
|
$styled = new SymfonyStyle($input, $output);
|
||||||
|
$styled->error($throwable->getMessage());
|
||||||
|
$styled->writeln($throwable->getTraceAsString());
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
$output->writeln(sprintf(
|
$output->writeln(sprintf(
|
||||||
'<fg=black;bg=green> ✓ Done, generated code for %d models.</>',
|
'<fg=black;bg=green> ✓ Done, generated code for %d models.</>',
|
||||||
|
@ -11,12 +11,12 @@ namespace RetailCrm\Api\Component;
|
|||||||
|
|
||||||
use Doctrine\Common\Annotations\AnnotationReader;
|
use Doctrine\Common\Annotations\AnnotationReader;
|
||||||
use Liip\MetadataParser\Builder;
|
use Liip\MetadataParser\Builder;
|
||||||
|
use Liip\MetadataParser\ModelParser\RawMetadata\PropertyCollection;
|
||||||
use Liip\MetadataParser\Parser;
|
use Liip\MetadataParser\Parser;
|
||||||
use Liip\MetadataParser\RecursionChecker;
|
use Liip\MetadataParser\RecursionChecker;
|
||||||
use Liip\Serializer\Configuration\GeneratorConfiguration;
|
use Liip\Serializer\Configuration\GeneratorConfiguration;
|
||||||
use Liip\Serializer\Template\Deserialization;
|
use Liip\Serializer\Template\Deserialization;
|
||||||
use Liip\Serializer\Template\Serialization;
|
use Liip\Serializer\Template\Serialization;
|
||||||
use RetailCrm\Api\Component\Utils;
|
|
||||||
use RetailCrm\Api\Component\Serializer\Generator\DeserializerGenerator;
|
use RetailCrm\Api\Component\Serializer\Generator\DeserializerGenerator;
|
||||||
use RetailCrm\Api\Component\Serializer\Generator\SerializerGenerator;
|
use RetailCrm\Api\Component\Serializer\Generator\SerializerGenerator;
|
||||||
use RetailCrm\Api\Component\Serializer\ModelsChecksumGenerator;
|
use RetailCrm\Api\Component\Serializer\ModelsChecksumGenerator;
|
||||||
@ -177,6 +177,7 @@ class ModelsGenerator
|
|||||||
$configurationArray['classes'][$class] = [];
|
$configurationArray['classes'][$class] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PropertyCollection::useIdenticalNamingStrategy();
|
||||||
$configuration = GeneratorConfiguration::createFomArray($configurationArray);
|
$configuration = GeneratorConfiguration::createFomArray($configurationArray);
|
||||||
$parsers = [new JMSParser(new AnnotationReader())];
|
$parsers = [new JMSParser(new AnnotationReader())];
|
||||||
$builder = new Builder(new Parser($parsers), new RecursionChecker(null, []));
|
$builder = new Builder(new Parser($parsers), new RecursionChecker(null, []));
|
||||||
|
@ -16,6 +16,217 @@ use Liip\Serializer\Exception\UnsupportedFormatException;
|
|||||||
use Liip\Serializer\SerializerInterface;
|
use Liip\Serializer\SerializerInterface;
|
||||||
use Pnz\JsonException\Json;
|
use Pnz\JsonException\Json;
|
||||||
|
|
||||||
|
if (PHP_VERSION_ID >= 80000) {
|
||||||
|
/**
|
||||||
|
* 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): mixed
|
||||||
|
{
|
||||||
|
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<int|string, mixed>
|
||||||
|
*/
|
||||||
|
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<int|string, mixed> $data
|
||||||
|
*
|
||||||
|
* @return array<int|string, mixed>|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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes array of arrays to array of objects.
|
||||||
|
*
|
||||||
|
* @param mixed[] $data
|
||||||
|
* @param string $type
|
||||||
|
* @param \Liip\Serializer\Context|null $context
|
||||||
|
*
|
||||||
|
* @return array<int|string, mixed>
|
||||||
|
* @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<string|int, Class\Name>' or 'array<string>'.
|
||||||
|
*
|
||||||
|
* @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
|
* Class ArraySupportDecorator
|
||||||
*
|
*
|
||||||
@ -225,3 +436,4 @@ class ArraySupportDecorator implements SerializerInterface
|
|||||||
return 'mixed';
|
return 'mixed';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
declare(strict_types=1);
|
||||||
* PHP version 7.3
|
|
||||||
*
|
|
||||||
* @category DeserializerGenerator
|
|
||||||
* @package RetailCrm\Api\Component\Serializer\Generator
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace RetailCrm\Api\Component\Serializer\Generator;
|
namespace RetailCrm\Api\Component\Serializer\Generator;
|
||||||
|
|
||||||
@ -18,6 +13,8 @@ use Liip\MetadataParser\Metadata\PropertyTypeDateTime;
|
|||||||
use Liip\MetadataParser\Metadata\PropertyTypePrimitive;
|
use Liip\MetadataParser\Metadata\PropertyTypePrimitive;
|
||||||
use Liip\MetadataParser\Metadata\PropertyTypeUnknown;
|
use Liip\MetadataParser\Metadata\PropertyTypeUnknown;
|
||||||
use Liip\MetadataParser\Reducer\TakeBestReducer;
|
use Liip\MetadataParser\Reducer\TakeBestReducer;
|
||||||
|
use Liip\Serializer\Configuration\ClassToGenerate;
|
||||||
|
use Liip\Serializer\Configuration\GeneratorConfiguration;
|
||||||
use Liip\Serializer\Path\ArrayPath;
|
use Liip\Serializer\Path\ArrayPath;
|
||||||
use Liip\Serializer\Path\ModelPath;
|
use Liip\Serializer\Path\ModelPath;
|
||||||
use Liip\Serializer\Template\Deserialization;
|
use Liip\Serializer\Template\Deserialization;
|
||||||
@ -26,129 +23,75 @@ use RetailCrm\Api\Component\Serializer\Type\PropertyTypeMixed;
|
|||||||
use RetailCrm\Api\Interfaces\Orders\CustomerInterface;
|
use RetailCrm\Api\Interfaces\Orders\CustomerInterface;
|
||||||
use RetailCrm\Api\Model\Entity\Customers\Customer;
|
use RetailCrm\Api\Model\Entity\Customers\Customer;
|
||||||
use RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerCorporate;
|
use RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerCorporate;
|
||||||
use RuntimeException;
|
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
|
||||||
/**
|
final class DeserializerGenerator
|
||||||
* Class DeserializerGenerator
|
|
||||||
*
|
|
||||||
* @category DeserializerGenerator
|
|
||||||
* @package RetailCrm\Api\Component\Serializer\Generator
|
|
||||||
* @license https://github.com/liip/serializer/blob/master/LICENSE MIT License
|
|
||||||
* @author Liip <https://github.com/liip>
|
|
||||||
* @author Pavel Kovalenko
|
|
||||||
* @see https://github.com/liip/serializer
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @SuppressWarnings(PHPMD)
|
|
||||||
*/
|
|
||||||
class DeserializerGenerator
|
|
||||||
{
|
{
|
||||||
private const FILENAME_PREFIX = 'deserialize';
|
private const FILENAME_PREFIX = 'deserialize';
|
||||||
|
|
||||||
/**
|
/** @var \Symfony\Component\Filesystem\Filesystem */
|
||||||
* @var Deserialization
|
|
||||||
*/
|
|
||||||
private $templating;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \RetailCrm\Api\Component\Serializer\Template\CustomDeserialization
|
|
||||||
*/
|
|
||||||
private $customTemplating;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Filesystem
|
|
||||||
*/
|
|
||||||
private $filesystem;
|
private $filesystem;
|
||||||
|
|
||||||
/**
|
/** @var \Liip\Serializer\Configuration\GeneratorConfiguration */
|
||||||
* @var Builder
|
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;
|
private $metadataBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a list of fqn classnames
|
* @param list<class-string> $classesToGenerate This is a list of FQCN 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
|
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
Deserialization $templating,
|
Deserialization $templating,
|
||||||
CustomDeserialization $customTemplating,
|
CustomDeserialization $customTemplating,
|
||||||
array $classesToGenerate,
|
array $classesToGenerate,
|
||||||
string $cacheDirectory
|
string $cacheDirectory,
|
||||||
|
GeneratorConfiguration $configuration = null
|
||||||
) {
|
) {
|
||||||
|
$this->cacheDirectory = $cacheDirectory;
|
||||||
$this->templating = $templating;
|
$this->templating = $templating;
|
||||||
$this->customTemplating = $customTemplating;
|
$this->customTemplating = $customTemplating;
|
||||||
$this->classesToGenerate = $classesToGenerate;
|
|
||||||
$this->cacheDirectory = $cacheDirectory;
|
|
||||||
$this->filesystem = new Filesystem();
|
$this->filesystem = new Filesystem();
|
||||||
|
$this->configuration = $this->createGeneratorConfiguration($configuration, $classesToGenerate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $className
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public static function buildDeserializerFunctionName(string $className): 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
|
public function generate(Builder $metadataBuilder): void
|
||||||
{
|
{
|
||||||
$this->metadataBuilder = $metadataBuilder;
|
$this->metadataBuilder = $metadataBuilder;
|
||||||
|
|
||||||
$this->filesystem->mkdir($this->cacheDirectory);
|
$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
|
// 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
|
// otherwise we end up with generated property names for accessor methods
|
||||||
$classMetadata = $metadataBuilder->build($className, [
|
$classMetadata = $metadataBuilder->build($classToGenerate->getClassName(), [
|
||||||
new TakeBestReducer(),
|
new TakeBestReducer(),
|
||||||
]);
|
]);
|
||||||
$this->writeFile($classMetadata);
|
$this->writeFile($classMetadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param \Liip\MetadataParser\Metadata\ClassMetadata $classMetadata
|
|
||||||
*
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
private function writeFile(ClassMetadata $classMetadata): void
|
private function writeFile(ClassMetadata $classMetadata): void
|
||||||
{
|
{
|
||||||
if (count($classMetadata->getConstructorParameters())) {
|
if (\count($classMetadata->getConstructorParameters())) {
|
||||||
throw new RuntimeException(sprintf(
|
throw new \Exception(sprintf('We currently do not support deserializing when the root class has a non-empty constructor. Class %s', $classMetadata->getClassName()));
|
||||||
'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');
|
$arrayPath = new ArrayPath('jsonData');
|
||||||
|
|
||||||
$code = $this->templating->renderFunction(
|
$code = $this->templating->renderFunction(
|
||||||
@ -162,13 +105,7 @@ class DeserializerGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Liip\MetadataParser\Metadata\ClassMetadata $classMetadata
|
* @param array<string, positive-int> $stack
|
||||||
* @param \Liip\Serializer\Path\ArrayPath $arrayPath
|
|
||||||
* @param \Liip\Serializer\Path\ModelPath $modelPath
|
|
||||||
* @param mixed[] $stack
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
*/
|
||||||
private function generateCodeForClass(
|
private function generateCodeForClass(
|
||||||
ClassMetadata $classMetadata,
|
ClassMetadata $classMetadata,
|
||||||
@ -179,9 +116,9 @@ class DeserializerGenerator
|
|||||||
$stack[$classMetadata->getClassName()] = ($stack[$classMetadata->getClassName()] ?? 0) + 1;
|
$stack[$classMetadata->getClassName()] = ($stack[$classMetadata->getClassName()] ?? 0) + 1;
|
||||||
|
|
||||||
$constructorArgumentNames = [];
|
$constructorArgumentNames = [];
|
||||||
|
$overwrittenNames = [];
|
||||||
$initCode = '';
|
$initCode = '';
|
||||||
$code = '';
|
$code = '';
|
||||||
|
|
||||||
foreach ($classMetadata->getProperties() as $propertyMetadata) {
|
foreach ($classMetadata->getProperties() as $propertyMetadata) {
|
||||||
$propertyArrayPath = $arrayPath->withFieldName($propertyMetadata->getSerializedName());
|
$propertyArrayPath = $arrayPath->withFieldName($propertyMetadata->getSerializedName());
|
||||||
|
|
||||||
@ -189,6 +126,9 @@ class DeserializerGenerator
|
|||||||
$argument = $classMetadata->getConstructorParameter($propertyMetadata->getName());
|
$argument = $classMetadata->getConstructorParameter($propertyMetadata->getName());
|
||||||
$default = var_export($argument->isRequired() ? null : $argument->getDefaultValue(), true);
|
$default = var_export($argument->isRequired() ? null : $argument->getDefaultValue(), true);
|
||||||
$tempVariable = ModelPath::tempVariable([(string) $modelPath, $propertyMetadata->getName()]);
|
$tempVariable = ModelPath::tempVariable([(string) $modelPath, $propertyMetadata->getName()]);
|
||||||
|
if (\array_key_exists($propertyMetadata->getName(), $constructorArgumentNames)) {
|
||||||
|
$overwrittenNames[$propertyMetadata->getName()] = true;
|
||||||
|
}
|
||||||
$constructorArgumentNames[$propertyMetadata->getName()] = (string) $tempVariable;
|
$constructorArgumentNames[$propertyMetadata->getName()] = (string) $tempVariable;
|
||||||
|
|
||||||
$initCode .= $this->templating->renderArgument(
|
$initCode .= $this->templating->renderArgument(
|
||||||
@ -206,26 +146,21 @@ class DeserializerGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
$constructorArguments = [];
|
$constructorArguments = [];
|
||||||
|
|
||||||
foreach ($classMetadata->getConstructorParameters() as $definition) {
|
foreach ($classMetadata->getConstructorParameters() as $definition) {
|
||||||
if (array_key_exists($definition->getName(), $constructorArgumentNames)) {
|
if (\array_key_exists($definition->getName(), $constructorArgumentNames)) {
|
||||||
$constructorArguments[] = $constructorArgumentNames[$definition->getName()];
|
$constructorArguments[] = $constructorArgumentNames[$definition->getName()];
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($definition->isRequired()) {
|
if ($definition->isRequired()) {
|
||||||
throw new RuntimeException(sprintf(
|
$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)));
|
||||||
'Unknown constructor argument "%s" in "%s(%s)"',
|
if ($overwrittenNames) {
|
||||||
$definition->getName(),
|
$msg .= sprintf(' Multiple definitions for fields %s seen - the last one overwrites previous ones.', implode(', ', array_keys($overwrittenNames)));
|
||||||
$classMetadata->getClassName(),
|
}
|
||||||
implode(', ', array_keys($constructorArgumentNames))
|
throw new \Exception($msg);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$constructorArguments[] = var_export($definition->getDefaultValue(), true);
|
$constructorArguments[] = var_export($definition->getDefaultValue(), true);
|
||||||
}
|
}
|
||||||
|
if (\count($constructorArgumentNames) > 0) {
|
||||||
if (count($constructorArgumentNames) > 0) {
|
|
||||||
$code .= $this->templating->renderUnset(array_values($constructorArgumentNames));
|
$code .= $this->templating->renderUnset(array_values($constructorArgumentNames));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,13 +168,7 @@ class DeserializerGenerator
|
|||||||
return $this->generateCustomerInterface($classMetadata, $arrayPath, $modelPath, $initCode, $stack);
|
return $this->generateCustomerInterface($classMetadata, $arrayPath, $modelPath, $initCode, $stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->templating->renderClass(
|
return $this->templating->renderClass((string) $modelPath, $classMetadata->getClassName(), $constructorArguments, $code, $initCode);
|
||||||
(string) $modelPath,
|
|
||||||
$classMetadata->getClassName(),
|
|
||||||
$constructorArguments,
|
|
||||||
$code,
|
|
||||||
$initCode
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -282,13 +211,7 @@ class DeserializerGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Liip\MetadataParser\Metadata\PropertyMetadata $propertyMetadata
|
* @param array<string, positive-int> $stack
|
||||||
* @param \Liip\Serializer\Path\ArrayPath $arrayPath
|
|
||||||
* @param \Liip\Serializer\Path\ModelPath $modelPath
|
|
||||||
* @param mixed[] $stack
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
*/
|
||||||
private function generateCodeForProperty(
|
private function generateCodeForProperty(
|
||||||
PropertyMetadata $propertyMetadata,
|
PropertyMetadata $propertyMetadata,
|
||||||
@ -300,16 +223,16 @@ class DeserializerGenerator
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Recursion::hasMaxDepthReached($propertyMetadata, $stack)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
if ($propertyMetadata->getAccessor()->hasSetterMethod()) {
|
if ($propertyMetadata->getAccessor()->hasSetterMethod()) {
|
||||||
$tempVariable = ModelPath::tempVariable([(string) $modelPath, $propertyMetadata->getName()]);
|
$tempVariable = ModelPath::tempVariable([(string) $modelPath, $propertyMetadata->getName()]);
|
||||||
$code = $this->generateCodeForField($propertyMetadata, $arrayPath, $tempVariable, $stack);
|
$code = $this->generateCodeForField($propertyMetadata, $arrayPath, $tempVariable, $stack);
|
||||||
$code .= $this->templating->renderConditional(
|
$code .= $this->templating->renderConditional(
|
||||||
(string) $tempVariable,
|
(string) $tempVariable,
|
||||||
$this->templating->renderSetter(
|
$this->templating->renderSetter((string) $modelPath, $propertyMetadata->getAccessor()->getSetterMethod(), (string) $tempVariable)
|
||||||
(string) $modelPath,
|
|
||||||
(string) $propertyMetadata->getAccessor()->getSetterMethod(),
|
|
||||||
(string) $tempVariable
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
$code .= $this->templating->renderUnset([(string) $tempVariable]);
|
$code .= $this->templating->renderUnset([(string) $tempVariable]);
|
||||||
|
|
||||||
@ -322,13 +245,7 @@ class DeserializerGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Liip\MetadataParser\Metadata\PropertyMetadata $propertyMetadata
|
* @param array<string, positive-int> $stack
|
||||||
* @param \Liip\Serializer\Path\ArrayPath $arrayPath
|
|
||||||
* @param \Liip\Serializer\Path\ModelPath $modelPath
|
|
||||||
* @param mixed[] $stack
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
*/
|
||||||
private function generateCodeForField(
|
private function generateCodeForField(
|
||||||
PropertyMetadata $propertyMetadata,
|
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 array<string, positive-int> $stack
|
||||||
* @param \Liip\Serializer\Path\ArrayPath $arrayPath
|
|
||||||
* @param \Liip\Serializer\Path\ModelPath $modelPropertyPath
|
|
||||||
* @param mixed[] $stack
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
*/
|
||||||
private function generateInnerCodeForFieldType(
|
private function generateInnerCodeForFieldType(
|
||||||
PropertyMetadata $propertyMetadata,
|
PropertyMetadata $propertyMetadata,
|
||||||
@ -359,64 +279,50 @@ class DeserializerGenerator
|
|||||||
): string {
|
): string {
|
||||||
$type = $propertyMetadata->getType();
|
$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) {
|
switch ($type) {
|
||||||
case $type instanceof PropertyTypeDateTime:
|
case $type instanceof PropertyTypeArray:
|
||||||
if (null !== $type->getZone()) {
|
if ($this->isArrayTraversable($type)) {
|
||||||
throw new RuntimeException('Timezone support is not implemented');
|
return $this->generateCodeForArrayCollection($propertyMetadata, $type, $arrayPath, $modelPropertyPath, $stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
$format = $type->getDeserializeFormat() ?: $type->getFormat();
|
return $this->generateCodeForArray($type, $arrayPath, $modelPropertyPath, $stack);
|
||||||
|
|
||||||
|
case $type instanceof PropertyTypeDateTime:
|
||||||
|
if (method_exists($type, 'getDeserializeFormat')) {
|
||||||
|
$format = $type->getDeserializeFormat();
|
||||||
|
|
||||||
if (null !== $format) {
|
if (null !== $format) {
|
||||||
return $this->templating->renderAssignDateTimeFromFormat(
|
return $this->templating->renderAssignDateTimeFromFormat($type->isImmutable(), (string) $modelPropertyPath, (string) $arrayPath, $format, $type->getZone());
|
||||||
$type->isImmutable(),
|
|
||||||
(string) $modelPropertyPath,
|
|
||||||
(string) $arrayPath,
|
|
||||||
$format
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->templating->renderAssignDateTimeToField(
|
return $this->templating->renderAssignDateTimeToField($type->isImmutable(), (string) $modelPropertyPath, (string) $arrayPath);
|
||||||
$type->isImmutable(),
|
}
|
||||||
(string) $modelPropertyPath,
|
|
||||||
(string) $arrayPath
|
$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);
|
||||||
|
|
||||||
case $type instanceof PropertyTypePrimitive && 'float' === $type->getTypeName():
|
case $type instanceof PropertyTypePrimitive && 'float' === $type->getTypeName():
|
||||||
return $this->templating->renderAssignJsonDataToFieldWithCasting(
|
return $this->templating->renderAssignJsonDataToFieldWithCasting((string) $modelPropertyPath, (string) $arrayPath, 'float');
|
||||||
(string) $modelPropertyPath,
|
|
||||||
(string) $arrayPath,
|
|
||||||
'float'
|
|
||||||
);
|
|
||||||
case $type instanceof PropertyTypePrimitive:
|
case $type instanceof PropertyTypePrimitive:
|
||||||
case $type instanceof PropertyTypeUnknown:
|
case $type instanceof PropertyTypeUnknown:
|
||||||
case $type instanceof PropertyTypeMixed:
|
case $type instanceof PropertyTypeMixed:
|
||||||
return $this->templating->renderAssignJsonDataToField((string) $modelPropertyPath, (string) $arrayPath);
|
return $this->templating->renderAssignJsonDataToField((string) $modelPropertyPath, (string) $arrayPath);
|
||||||
|
|
||||||
case $type instanceof PropertyTypeClass:
|
case $type instanceof PropertyTypeClass:
|
||||||
return $this->generateCodeForClass($type->getClassMetadata(), $arrayPath, $modelPropertyPath, $stack);
|
return $this->generateCodeForClass($type->getClassMetadata(), $arrayPath, $modelPropertyPath, $stack);
|
||||||
|
|
||||||
default:
|
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 array<string, positive-int> $stack
|
||||||
* @param \Liip\Serializer\Path\ArrayPath $arrayPath
|
|
||||||
* @param \Liip\Serializer\Path\ModelPath $modelPath
|
|
||||||
* @param mixed[] $stack
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
*/
|
||||||
private function generateCodeForArray(
|
private function generateCodeForArray(
|
||||||
PropertyTypeArray $type,
|
PropertyTypeArray $type,
|
||||||
@ -424,6 +330,11 @@ class DeserializerGenerator
|
|||||||
ModelPath $modelPath,
|
ModelPath $modelPath,
|
||||||
array $stack
|
array $stack
|
||||||
): string {
|
): 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);
|
$index = ModelPath::indexVariable((string) $arrayPath);
|
||||||
$arrayPropertyPath = $arrayPath->withVariable((string) $index);
|
$arrayPropertyPath = $arrayPath->withVariable((string) $index);
|
||||||
$modelPropertyPath = $modelPath->withArray((string) $index);
|
$modelPropertyPath = $modelPath->withArray((string) $index);
|
||||||
@ -433,22 +344,16 @@ class DeserializerGenerator
|
|||||||
case $subType instanceof PropertyTypeArray:
|
case $subType instanceof PropertyTypeArray:
|
||||||
$innerCode = $this->generateCodeForArray($subType, $arrayPropertyPath, $modelPropertyPath, $stack);
|
$innerCode = $this->generateCodeForArray($subType, $arrayPropertyPath, $modelPropertyPath, $stack);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case $subType instanceof PropertyTypeClass:
|
case $subType instanceof PropertyTypeClass:
|
||||||
$innerCode = $this->generateCodeForClass(
|
$innerCode = $this->generateCodeForClass($subType->getClassMetadata(), $arrayPropertyPath, $modelPropertyPath, $stack);
|
||||||
$subType->getClassMetadata(),
|
|
||||||
$arrayPropertyPath,
|
|
||||||
$modelPropertyPath,
|
|
||||||
$stack
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case $subType instanceof PropertyTypeUnknown:
|
case $subType instanceof PropertyTypeUnknown:
|
||||||
$innerCode = $this->templating->renderAssignJsonDataToField(
|
return $this->templating->renderAssignJsonDataToField((string) $modelPath, (string) $arrayPath);
|
||||||
$modelPropertyPath,
|
|
||||||
$arrayPropertyPath
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException('Unexpected array subtype ' . get_class($subType));
|
throw new \Exception('Unexpected array subtype '. get_class($subType));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('' === $innerCode) {
|
if ('' === $innerCode) {
|
||||||
@ -460,4 +365,42 @@ class DeserializerGenerator
|
|||||||
|
|
||||||
return $code;
|
return $code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, positive-int> $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<class-string> $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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
57
src/Component/Serializer/Generator/Recursion.php
Normal file
57
src/Component/Serializer/Generator/Recursion.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace RetailCrm\Api\Component\Serializer\Generator;
|
||||||
|
|
||||||
|
use Liip\MetadataParser\Metadata\PropertyMetadata;
|
||||||
|
use Liip\MetadataParser\Metadata\PropertyTypeArray;
|
||||||
|
use Liip\MetadataParser\Metadata\PropertyTypeClass;
|
||||||
|
|
||||||
|
abstract class Recursion
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param array<string, positive-int> $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<string, positive-int> $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();
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
|
||||||
* PHP version 7.3
|
|
||||||
*
|
|
||||||
* @category SerializerGenerator
|
|
||||||
* @package RetailCrm\Api\Component\Serializer\Generator
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace RetailCrm\Api\Component\Serializer\Generator;
|
namespace RetailCrm\Api\Component\Serializer\Generator;
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
use Liip\MetadataParser\Builder;
|
use Liip\MetadataParser\Builder;
|
||||||
use Liip\MetadataParser\Metadata\ClassMetadata;
|
use Liip\MetadataParser\Metadata\ClassMetadata;
|
||||||
use Liip\MetadataParser\Metadata\PropertyMetadata;
|
use Liip\MetadataParser\Metadata\PropertyMetadata;
|
||||||
@ -27,140 +19,99 @@ use Liip\MetadataParser\Reducer\TakeBestReducer;
|
|||||||
use Liip\MetadataParser\Reducer\VersionReducer;
|
use Liip\MetadataParser\Reducer\VersionReducer;
|
||||||
use Liip\Serializer\Configuration\GeneratorConfiguration;
|
use Liip\Serializer\Configuration\GeneratorConfiguration;
|
||||||
use Liip\Serializer\Template\Serialization;
|
use Liip\Serializer\Template\Serialization;
|
||||||
|
use RetailCrm\Api\Component\Serializer\Template\CustomSerialization;
|
||||||
|
use RetailCrm\Api\Component\Serializer\Type\PropertyTypeMixed;
|
||||||
use RetailCrm\Api\Interfaces\Orders\CustomerInterface;
|
use RetailCrm\Api\Interfaces\Orders\CustomerInterface;
|
||||||
use RetailCrm\Api\Model\Entity\Customers\Customer;
|
use RetailCrm\Api\Model\Entity\Customers\Customer;
|
||||||
use RetailCrm\Api\Model\Entity\Customers\CustomerTag;
|
use RetailCrm\Api\Model\Entity\Customers\CustomerTag;
|
||||||
use RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerCorporate;
|
use RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerCorporate;
|
||||||
use RetailCrm\Api\Component\Serializer\Template\CustomSerialization;
|
|
||||||
use RetailCrm\Api\Component\Serializer\Type\PropertyTypeMixed;
|
|
||||||
use RetailCrm\Api\Model\Entity\CustomersCorporate\SerializedRelationAbstractCustomer;
|
use RetailCrm\Api\Model\Entity\CustomersCorporate\SerializedRelationAbstractCustomer;
|
||||||
use RetailCrm\Api\Model\Entity\Orders\SerializedRelationCustomer;
|
use RetailCrm\Api\Model\Entity\Orders\SerializedRelationCustomer;
|
||||||
use RuntimeException;
|
|
||||||
use Symfony\Component\Filesystem\Filesystem;
|
use Symfony\Component\Filesystem\Filesystem;
|
||||||
|
|
||||||
/**
|
final class SerializerGenerator
|
||||||
* Class SerializerGenerator
|
|
||||||
*
|
|
||||||
* @category SerializerGenerator
|
|
||||||
* @package RetailCrm\Api\Component\Serializer\Generator
|
|
||||||
* @license https://github.com/liip/serializer/blob/master/LICENSE MIT License
|
|
||||||
* @author Liip <https://github.com/liip>
|
|
||||||
* @author Pavel Kovalenko
|
|
||||||
* @see https://github.com/liip/serializer
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @SuppressWarnings(PHPMD)
|
|
||||||
*/
|
|
||||||
class SerializerGenerator
|
|
||||||
{
|
{
|
||||||
private const FILENAME_PREFIX = 'serialize';
|
private const FILENAME_PREFIX = 'serialize';
|
||||||
|
|
||||||
/**
|
/** @var \Symfony\Component\Filesystem\Filesystem */
|
||||||
* @var Serialization
|
|
||||||
*/
|
|
||||||
private $templating;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var \RetailCrm\Api\Component\Serializer\Template\CustomSerialization
|
|
||||||
*/
|
|
||||||
private $customTemplating;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Builder
|
|
||||||
*/
|
|
||||||
private $metadataBuilder;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var GeneratorConfiguration<mixed>
|
|
||||||
*/
|
|
||||||
private $configuration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $cacheDirectory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Filesystem
|
|
||||||
*/
|
|
||||||
private $filesystem;
|
private $filesystem;
|
||||||
|
|
||||||
/**
|
/** @var \Liip\Serializer\Template\Serialization */
|
||||||
* SerializerGenerator constructor.
|
private $templating;
|
||||||
*
|
|
||||||
* @param \Liip\Serializer\Template\Serialization $templating
|
/** @var \Liip\Serializer\Configuration\GeneratorConfiguration */
|
||||||
* @param \RetailCrm\Api\Component\Serializer\Template\CustomSerialization $customTemplating
|
private $configuration;
|
||||||
* @param \Liip\Serializer\Configuration\GeneratorConfiguration<mixed> $configuration
|
|
||||||
* @param string $cacheDirectory
|
/** @var string */
|
||||||
*/
|
private $cacheDirectory;
|
||||||
|
|
||||||
|
/** @var \RetailCrm\Api\Component\Serializer\Template\CustomSerialization */
|
||||||
|
private $customTemplating;
|
||||||
|
|
||||||
|
/** @var \Liip\MetadataParser\Builder */
|
||||||
|
private $metadataBuilder;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
Serialization $templating,
|
Serialization $templating,
|
||||||
CustomSerialization $customTemplating,
|
CustomSerialization $customTemplating,
|
||||||
GeneratorConfiguration $configuration,
|
GeneratorConfiguration $configuration,
|
||||||
string $cacheDirectory
|
string $cacheDirectory
|
||||||
) {
|
) {
|
||||||
|
$this->cacheDirectory = $cacheDirectory;
|
||||||
|
$this->configuration = $configuration;
|
||||||
$this->templating = $templating;
|
$this->templating = $templating;
|
||||||
$this->customTemplating = $customTemplating;
|
$this->customTemplating = $customTemplating;
|
||||||
$this->configuration = $configuration;
|
|
||||||
$this->cacheDirectory = $cacheDirectory;
|
|
||||||
|
|
||||||
$this->filesystem = new Filesystem();
|
$this->filesystem = new Filesystem();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $className
|
* @param list<string> $serializerGroups
|
||||||
* @param string|null $apiVersion
|
|
||||||
* @param array<mixed> $serializerGroups
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public static function buildSerializerFunctionName(
|
public static function buildSerializerFunctionName(string $className, ?string $apiVersion, array $serializerGroups): string
|
||||||
string $className,
|
{
|
||||||
?string $apiVersion,
|
$functionName = self::FILENAME_PREFIX.'_'.$className;
|
||||||
array $serializerGroups
|
if (\count($serializerGroups)) {
|
||||||
): string {
|
|
||||||
$functionName = static::FILENAME_PREFIX . '_' . $className;
|
|
||||||
|
|
||||||
if (count($serializerGroups)) {
|
|
||||||
$functionName .= '_'.implode('_', $serializerGroups);
|
$functionName .= '_'.implode('_', $serializerGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null !== $apiVersion) {
|
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
|
public function generate(Builder $metadataBuilder): void
|
||||||
{
|
{
|
||||||
$this->metadataBuilder = $metadataBuilder;
|
$this->metadataBuilder = $metadataBuilder;
|
||||||
|
|
||||||
$this->filesystem->mkdir($this->cacheDirectory);
|
$this->filesystem->mkdir($this->cacheDirectory);
|
||||||
|
|
||||||
foreach ($this->configuration as $classToGenerate) {
|
foreach ($this->configuration as $classToGenerate) {
|
||||||
foreach ($classToGenerate as $groupCombination) {
|
foreach ($classToGenerate as $groupCombination) {
|
||||||
$className = $classToGenerate->getClassName();
|
$className = $classToGenerate->getClassName();
|
||||||
|
|
||||||
foreach ($groupCombination->getVersions() as $version) {
|
foreach ($groupCombination->getVersions() as $version) {
|
||||||
|
$groups = $groupCombination->getGroups();
|
||||||
if ('' === $version) {
|
if ('' === $version) {
|
||||||
|
if ([] === $groups) {
|
||||||
$metadata = $metadataBuilder->build($className, [
|
$metadata = $metadataBuilder->build($className, [
|
||||||
new PreferredReducer(),
|
new PreferredReducer(),
|
||||||
new TakeBestReducer(),
|
new TakeBestReducer(),
|
||||||
]);
|
]);
|
||||||
|
$this->writeFile($className, null, [], $metadata);
|
||||||
$this->writeFile($className, null, $groupCombination->getGroups(), $metadata);
|
} else {
|
||||||
|
$metadata = $metadataBuilder->build($className, [
|
||||||
|
new GroupReducer($groups),
|
||||||
|
new PreferredReducer(),
|
||||||
|
new TakeBestReducer(),
|
||||||
|
]);
|
||||||
|
$this->writeFile($className, null, $groups, $metadata);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$metadata = $metadataBuilder->build($className, [
|
$metadata = $metadataBuilder->build($className, [
|
||||||
new VersionReducer($version),
|
new VersionReducer($version),
|
||||||
new GroupReducer($groupCombination->getGroups()),
|
new GroupReducer($groups),
|
||||||
new TakeBestReducer(),
|
new TakeBestReducer(),
|
||||||
]);
|
]);
|
||||||
|
$this->writeFile($className, $version, $groups, $metadata);
|
||||||
$this->writeFile($className, $version, $groupCombination->getGroups(), $metadata);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,10 +119,7 @@ class SerializerGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $className
|
* @param list<string> $serializerGroups
|
||||||
* @param string|null $apiVersion
|
|
||||||
* @param array<mixed> $serializerGroups
|
|
||||||
* @param \Liip\MetadataParser\Metadata\ClassMetadata $classMetadata
|
|
||||||
*/
|
*/
|
||||||
private function writeFile(
|
private function writeFile(
|
||||||
string $className,
|
string $className,
|
||||||
@ -179,8 +127,7 @@ class SerializerGenerator
|
|||||||
array $serializerGroups,
|
array $serializerGroups,
|
||||||
ClassMetadata $classMetadata
|
ClassMetadata $classMetadata
|
||||||
): void {
|
): void {
|
||||||
sort($serializerGroups);
|
$functionName = self::buildSerializerFunctionName($className, $apiVersion, $serializerGroups);
|
||||||
$functionName = static::buildSerializerFunctionName($className, $apiVersion, $serializerGroups);
|
|
||||||
|
|
||||||
$code = $this->templating->renderFunction(
|
$code = $this->templating->renderFunction(
|
||||||
$functionName,
|
$functionName,
|
||||||
@ -192,15 +139,8 @@ class SerializerGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Liip\MetadataParser\Metadata\ClassMetadata $classMetadata
|
* @param list<string> $serializerGroups
|
||||||
* @param string|null $apiVersion
|
* @param array<string, positive-int> $stack
|
||||||
* @param array<mixed> $serializerGroups
|
|
||||||
* @param string $arrayPath
|
|
||||||
* @param string $modelPath
|
|
||||||
* @param array<mixed> $stack
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
*/
|
||||||
private function generateCodeForClass(
|
private function generateCodeForClass(
|
||||||
ClassMetadata $classMetadata,
|
ClassMetadata $classMetadata,
|
||||||
@ -226,17 +166,10 @@ class SerializerGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
$stack[$classMetadata->getClassName()] = ($stack[$classMetadata->getClassName()] ?? 0) + 1;
|
$stack[$classMetadata->getClassName()] = ($stack[$classMetadata->getClassName()] ?? 0) + 1;
|
||||||
$code = '';
|
|
||||||
|
|
||||||
|
$code = '';
|
||||||
foreach ($classMetadata->getProperties() as $propertyMetadata) {
|
foreach ($classMetadata->getProperties() as $propertyMetadata) {
|
||||||
$code .= $this->generateCodeForField(
|
$code .= $this->generateCodeForField($propertyMetadata, $apiVersion, $serializerGroups, $arrayPath, $modelPath, $stack);
|
||||||
$propertyMetadata,
|
|
||||||
$apiVersion,
|
|
||||||
$serializerGroups,
|
|
||||||
$arrayPath,
|
|
||||||
$modelPath,
|
|
||||||
$stack
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->templating->renderClass($arrayPath, $code);
|
return $this->templating->renderClass($arrayPath, $code);
|
||||||
@ -334,15 +267,8 @@ class SerializerGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Liip\MetadataParser\Metadata\PropertyMetadata $propertyMetadata
|
* @param list<string> $serializerGroups
|
||||||
* @param string|null $apiVersion
|
* @param array<string, positive-int> $stack
|
||||||
* @param array<mixed> $serializerGroups
|
|
||||||
* @param string $arrayPath
|
|
||||||
* @param string $modelPath
|
|
||||||
* @param array<mixed> $stack
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
*/
|
||||||
private function generateCodeForField(
|
private function generateCodeForField(
|
||||||
PropertyMetadata $propertyMetadata,
|
PropertyMetadata $propertyMetadata,
|
||||||
@ -352,6 +278,10 @@ class SerializerGenerator
|
|||||||
string $modelPath,
|
string $modelPath,
|
||||||
array $stack
|
array $stack
|
||||||
): string {
|
): string {
|
||||||
|
if (Recursion::hasMaxDepthReached($propertyMetadata, $stack)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
$modelPropertyPath = $modelPath.'->'.$propertyMetadata->getName();
|
$modelPropertyPath = $modelPath.'->'.$propertyMetadata->getName();
|
||||||
$fieldPath = $arrayPath.'["'.$propertyMetadata->getSerializedName().'"]';
|
$fieldPath = $arrayPath.'["'.$propertyMetadata->getSerializedName().'"]';
|
||||||
|
|
||||||
@ -359,54 +289,23 @@ class SerializerGenerator
|
|||||||
$tempVariable = str_replace(['->', '[', ']', '$'], '', $modelPath).ucfirst($propertyMetadata->getName());
|
$tempVariable = str_replace(['->', '[', ']', '$'], '', $modelPath).ucfirst($propertyMetadata->getName());
|
||||||
|
|
||||||
return $this->templating->renderConditional(
|
return $this->templating->renderConditional(
|
||||||
$this->templating->renderTempVariable(
|
$this->templating->renderTempVariable($tempVariable, $this->templating->renderGetter($modelPath, $propertyMetadata->getAccessor()->getGetterMethod())),
|
||||||
$tempVariable,
|
$this->generateCodeForFieldType($propertyMetadata->getType(), $apiVersion, $serializerGroups, $fieldPath, '$'.$tempVariable, $stack)
|
||||||
$this->templating->renderGetter(
|
|
||||||
$modelPath,
|
|
||||||
(string) $propertyMetadata->getAccessor()->getGetterMethod()
|
|
||||||
)
|
|
||||||
),
|
|
||||||
$this->generateCodeForFieldType(
|
|
||||||
$propertyMetadata->getType(),
|
|
||||||
$apiVersion,
|
|
||||||
$serializerGroups,
|
|
||||||
$fieldPath,
|
|
||||||
'$' . $tempVariable,
|
|
||||||
$stack
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!$propertyMetadata->isPublic()) {
|
if (!$propertyMetadata->isPublic()) {
|
||||||
throw new RuntimeException(sprintf(
|
throw new \Exception(sprintf('Property %s is not public and no getter has been defined. Stack %s', $modelPropertyPath, var_export($stack, true)));
|
||||||
'Property %s is not public and no getter has been defined. Stack %s',
|
|
||||||
$modelPropertyPath,
|
|
||||||
var_export($stack, true)
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->templating->renderConditional(
|
return $this->templating->renderConditional(
|
||||||
$modelPropertyPath,
|
$modelPropertyPath,
|
||||||
$this->generateCodeForFieldType(
|
$this->generateCodeForFieldType($propertyMetadata->getType(), $apiVersion, $serializerGroups, $fieldPath, $modelPropertyPath, $stack)
|
||||||
$propertyMetadata->getType(),
|
|
||||||
$apiVersion,
|
|
||||||
$serializerGroups,
|
|
||||||
$fieldPath,
|
|
||||||
$modelPropertyPath,
|
|
||||||
$stack
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \Liip\MetadataParser\Metadata\PropertyType $type
|
* @param list<string> $serializerGroups
|
||||||
* @param string|null $apiVersion
|
* @param array<string, positive-int> $stack
|
||||||
* @param array<mixed> $serializerGroups
|
|
||||||
* @param string $fieldPath
|
|
||||||
* @param string $modelPropertyPath
|
|
||||||
* @param array<mixed> $stack
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
*/
|
||||||
private function generateCodeForFieldType(
|
private function generateCodeForFieldType(
|
||||||
PropertyType $type,
|
PropertyType $type,
|
||||||
@ -416,31 +315,9 @@ class SerializerGenerator
|
|||||||
string $modelPropertyPath,
|
string $modelPropertyPath,
|
||||||
array $stack
|
array $stack
|
||||||
): string {
|
): 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) {
|
switch ($type) {
|
||||||
case $type instanceof PropertyTypeDateTime:
|
case $type instanceof PropertyTypeDateTime:
|
||||||
if (null !== $type->getZone()) {
|
$dateFormat = $type->getFormat() ?: \DateTimeInterface::ISO8601;
|
||||||
throw new \RuntimeException('Timezone support is not implemented');
|
|
||||||
}
|
|
||||||
|
|
||||||
$dateFormat = $type->getFormat() ?: DateTime::ATOM;
|
|
||||||
|
|
||||||
return $this->templating->renderAssign(
|
return $this->templating->renderAssign(
|
||||||
$fieldPath,
|
$fieldPath,
|
||||||
@ -454,30 +331,19 @@ class SerializerGenerator
|
|||||||
return $this->templating->renderAssign($fieldPath, $modelPropertyPath);
|
return $this->templating->renderAssign($fieldPath, $modelPropertyPath);
|
||||||
|
|
||||||
case $type instanceof PropertyTypeClass:
|
case $type instanceof PropertyTypeClass:
|
||||||
return $this->generateCodeForClass(
|
return $this->generateCodeForClass($type->getClassMetadata(), $apiVersion, $serializerGroups, $fieldPath, $modelPropertyPath, $stack);
|
||||||
$type->getClassMetadata(),
|
|
||||||
$apiVersion,
|
case $type instanceof PropertyTypeArray:
|
||||||
$serializerGroups,
|
return $this->generateCodeForArray($type, $apiVersion, $serializerGroups, $fieldPath, $modelPropertyPath, $stack);
|
||||||
$fieldPath,
|
|
||||||
$modelPropertyPath,
|
|
||||||
$stack
|
|
||||||
);
|
|
||||||
|
|
||||||
default:
|
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 list<string> $serializerGroups
|
||||||
* @param string|null $apiVersion
|
* @param array<string, positive-int> $stack
|
||||||
* @param array<mixed> $serializerGroups
|
|
||||||
* @param string $arrayPath
|
|
||||||
* @param string $modelPath
|
|
||||||
* @param array<mixed> $stack
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
*/
|
||||||
private function generateCodeForArray(
|
private function generateCodeForArray(
|
||||||
PropertyTypeArray $type,
|
PropertyTypeArray $type,
|
||||||
@ -487,35 +353,25 @@ class SerializerGenerator
|
|||||||
string $modelPath,
|
string $modelPath,
|
||||||
array $stack
|
array $stack
|
||||||
): string {
|
): string {
|
||||||
$index = '$index' . \mb_strlen($arrayPath);
|
$index = '$index'.mb_strlen($arrayPath);
|
||||||
$subType = $type->getSubType();
|
$subType = $type->getSubType();
|
||||||
|
|
||||||
switch ($subType) {
|
switch ($subType) {
|
||||||
case $subType instanceof PropertyTypeArray:
|
case $subType instanceof PropertyTypePrimitive:
|
||||||
$innerCode = $this->generateCodeForArray(
|
case $subType instanceof PropertyTypeArray && self::isArrayForPrimitive($subType):
|
||||||
$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 PropertyTypeUnknown:
|
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;
|
break;
|
||||||
|
|
||||||
|
case $subType instanceof PropertyTypeClass:
|
||||||
|
$innerCode = $this->generateCodeForClass($subType->getClassMetadata(), $apiVersion, $serializerGroups, $arrayPath.'['.$index.']', $modelPath.'['.$index.']', $stack);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException('Unexpected array subtype ' . get_class($subType));
|
throw new \Exception('Unexpected array subtype '. get_class($subType));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('' === $innerCode) {
|
if ('' === $innerCode) {
|
||||||
@ -532,4 +388,16 @@ class SerializerGenerator
|
|||||||
|
|
||||||
return $this->templating->renderLoopArray($arrayPath, $modelPath, $index, $innerCode);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,197 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PHP version 7.3
|
|
||||||
*
|
|
||||||
* @category BaseJMSParser
|
|
||||||
* @package RetailCrm\Api\Component\Serializer\Parser
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace RetailCrm\Api\Component\Serializer\Parser;
|
|
||||||
|
|
||||||
use ReflectionClass;
|
|
||||||
use RetailCrm\Api\Component\Serializer\Exception\SyntaxError;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class Parser
|
|
||||||
*
|
|
||||||
* @package RetailCrm\Api\Component\Serializer\Parser
|
|
||||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
|
||||||
* @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<string, mixed>
|
|
||||||
*/
|
|
||||||
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<int, mixed>
|
|
||||||
*/
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace RetailCrm\Api\Component\Serializer\Parser\JMSCore\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base exception for the Serializer.
|
||||||
|
*
|
||||||
|
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||||
|
*/
|
||||||
|
interface Exception extends \Throwable
|
||||||
|
{
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type\Exception;
|
||||||
|
|
||||||
|
use RetailCrm\Api\Component\Serializer\Parser\JMSCore\Exception\Exception as BaseException;
|
||||||
|
|
||||||
|
interface Exception extends BaseException
|
||||||
|
{
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type\Exception;
|
||||||
|
|
||||||
|
final class InvalidNode extends \LogicException implements Exception
|
||||||
|
{
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type\Exception;
|
||||||
|
|
||||||
|
final class SyntaxError extends \RuntimeException implements Exception
|
||||||
|
{
|
||||||
|
}
|
@ -1,24 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
|
||||||
* PHP version 7.3
|
|
||||||
*
|
|
||||||
* @category BaseJMSParser
|
|
||||||
* @package RetailCrm\Api\Component\Serializer\Parser
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace RetailCrm\Api\Component\Serializer\Parser;
|
namespace RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type;
|
||||||
|
|
||||||
use Doctrine\Common\Lexer\AbstractLexer;
|
use Doctrine\Common\Lexer\AbstractLexer;
|
||||||
use RetailCrm\Api\Component\Serializer\Exception\SyntaxError;
|
use RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type\Exception\SyntaxError;
|
||||||
use Throwable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
final class JMSLexer extends AbstractLexer
|
final class Lexer extends AbstractLexer
|
||||||
{
|
{
|
||||||
public const T_UNKNOWN = 0;
|
public const T_UNKNOWN = 0;
|
||||||
public const T_INTEGER = 1;
|
public const T_INTEGER = 1;
|
||||||
@ -32,23 +24,15 @@ final class JMSLexer extends AbstractLexer
|
|||||||
public const T_IDENTIFIER = 9;
|
public const T_IDENTIFIER = 9;
|
||||||
public const T_NULL = 10;
|
public const T_NULL = 10;
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $type
|
|
||||||
*
|
|
||||||
* @return int|string|null
|
|
||||||
*/
|
|
||||||
public function parse(string $type)
|
public function parse(string $type)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return $this->getType($type);
|
return $this->getType($type);
|
||||||
} catch (Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
throw new SyntaxError($e->getMessage(), 0, $e);
|
throw new SyntaxError($e->getMessage(), 0, $e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
protected function getCatchablePatterns(): array
|
protected function getCatchablePatterns(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@ -63,18 +47,15 @@ final class JMSLexer extends AbstractLexer
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
protected function getNonCatchablePatterns(): array
|
protected function getNonCatchablePatterns(): array
|
||||||
{
|
{
|
||||||
return ['\s+'];
|
return ['\s+'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {{@inheritDoc}}
|
* {@inheritDoc}
|
||||||
*
|
*
|
||||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
* @return int|string|null
|
||||||
*/
|
*/
|
||||||
protected function getType(&$value)
|
protected function getType(&$value)
|
||||||
{
|
{
|
220
src/Component/Serializer/Parser/JMSCore/Type/Parser.php
Normal file
220
src/Component/Serializer/Parser/JMSCore/Type/Parser.php
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type;
|
||||||
|
|
||||||
|
use RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type\Exception\SyntaxError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class Parser implements ParserInterface
|
||||||
|
{
|
||||||
|
/** @var \RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type\Lexer */
|
||||||
|
private $lexer;
|
||||||
|
|
||||||
|
/** @var \RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type\Token|null */
|
||||||
|
private $token = null;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $input;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $root = true;
|
||||||
|
|
||||||
|
public function parse(string $type): array
|
||||||
|
{
|
||||||
|
$this->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());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type;
|
||||||
|
|
||||||
|
interface ParserInterface
|
||||||
|
{
|
||||||
|
public function parse(string $type): array;
|
||||||
|
}
|
66
src/Component/Serializer/Parser/JMSCore/Type/Token.php
Normal file
66
src/Component/Serializer/Parser/JMSCore/Type/Token.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type;
|
||||||
|
|
||||||
|
use function in_array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T of string|int
|
||||||
|
* @template V of string|int
|
||||||
|
*/
|
||||||
|
final class Token
|
||||||
|
{
|
||||||
|
public static function fromArray(array $source): Token
|
||||||
|
{
|
||||||
|
return new self($source['value'] ?? '', $source['type'] ?? '', $source['position'] ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromObject(object $source): Token
|
||||||
|
{
|
||||||
|
return new self($source->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);
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
declare(strict_types=1);
|
||||||
* PHP version 7.3
|
|
||||||
*
|
|
||||||
* @category JMSParser
|
|
||||||
* @package RetailCrm\Api\Component\Serializer\Parser
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace RetailCrm\Api\Component\Serializer\Parser;
|
namespace RetailCrm\Api\Component\Serializer\Parser;
|
||||||
|
|
||||||
use Doctrine\Common\Annotations\AnnotationException;
|
use Doctrine\Common\Annotations\AnnotationException;
|
||||||
use Doctrine\Common\Annotations\Reader;
|
use Doctrine\Common\Annotations\Reader;
|
||||||
|
use Liip\MetadataParser\ModelParser\ModelParserInterface;
|
||||||
use RetailCrm\Api\Component\Serializer\Annotation\Accessor;
|
use RetailCrm\Api\Component\Serializer\Annotation\Accessor;
|
||||||
use RetailCrm\Api\Component\Serializer\Annotation\AccessorOrder;
|
use RetailCrm\Api\Component\Serializer\Annotation\AccessorOrder;
|
||||||
use RetailCrm\Api\Component\Serializer\Annotation\Exclude;
|
use RetailCrm\Api\Component\Serializer\Annotation\Exclude;
|
||||||
use RetailCrm\Api\Component\Serializer\Annotation\ExclusionPolicy;
|
use RetailCrm\Api\Component\Serializer\Annotation\ExclusionPolicy;
|
||||||
use RetailCrm\Api\Component\Serializer\Annotation\Groups;
|
use RetailCrm\Api\Component\Serializer\Annotation\Groups;
|
||||||
|
use RetailCrm\Api\Component\Serializer\Annotation\MaxDepth;
|
||||||
use RetailCrm\Api\Component\Serializer\Annotation\PostDeserialize;
|
use RetailCrm\Api\Component\Serializer\Annotation\PostDeserialize;
|
||||||
use RetailCrm\Api\Component\Serializer\Annotation\SerializedName;
|
use RetailCrm\Api\Component\Serializer\Annotation\SerializedName;
|
||||||
use RetailCrm\Api\Component\Serializer\Annotation\Since;
|
use RetailCrm\Api\Component\Serializer\Annotation\Since;
|
||||||
@ -27,54 +24,32 @@ use Liip\MetadataParser\Exception\ParseException;
|
|||||||
use Liip\MetadataParser\Metadata\PropertyAccessor;
|
use Liip\MetadataParser\Metadata\PropertyAccessor;
|
||||||
use Liip\MetadataParser\Metadata\PropertyType;
|
use Liip\MetadataParser\Metadata\PropertyType;
|
||||||
use Liip\MetadataParser\Metadata\PropertyTypeUnknown;
|
use Liip\MetadataParser\Metadata\PropertyTypeUnknown;
|
||||||
use Liip\MetadataParser\ModelParser\ModelParserInterface;
|
|
||||||
use Liip\MetadataParser\ModelParser\RawMetadata\PropertyCollection;
|
use Liip\MetadataParser\ModelParser\RawMetadata\PropertyCollection;
|
||||||
use Liip\MetadataParser\ModelParser\RawMetadata\PropertyVariationMetadata;
|
use Liip\MetadataParser\ModelParser\RawMetadata\PropertyVariationMetadata;
|
||||||
use Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata;
|
use Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata;
|
||||||
use Liip\MetadataParser\TypeParser\PhpTypeParser;
|
use Liip\MetadataParser\TypeParser\PhpTypeParser;
|
||||||
use ReflectionClass;
|
use RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type\Exception\SyntaxError;
|
||||||
use ReflectionException;
|
|
||||||
use ReflectionMethod;
|
|
||||||
use ReflectionProperty;
|
|
||||||
use UnexpectedValueException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class JMSParser
|
* Parse JMSSerializer annotations.
|
||||||
|
*
|
||||||
|
* Run this parser *after* the PHPDoc parser as JMS annotations are more precise.
|
||||||
*
|
*
|
||||||
* @category JMSParser
|
|
||||||
* @package RetailCrm\Api\Component\Serializer\Parser
|
|
||||||
* @license https://github.com/liip/metadata-parser/blob/master/LICENSE MIT License
|
|
||||||
* @author Liip <https://github.com/liip>
|
|
||||||
* @author Pavel Kovalenko
|
|
||||||
* @see https://github.com/liip/metadata-parser
|
|
||||||
* @internal
|
* @internal
|
||||||
*
|
|
||||||
* @SuppressWarnings(PHPMD)
|
|
||||||
*/
|
*/
|
||||||
class JMSParser implements ModelParserInterface
|
class JMSParser implements ModelParserInterface
|
||||||
{
|
{
|
||||||
private const ACCESS_ORDER_CUSTOM = 'custom';
|
private const ACCESS_ORDER_CUSTOM = 'custom';
|
||||||
|
|
||||||
/**
|
/** @var \Doctrine\Common\Annotations\Reader */
|
||||||
* @var Reader
|
|
||||||
*/
|
|
||||||
private $annotationsReader;
|
private $annotationsReader;
|
||||||
|
|
||||||
/**
|
/** @var \Liip\MetadataParser\TypeParser\PhpTypeParser */
|
||||||
* @var PhpTypeParser
|
|
||||||
*/
|
|
||||||
private $phpTypeParser;
|
private $phpTypeParser;
|
||||||
|
|
||||||
/**
|
/** @var \RetailCrm\Api\Component\Serializer\Parser\JMSTypeParser */
|
||||||
* @var JMSTypeParser
|
protected $jmsTypeParser;
|
||||||
*/
|
|
||||||
private $jmsTypeParser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JMSParser constructor.
|
|
||||||
*
|
|
||||||
* @param \Doctrine\Common\Annotations\Reader $annotationsReader
|
|
||||||
*/
|
|
||||||
public function __construct(Reader $annotationsReader)
|
public function __construct(Reader $annotationsReader)
|
||||||
{
|
{
|
||||||
$this->annotationsReader = $annotationsReader;
|
$this->annotationsReader = $annotationsReader;
|
||||||
@ -82,124 +57,99 @@ class JMSParser implements ModelParserInterface
|
|||||||
$this->jmsTypeParser = new JMSTypeParser();
|
$this->jmsTypeParser = new JMSTypeParser();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata
|
|
||||||
*/
|
|
||||||
public function parse(RawClassMetadata $classMetadata): void
|
public function parse(RawClassMetadata $classMetadata): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$refClass = new ReflectionClass($classMetadata->getClassName()); // @phpstan-ignore-line
|
$reflClass = new \ReflectionClass($classMetadata->getClassName());
|
||||||
} catch (ReflectionException $exception) {
|
} catch (\ReflectionException $e) {
|
||||||
throw ParseException::classNotFound($classMetadata->getClassName(), $exception);
|
throw ParseException::classNotFound($classMetadata->getClassName(), $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->parseProperties($refClass, $classMetadata);
|
|
||||||
$this->parseMethods($refClass, $classMetadata);
|
|
||||||
$this->parseClass($refClass, $classMetadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param \ReflectionClass $refClass
|
|
||||||
* @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata
|
|
||||||
*
|
|
||||||
* @phpstan-ignore-next-line
|
|
||||||
*/
|
|
||||||
private function parseProperties(ReflectionClass $refClass, RawClassMetadata $classMetadata): void
|
|
||||||
{
|
|
||||||
if ($refParentClass = $refClass->getParentClass()) {
|
|
||||||
$this->parseProperties($refParentClass, $classMetadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($refClass->getProperties() as $refProperty) {
|
|
||||||
try {
|
try {
|
||||||
$annotations = $this->annotationsReader->getPropertyAnnotations($refProperty);
|
$this->parseProperties($reflClass, $classMetadata);
|
||||||
} catch (AnnotationException $exception) {
|
$this->parseMethods($reflClass, $classMetadata);
|
||||||
throw ParseException::propertyError((string) $classMetadata, $refProperty->getName(), $exception);
|
$this->parseClass($reflClass, $classMetadata);
|
||||||
|
} catch (SyntaxError $exception) {
|
||||||
|
throw new ParseException($exception->getMessage(), $exception->getCode(), $exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$property = $this->getProperty($classMetadata, $refProperty, $annotations);
|
private function parseProperties(\ReflectionClass $reflClass, RawClassMetadata $classMetadata): void
|
||||||
|
{
|
||||||
|
if ($reflParentClass = $reflClass->getParentClass()) {
|
||||||
|
$this->parseProperties($reflParentClass, $classMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($reflClass->getProperties() as $reflProperty) {
|
||||||
|
try {
|
||||||
|
$annotations = $this->annotationsReader->getPropertyAnnotations($reflProperty);
|
||||||
|
} catch (AnnotationException $e) {
|
||||||
|
throw ParseException::propertyError((string) $classMetadata, $reflProperty->getName(), $e);
|
||||||
|
}
|
||||||
|
|
||||||
|
$property = $this->getProperty($classMetadata, $reflProperty, $annotations);
|
||||||
$this->parsePropertyAnnotations($classMetadata, $property, $annotations);
|
$this->parsePropertyAnnotations($classMetadata, $property, $annotations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function parseMethods(\ReflectionClass $reflClass, RawClassMetadata $classMetadata): void
|
||||||
* @param \ReflectionClass $refClass
|
|
||||||
* @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata
|
|
||||||
*
|
|
||||||
* @phpstan-ignore-next-line
|
|
||||||
*/
|
|
||||||
private function parseMethods(ReflectionClass $refClass, RawClassMetadata $classMetadata): void
|
|
||||||
{
|
{
|
||||||
if ($refParentClass = $refClass->getParentClass()) {
|
if ($reflParentClass = $reflClass->getParentClass()) {
|
||||||
$this->parseMethods($refParentClass, $classMetadata);
|
$this->parseMethods($reflParentClass, $classMetadata);
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($refClass->getMethods() as $refMethod) {
|
|
||||||
if (false === $refMethod->getDocComment()) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($reflClass->getMethods() as $reflMethod) {
|
||||||
try {
|
try {
|
||||||
$annotations = $this->annotationsReader->getMethodAnnotations($refMethod);
|
$annotations = $this->annotationsReader->getMethodAnnotations($reflMethod);
|
||||||
} catch (AnnotationException $exception) {
|
} catch (AnnotationException $e) {
|
||||||
throw ParseException::propertyError((string) $classMetadata, $refMethod->getName(), $exception);
|
throw ParseException::propertyError((string) $classMetadata, $reflMethod->getName(), $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->isVirtualProperty($annotations)) {
|
if ($this->isVirtualProperty($annotations)) {
|
||||||
if (!$refMethod->isPublic()) {
|
if (!$reflMethod->isPublic()) {
|
||||||
throw ParseException::nonPublicMethod((string) $classMetadata, $refMethod->getName());
|
throw ParseException::nonPublicMethod((string) $classMetadata, $reflMethod->getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
$methodName = $this->getMethodName($annotations, $refMethod);
|
$methodName = $this->getMethodName($annotations, $reflMethod);
|
||||||
$name = $this->getSerializedName($annotations) ?: $methodName;
|
$name = $this->getSerializedName($annotations) ?: $methodName;
|
||||||
|
|
||||||
$property = new PropertyVariationMetadata($methodName, true, true);
|
$property = new PropertyVariationMetadata($methodName, true, true);
|
||||||
$classMetadata->addPropertyVariation($name, $property);
|
$classMetadata->addPropertyVariation($name, $property);
|
||||||
|
|
||||||
$property->setType($this->getReturnType($property, $refMethod, $refClass));
|
$property->setType($this->getReturnType($property, $reflMethod, $reflClass));
|
||||||
$property->setAccessor(new PropertyAccessor($refMethod->getName(), null));
|
$property->setAccessor(new PropertyAccessor($reflMethod->getName(), null));
|
||||||
|
|
||||||
$this->parsePropertyAnnotations($classMetadata, $property, $annotations);
|
$this->parsePropertyAnnotations($classMetadata, $property, $annotations);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->isPostDeserializeMethod($annotations)) {
|
if ($this->isPostDeserializeMethod($annotations)) {
|
||||||
if (!$refMethod->isPublic()) {
|
if (!$reflMethod->isPublic()) {
|
||||||
throw ParseException::nonPublicMethod((string) $classMetadata, $refMethod->getName());
|
throw ParseException::nonPublicMethod((string) $classMetadata, $reflMethod->getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
$classMetadata->addPostDeserializeMethod($refMethod->getName());
|
$classMetadata->addPostDeserializeMethod($reflMethod->getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function parseClass(\ReflectionClass $reflClass, RawClassMetadata $classMetadata): void
|
||||||
* @param \ReflectionClass $refClass
|
|
||||||
* @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata
|
|
||||||
*
|
|
||||||
* @phpstan-ignore-next-line
|
|
||||||
*/
|
|
||||||
private function parseClass(ReflectionClass $refClass, RawClassMetadata $classMetadata): void
|
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$annotations = $this->gatherClassAnnotations($refClass);
|
$annotations = $this->gatherClassAnnotations($reflClass);
|
||||||
} catch (AnnotationException $e) {
|
} catch (AnnotationException $e) {
|
||||||
throw ParseException::classError($refClass->getName(), $e);
|
throw ParseException::classError($reflClass->getName(), $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($annotations as $annotation) {
|
foreach ($annotations as $annotation) {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case $annotation instanceof AccessorOrder:
|
case $annotation instanceof AccessorOrder:
|
||||||
if (self::ACCESS_ORDER_CUSTOM !== $annotation->order) {
|
if (self::ACCESS_ORDER_CUSTOM !== $annotation->order) {
|
||||||
throw ParseException::unsupportedClassAnnotation(
|
throw ParseException::unsupportedClassAnnotation((string) $classMetadata, 'AccessorOrder::'.$annotation->order);
|
||||||
(string) $classMetadata,
|
|
||||||
'AccessorOrder::' . $annotation->order
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// usort is not stable for the same result. we want to preserve order of
|
// usort is not stable for the same result. we want to preserve order of the fields that are not explicitly mentioned
|
||||||
// the fields that are not explicitly mentioned
|
|
||||||
$order = [];
|
$order = [];
|
||||||
$init = count($annotation->custom);
|
$init = \count($annotation->custom);
|
||||||
foreach ($classMetadata->getPropertyCollections() as $property) {
|
foreach ($classMetadata->getPropertyCollections() as $property) {
|
||||||
$position = $property->getPosition($annotation->custom);
|
$position = $property->getPosition($annotation->custom);
|
||||||
if (null === $position) {
|
if (null === $position) {
|
||||||
@ -208,21 +158,17 @@ class JMSParser implements ModelParserInterface
|
|||||||
$order[$property->getSerializedName()] = $position;
|
$order[$property->getSerializedName()] = $position;
|
||||||
}
|
}
|
||||||
|
|
||||||
$classMetadata->sortProperties(static function (
|
$classMetadata->sortProperties(static function (PropertyCollection $propA, PropertyCollection $propB) use ($order): int {
|
||||||
PropertyCollection $propA,
|
|
||||||
PropertyCollection $propB
|
|
||||||
) use ($order): int {
|
|
||||||
return $order[$propA->getSerializedName()] <=> $order[$propB->getSerializedName()];
|
return $order[$propA->getSerializedName()] <=> $order[$propB->getSerializedName()];
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case $annotation instanceof ExclusionPolicy:
|
case $annotation instanceof ExclusionPolicy:
|
||||||
if (ExclusionPolicy::NONE !== $annotation->policy) {
|
if (ExclusionPolicy::NONE !== $annotation->policy) {
|
||||||
throw ParseException::unsupportedClassAnnotation(
|
throw ParseException::unsupportedClassAnnotation((string) $classMetadata, 'ExclusionPolicy::'.$annotation->policy);
|
||||||
(string) $classMetadata,
|
|
||||||
'ExclusionPolicy::' . $annotation->policy
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (
|
if (
|
||||||
0 === strncmp(
|
0 === strncmp(
|
||||||
@ -232,10 +178,7 @@ class JMSParser implements ModelParserInterface
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
// if there are annotations we can safely ignore, we need to explicitly ignore them
|
// if there are annotations we can safely ignore, we need to explicitly ignore them
|
||||||
throw ParseException::unsupportedClassAnnotation(
|
throw ParseException::unsupportedClassAnnotation((string) $classMetadata, \get_class($annotation));
|
||||||
(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.
|
* Find the annotations we care about by looking through all ancestors of $reflectionClass.
|
||||||
*
|
*
|
||||||
* @param \ReflectionClass $reflectionClass
|
|
||||||
*
|
|
||||||
* @return object[] Hashmap of annotation class => annotation object
|
* @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 = [];
|
$map = [];
|
||||||
|
|
||||||
if ($parent = $reflectionClass->getParentClass()) {
|
if ($parent = $reflectionClass->getParentClass()) {
|
||||||
$map = $this->gatherClassAnnotations($parent);
|
$map = $this->gatherClassAnnotations($parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
$annotations = $this->annotationsReader->getClassAnnotations($reflectionClass);
|
$annotations = $this->annotationsReader->getClassAnnotations($reflectionClass);
|
||||||
|
|
||||||
foreach ($annotations as $annotation) {
|
foreach ($annotations as $annotation) {
|
||||||
$map[get_class($annotation)] = $annotation;
|
$map[\get_class($annotation)] = $annotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $map;
|
return $map;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function parsePropertyAnnotations(RawClassMetadata $classMetadata, PropertyVariationMetadata $property, array $annotations): void
|
||||||
* @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata
|
{
|
||||||
* @param \Liip\MetadataParser\ModelParser\RawMetadata\PropertyVariationMetadata $property
|
|
||||||
* @param array<mixed> $annotations
|
|
||||||
*/
|
|
||||||
private function parsePropertyAnnotations(
|
|
||||||
RawClassMetadata $classMetadata,
|
|
||||||
PropertyVariationMetadata $property,
|
|
||||||
array $annotations
|
|
||||||
): void {
|
|
||||||
foreach ($annotations as $annotation) {
|
foreach ($annotations as $annotation) {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case $annotation instanceof Type:
|
case $annotation instanceof Type:
|
||||||
|
if (null === $annotation->name) {
|
||||||
|
throw ParseException::propertyTypeNameNull((string) $classMetadata, (string) $property);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
$type = $this->jmsTypeParser->parse($annotation->name);
|
$type = $this->jmsTypeParser->parse($annotation->name);
|
||||||
} catch (InvalidTypeException $exception) {
|
} catch (InvalidTypeException $e) {
|
||||||
throw ParseException::propertyTypeError(
|
throw ParseException::propertyTypeError((string) $classMetadata, (string) $property, $e);
|
||||||
(string) $classMetadata,
|
|
||||||
(string) $property,
|
|
||||||
$exception
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($property->getType() instanceof PropertyTypeUnknown) {
|
if ($property->getType() instanceof PropertyTypeUnknown) {
|
||||||
@ -296,52 +224,49 @@ class JMSParser implements ModelParserInterface
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
$property->setType($property->getType()->merge($type));
|
$property->setType($property->getType()->merge($type));
|
||||||
} catch (UnexpectedValueException $exception) {
|
} catch (\UnexpectedValueException $e) {
|
||||||
throw ParseException::propertyTypeConflict(
|
throw ParseException::propertyTypeConflict((string) $classMetadata, (string) $property, (string) $property->getType(), (string) $type, $e);
|
||||||
(string) $classMetadata,
|
|
||||||
(string) $property,
|
|
||||||
(string) $property->getType(),
|
|
||||||
(string) $type,
|
|
||||||
$exception
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case $annotation instanceof Exclude:
|
case $annotation instanceof Exclude:
|
||||||
if (null !== $annotation->if) {
|
if (null !== $annotation->if) {
|
||||||
throw ParseException::unsupportedPropertyAnnotation(
|
throw ParseException::unsupportedPropertyAnnotation((string) $classMetadata, (string) $property, 'Exclude::if');
|
||||||
(string) $classMetadata,
|
|
||||||
(string) $property,
|
|
||||||
'Exclude::if'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
$classMetadata->removePropertyVariation((string) $property);
|
$classMetadata->removePropertyVariation((string) $property);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case $annotation instanceof Groups:
|
case $annotation instanceof Groups:
|
||||||
$property->setGroups($annotation->groups);
|
$property->setGroups($annotation->groups);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case $annotation instanceof Accessor:
|
case $annotation instanceof Accessor:
|
||||||
$property->setAccessor(new PropertyAccessor($annotation->getter, $annotation->setter));
|
$property->setAccessor(new PropertyAccessor($annotation->getter, $annotation->setter));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case $annotation instanceof Since:
|
case $annotation instanceof Since:
|
||||||
$property->setVersionRange($property->getVersionRange()->withSince($annotation->version));
|
$property->setVersionRange($property->getVersionRange()->withSince($annotation->version));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case $annotation instanceof Until:
|
case $annotation instanceof Until:
|
||||||
$property->setVersionRange($property->getVersionRange()->withUntil($annotation->version));
|
$property->setVersionRange($property->getVersionRange()->withUntil($annotation->version));
|
||||||
break;
|
break;
|
||||||
case $annotation instanceof SerializedName:
|
|
||||||
// we handle this separately
|
case $annotation instanceof MaxDepth:
|
||||||
|
$property->setMaxDepth($annotation->depth);
|
||||||
|
break;
|
||||||
|
|
||||||
case $annotation instanceof VirtualProperty:
|
case $annotation instanceof VirtualProperty:
|
||||||
// we handle this separately
|
// we handle this separately
|
||||||
|
case $annotation instanceof SerializedName:
|
||||||
|
// we handle this separately
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
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
|
// if there are annotations we can safely ignore, we need to explicitly ignore them
|
||||||
throw ParseException::unsupportedPropertyAnnotation(
|
throw ParseException::unsupportedPropertyAnnotation((string) $classMetadata, (string) $property, \get_class($annotation));
|
||||||
(string) $classMetadata,
|
|
||||||
(string) $property,
|
|
||||||
get_class($annotation)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -352,96 +277,38 @@ class JMSParser implements ModelParserInterface
|
|||||||
* Returns the property metadata for the specified property.
|
* Returns the property metadata for the specified property.
|
||||||
*
|
*
|
||||||
* If the property already exists on the class metadata this is returned.
|
* 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,
|
* If the property has a serialized name that overrides the name of an existing property, it will be renamed and merged.
|
||||||
* it will be renamed and merged.
|
|
||||||
*
|
|
||||||
* @param \Liip\MetadataParser\ModelParser\RawMetadata\RawClassMetadata $classMetadata
|
|
||||||
* @param \ReflectionProperty $refProperty
|
|
||||||
* @param array<mixed> $annotations
|
|
||||||
*
|
|
||||||
* @return \Liip\MetadataParser\ModelParser\RawMetadata\PropertyVariationMetadata
|
|
||||||
* @throws \ReflectionException
|
|
||||||
*/
|
*/
|
||||||
private function getProperty(
|
private function getProperty(RawClassMetadata $classMetadata, \ReflectionProperty $reflProperty, array $annotations): PropertyVariationMetadata
|
||||||
RawClassMetadata $classMetadata,
|
{
|
||||||
ReflectionProperty $refProperty,
|
$defaultName = PropertyCollection::serializedName($reflProperty->getName());
|
||||||
array $annotations
|
|
||||||
): PropertyVariationMetadata {
|
|
||||||
$defaultName = PropertyCollection::serializedName($refProperty->getName());
|
|
||||||
$name = $this->getSerializedName($annotations) ?: $defaultName;
|
$name = $this->getSerializedName($annotations) ?: $defaultName;
|
||||||
|
if ($classMetadata->hasPropertyVariation($reflProperty->getName())) {
|
||||||
if ($classMetadata->hasPropertyVariation($refProperty->getName())) {
|
$property = $classMetadata->getPropertyVariation($reflProperty->getName());
|
||||||
$property = $classMetadata->getPropertyVariation($refProperty->getName());
|
|
||||||
|
|
||||||
if ($defaultName !== $name && $classMetadata->hasPropertyCollection($defaultName)) {
|
if ($defaultName !== $name && $classMetadata->hasPropertyCollection($defaultName)) {
|
||||||
$classMetadata->removePropertyVariation($defaultName);
|
$classMetadata->renameProperty($defaultName, $name);
|
||||||
$this->addPropertyVariation($defaultName, $name, $property, $classMetadata);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$property = PropertyVariationMetadata::fromReflection($refProperty);
|
$property = PropertyVariationMetadata::fromReflection($reflProperty);
|
||||||
$this->addPropertyVariation($defaultName, $name, $property, $classMetadata);
|
$classMetadata->addPropertyVariation($name, $property);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $property;
|
return $property;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function getReturnType(PropertyVariationMetadata $property, \ReflectionMethod $reflMethod, \ReflectionClass $reflClass): PropertyType
|
||||||
* 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 {
|
|
||||||
$type = new PropertyTypeUnknown(true);
|
$type = new PropertyTypeUnknown(true);
|
||||||
$refType = $refMethod->getReturnType();
|
|
||||||
|
|
||||||
if (null !== $refType) {
|
$reflType = $reflMethod->getReturnType();
|
||||||
$type = $this->phpTypeParser->parseReflectionType($refType);
|
if (null !== $reflType) {
|
||||||
|
$type = $this->phpTypeParser->parseReflectionType($reflType);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$docBlockType = $this->getReturnTypeOfMethod($refMethod, $refClass);
|
$docBlockType = $this->getReturnTypeOfMethod($reflMethod);
|
||||||
} catch (InvalidTypeException $exception) {
|
} catch (InvalidTypeException $e) {
|
||||||
throw ParseException::propertyTypeError($refClass->getName(), (string) $property, $exception);
|
throw ParseException::propertyTypeError($reflClass->getName(), (string) $property, $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null === $docBlockType) {
|
if (null === $docBlockType) {
|
||||||
@ -450,47 +317,27 @@ class JMSParser implements ModelParserInterface
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
return $type->merge($docBlockType);
|
return $type->merge($docBlockType);
|
||||||
} catch (UnexpectedValueException $exception) {
|
} catch (\UnexpectedValueException $e) {
|
||||||
throw ParseException::propertyTypeConflict(
|
throw ParseException::propertyTypeConflict($reflClass->getName(), (string) $property, (string) $type, (string) $docBlockType, $e);
|
||||||
$refClass->getName(),
|
|
||||||
(string) $property,
|
|
||||||
(string) $type,
|
|
||||||
(string) $docBlockType,
|
|
||||||
$exception
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function getReturnTypeOfMethod(\ReflectionMethod $reflMethod): ?PropertyType
|
||||||
* @param \ReflectionMethod $refMethod
|
|
||||||
* @param \ReflectionClass $refClass
|
|
||||||
*
|
|
||||||
* @return \Liip\MetadataParser\Metadata\PropertyType|null
|
|
||||||
*
|
|
||||||
* @phpstan-ignore-next-line
|
|
||||||
*/
|
|
||||||
private function getReturnTypeOfMethod(ReflectionMethod $refMethod, ReflectionClass $refClass): ?PropertyType
|
|
||||||
{
|
{
|
||||||
$docComment = $refMethod->getDocComment();
|
$docComment = $reflMethod->getDocComment();
|
||||||
|
|
||||||
if (false === $docComment) {
|
if (false === $docComment) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (explode("\n", $docComment) as $line) {
|
foreach (explode("\n", $docComment) as $line) {
|
||||||
if (1 === preg_match('/@return ([^ ]+)/', $line, $matches)) {
|
if (1 === preg_match('/@return ([^ ]+)/', $line, $matches)) {
|
||||||
return $this->phpTypeParser->parseAnnotationType($matches[1], $refClass);
|
return $this->phpTypeParser->parseAnnotationType($matches[1], $reflMethod->getDeclaringClass());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<mixed> $annotations
|
|
||||||
*
|
|
||||||
* @return string|null
|
|
||||||
*/
|
|
||||||
private function getSerializedName(array $annotations): ?string
|
private function getSerializedName(array $annotations): ?string
|
||||||
{
|
{
|
||||||
foreach ($annotations as $annotation) {
|
foreach ($annotations as $annotation) {
|
||||||
@ -502,11 +349,6 @@ class JMSParser implements ModelParserInterface
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<mixed> $annotations
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
private function isVirtualProperty(array $annotations): bool
|
private function isVirtualProperty(array $annotations): bool
|
||||||
{
|
{
|
||||||
foreach ($annotations as $annotation) {
|
foreach ($annotations as $annotation) {
|
||||||
@ -518,11 +360,6 @@ class JMSParser implements ModelParserInterface
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<mixed> $annotations
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
private function isPostDeserializeMethod(array $annotations): bool
|
private function isPostDeserializeMethod(array $annotations): bool
|
||||||
{
|
{
|
||||||
foreach ($annotations as $annotation) {
|
foreach ($annotations as $annotation) {
|
||||||
@ -534,16 +371,9 @@ class JMSParser implements ModelParserInterface
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function getMethodName(array $annotations, \ReflectionMethod $reflMethod): string
|
||||||
* @param array<mixed> $annotations
|
|
||||||
* @param \ReflectionMethod $refMethod
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
private function getMethodName(array $annotations, ReflectionMethod $refMethod): string
|
|
||||||
{
|
{
|
||||||
$name = $refMethod->getName();
|
$name = $reflMethod->getName();
|
||||||
|
|
||||||
foreach ($annotations as $annotation) {
|
foreach ($annotations as $annotation) {
|
||||||
if ($annotation instanceof VirtualProperty && null !== $annotation->name) {
|
if ($annotation instanceof VirtualProperty && null !== $annotation->name) {
|
||||||
$name = $annotation->name;
|
$name = $annotation->name;
|
||||||
|
@ -1,59 +1,42 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
declare(strict_types=1);
|
||||||
* PHP version 7.3
|
|
||||||
*
|
|
||||||
* @category JMSTypeParser
|
|
||||||
* @package RetailCrm\Api\Component\Serializer\Parser
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace RetailCrm\Api\Component\Serializer\Parser;
|
namespace RetailCrm\Api\Component\Serializer\Parser;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Liip\MetadataParser\Exception\InvalidTypeException;
|
use Liip\MetadataParser\Exception\InvalidTypeException;
|
||||||
|
use Liip\MetadataParser\Metadata\AbstractPropertyType;
|
||||||
use Liip\MetadataParser\Metadata\DateTimeOptions;
|
use Liip\MetadataParser\Metadata\DateTimeOptions;
|
||||||
use Liip\MetadataParser\Metadata\PropertyType;
|
use Liip\MetadataParser\Metadata\PropertyType;
|
||||||
use Liip\MetadataParser\Metadata\PropertyTypeArray;
|
|
||||||
use Liip\MetadataParser\Metadata\PropertyTypeClass;
|
use Liip\MetadataParser\Metadata\PropertyTypeClass;
|
||||||
use Liip\MetadataParser\Metadata\PropertyTypeDateTime;
|
use Liip\MetadataParser\Metadata\PropertyTypeDateTime;
|
||||||
|
use Liip\MetadataParser\Metadata\PropertyTypeIterable;
|
||||||
|
use Liip\MetadataParser\Metadata\PropertyTypeArray;
|
||||||
use Liip\MetadataParser\Metadata\PropertyTypePrimitive;
|
use Liip\MetadataParser\Metadata\PropertyTypePrimitive;
|
||||||
use Liip\MetadataParser\Metadata\PropertyTypeUnknown;
|
use Liip\MetadataParser\Metadata\PropertyTypeUnknown;
|
||||||
|
use RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type\Parser;
|
||||||
use RetailCrm\Api\Component\Serializer\Type\PropertyTypeMixed;
|
use RetailCrm\Api\Component\Serializer\Type\PropertyTypeMixed;
|
||||||
|
|
||||||
/**
|
final class JMSTypeParser
|
||||||
* Class JMSTypeParser
|
|
||||||
*
|
|
||||||
* @category JMSTypeParser
|
|
||||||
* @package RetailCrm\Api\Component\Serializer\Parser
|
|
||||||
* @license https://github.com/liip/metadata-parser/blob/master/LICENSE MIT License
|
|
||||||
* @author Liip <https://github.com/liip>
|
|
||||||
* @author Pavel Kovalenko
|
|
||||||
* @see https://github.com/liip/metadata-parser
|
|
||||||
* @internal
|
|
||||||
*
|
|
||||||
* @SuppressWarnings(PHPMD)
|
|
||||||
*/
|
|
||||||
class JMSTypeParser
|
|
||||||
{
|
{
|
||||||
private const TYPE_ARRAY = 'array';
|
private const TYPE_ARRAY = 'array';
|
||||||
|
private const TYPE_ARRAY_COLLECTION = 'ArrayCollection';
|
||||||
|
private const TYPE_DATETIME_INTERFACE = 'DateTimeInterface';
|
||||||
|
|
||||||
/**
|
/** @var \RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type\Parser */
|
||||||
* @var \RetailCrm\Api\Component\Serializer\Parser\BaseJMSParser
|
|
||||||
*/
|
|
||||||
private $jmsTypeParser;
|
private $jmsTypeParser;
|
||||||
|
|
||||||
/**
|
private $useArrayDateFormat;
|
||||||
* JMSTypeParser constructor.
|
|
||||||
*/
|
|
||||||
public function __construct()
|
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
|
public function parse(string $rawType): PropertyType
|
||||||
{
|
{
|
||||||
if ('' === $rawType) {
|
if ('' === $rawType) {
|
||||||
@ -63,12 +46,6 @@ class JMSTypeParser
|
|||||||
return $this->parseType($this->jmsTypeParser->parse($rawType));
|
return $this->parseType($this->jmsTypeParser->parse($rawType));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<mixed> $typeInfo
|
|
||||||
* @param bool $isSubType
|
|
||||||
*
|
|
||||||
* @return \Liip\MetadataParser\Metadata\PropertyType
|
|
||||||
*/
|
|
||||||
private function parseType(array $typeInfo, bool $isSubType = false): PropertyType
|
private function parseType(array $typeInfo, bool $isSubType = false): PropertyType
|
||||||
{
|
{
|
||||||
$typeInfo = array_merge(
|
$typeInfo = array_merge(
|
||||||
@ -84,13 +61,12 @@ class JMSTypeParser
|
|||||||
|
|
||||||
if (0 === \count($typeInfo['params'])) {
|
if (0 === \count($typeInfo['params'])) {
|
||||||
if (self::TYPE_ARRAY === $typeInfo['name']) {
|
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'])) {
|
if (PropertyTypePrimitive::isTypePrimitive($typeInfo['name'])) {
|
||||||
return new PropertyTypePrimitive($typeInfo['name'], $nullable);
|
return new PropertyTypePrimitive($typeInfo['name'], $nullable);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PropertyTypeDateTime::isTypeDateTime($typeInfo['name'])) {
|
if (PropertyTypeDateTime::isTypeDateTime($typeInfo['name'])) {
|
||||||
return PropertyTypeDateTime::fromDateTimeClass($typeInfo['name'], $nullable);
|
return PropertyTypeDateTime::fromDateTimeClass($typeInfo['name'], $nullable);
|
||||||
}
|
}
|
||||||
@ -102,41 +78,62 @@ class JMSTypeParser
|
|||||||
return new PropertyTypeClass($typeInfo['name'], $nullable);
|
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'])) {
|
if (1 === \count($typeInfo['params'])) {
|
||||||
return new PropertyTypeArray(
|
return self::iterableType($this->parseType($typeInfo['params'][0], true), false, $nullable, $collectionClass);
|
||||||
$this->parseType($typeInfo['params'][0], true),
|
|
||||||
false,
|
|
||||||
$nullable
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (2 === \count($typeInfo['params'])) {
|
if (2 === \count($typeInfo['params'])) {
|
||||||
return new PropertyTypeArray(
|
return self::iterableType($this->parseType($typeInfo['params'][1], true), true, $nullable, $collectionClass);
|
||||||
$this->parseType($typeInfo['params'][1], true),
|
|
||||||
true,
|
|
||||||
$nullable
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidTypeException(sprintf(
|
throw new InvalidTypeException(sprintf('JMS property type array can\'t have more than 2 parameters (%s)', var_export($typeInfo, true)));
|
||||||
'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
|
// 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(
|
return PropertyTypeDateTime::fromDateTimeClass(
|
||||||
$typeInfo['name'],
|
$className,
|
||||||
$nullable,
|
$nullable,
|
||||||
new DateTimeOptions(
|
new DateTimeOptions(
|
||||||
$typeInfo['params'][0] ?: null,
|
$serializeFormat,
|
||||||
($typeInfo['params'][1] ?? null) ?: null,
|
($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)));
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@ use RetailCrm\Api\Component\Serializer\Annotation as JMS;
|
|||||||
*
|
*
|
||||||
* @category Currency
|
* @category Currency
|
||||||
* @package RetailCrm\Api\Model\Entity\References
|
* @package RetailCrm\Api\Model\Entity\References
|
||||||
|
*
|
||||||
|
* @SuppressWarnings(PHPMD.LongVariable)
|
||||||
*/
|
*/
|
||||||
class Currency
|
class Currency
|
||||||
{
|
{
|
||||||
|
@ -18,7 +18,10 @@ if (!is_file($autoloadFile = __DIR__ . '/../vendor/autoload.php')) {
|
|||||||
$loader = require $autoloadFile;
|
$loader = require $autoloadFile;
|
||||||
$loader->add('RetailCrm\\TestUtils', __DIR__ . '/tests/utils');
|
$loader->add('RetailCrm\\TestUtils', __DIR__ . '/tests/utils');
|
||||||
$loader->add('RetailCrm\\Tests', __DIR__ . '/src');
|
$loader->add('RetailCrm\\Tests', __DIR__ . '/src');
|
||||||
|
|
||||||
|
if (method_exists(AnnotationRegistry::class, 'registerLoader')) {
|
||||||
AnnotationRegistry::registerLoader('class_exists');
|
AnnotationRegistry::registerLoader('class_exists');
|
||||||
|
}
|
||||||
|
|
||||||
if (file_exists(__DIR__ . '/../.env')) {
|
if (file_exists(__DIR__ . '/../.env')) {
|
||||||
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
|
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
|
||||||
|
Loading…
Reference in New Issue
Block a user