diff --git a/composer.json b/composer.json index 1af738a..cc9a36e 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "php-http/message-factory": "^1.0", "php-http/discovery": "^1.13", "doctrine/annotations": "^1.13|^2.0", - "liip/serializer": "2.6.*", + "liip/serializer": "2.2.* || 2.6.*", "php-http/httplug": "^2.2", "civicrm/composer-compile-plugin": "^0.18.0", "symfony/console": "^4.0|^5.0|^6.0", diff --git a/src/Component/Serializer/Generator/DeserializerGenerator.php b/src/Component/Serializer/Generator/DeserializerGenerator.php index 4bd7c28..f424f96 100644 --- a/src/Component/Serializer/Generator/DeserializerGenerator.php +++ b/src/Component/Serializer/Generator/DeserializerGenerator.php @@ -253,6 +253,15 @@ final class DeserializerGenerator ); } + private function isArrayTraversable(PropertyTypeArray $array): bool + { + if (method_exists($array, 'isCollection')) { + return $array->isCollection(); + } + + return $array->isTraversable(); + } + /** * @param array $stack */ @@ -266,13 +275,23 @@ final class DeserializerGenerator switch ($type) { case $type instanceof PropertyTypeArray: - if ($type->isTraversable()) { + if ($this->isArrayTraversable($type)) { return $this->generateCodeForArrayCollection($propertyMetadata, $type, $arrayPath, $modelPropertyPath, $stack); } return $this->generateCodeForArray($type, $arrayPath, $modelPropertyPath, $stack); case $type instanceof PropertyTypeDateTime: + if (method_exists($type, 'getDeserializeFormat')) { + $format = $type->getDeserializeFormat(); + + if (null !== $format) { + return $this->templating->renderAssignDateTimeFromFormat($type->isImmutable(), (string) $modelPropertyPath, (string) $arrayPath, $format, $type->getZone()); + } + + return $this->templating->renderAssignDateTimeToField($type->isImmutable(), (string) $modelPropertyPath, (string) $arrayPath); + } + $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()); diff --git a/src/Component/Serializer/Parser/JMSCore/Type/Parser.php b/src/Component/Serializer/Parser/JMSCore/Type/Parser.php index b6250df..d77b5f9 100644 --- a/src/Component/Serializer/Parser/JMSCore/Type/Parser.php +++ b/src/Component/Serializer/Parser/JMSCore/Type/Parser.php @@ -16,15 +16,21 @@ final class Parser implements ParserInterface */ private Lexer $lexer; + private ?Token $token = null; + + private string $input; + /** * @var bool */ private bool $root = true; - public function parse(string $string): array + public function parse(string $type): array { + $this->input = $type; + $this->lexer = new Lexer(); - $this->lexer->setInput($string); + $this->lexer->setInput($type); $this->lexer->moveNext(); return $this->visit(); @@ -33,7 +39,7 @@ final class Parser implements ParserInterface /** * @return mixed */ - private function visit() + private function visit(bool $fetchingParam = false) { $this->lexer->moveNext(); @@ -43,15 +49,44 @@ final class Parser implements ParserInterface ); } - if (Lexer::T_FLOAT === $this->lexer->token->type) { - return floatval($this->lexer->token->value); - } elseif (Lexer::T_INTEGER === $this->lexer->token->type) { - return intval($this->lexer->token->value); - } elseif (Lexer::T_NULL === $this->lexer->token->type) { + 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 && $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->lexer->token->type) { - return $this->lexer->token->value; - } elseif (Lexer::T_IDENTIFIER === $this->lexer->token->type) { + } 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)) { @@ -59,14 +94,14 @@ final class Parser implements ParserInterface } return $this->visitSimpleType(); - } elseif (!$this->root && Lexer::T_ARRAY_START === $this->lexer->token->type) { + } elseif (!$this->root && Lexer::T_ARRAY_START === $this->token->type) { return $this->visitArrayType(); } throw new SyntaxError(sprintf( 'Syntax error, unexpected "%s" (%s)', - $this->lexer->token->value, - $this->getConstant($this->lexer->token->type), + $this->token->value, + $this->getConstant($this->token->type), )); } @@ -75,7 +110,7 @@ final class Parser implements ParserInterface */ private function visitSimpleType() { - $value = $this->lexer->token->value; + $value = $this->token->value; return ['name' => $value, 'params' => []]; } @@ -83,13 +118,13 @@ final class Parser implements ParserInterface private function visitCompoundType(): array { $this->root = false; - $name = $this->lexer->token->value; + $name = $this->token->value; $this->match(Lexer::T_TYPE_START); $params = []; if (!$this->lexer->isNextToken(Lexer::T_TYPE_END)) { while (true) { - $params[] = $this->visit(); + $params[] = $this->visit(true); if ($this->lexer->isNextToken(Lexer::T_TYPE_END)) { break; @@ -139,7 +174,13 @@ final class Parser implements ParserInterface ); } - if ($this->lexer->lookahead->type === $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; @@ -147,8 +188,8 @@ final class Parser implements ParserInterface throw new SyntaxError(sprintf( 'Syntax error, unexpected "%s" (%s), expected was %s', - $this->lexer->lookahead->value, - $this->getConstant($this->lexer->lookahead->type), + $lookahead->value, + $this->getConstant($lookahead->type), $this->getConstant($token), )); } diff --git a/src/Component/Serializer/Parser/JMSCore/Type/Token.php b/src/Component/Serializer/Parser/JMSCore/Type/Token.php new file mode 100644 index 0000000..f576840 --- /dev/null +++ b/src/Component/Serializer/Parser/JMSCore/Type/Token.php @@ -0,0 +1,64 @@ +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 + * + * @readonly + */ + public int $position; + + /** + * @param string|int $value + * @param string|int $type + */ + public function __construct($value, $type, int $position) + { + $this->value = $value; + $this->type = $type; + $this->position = $position; + } + + /** @param T ...$types */ + public function isA(...$types): bool + { + return in_array($this->type, $types, true); + } +} diff --git a/src/Component/Serializer/Parser/JMSTypeParser.php b/src/Component/Serializer/Parser/JMSTypeParser.php index 8a70080..1379290 100644 --- a/src/Component/Serializer/Parser/JMSTypeParser.php +++ b/src/Component/Serializer/Parser/JMSTypeParser.php @@ -7,11 +7,13 @@ namespace RetailCrm\Api\Component\Serializer\Parser; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Liip\MetadataParser\Exception\InvalidTypeException; +use Liip\MetadataParser\Metadata\AbstractPropertyType; use Liip\MetadataParser\Metadata\DateTimeOptions; use Liip\MetadataParser\Metadata\PropertyType; use Liip\MetadataParser\Metadata\PropertyTypeClass; use Liip\MetadataParser\Metadata\PropertyTypeDateTime; use Liip\MetadataParser\Metadata\PropertyTypeIterable; +use Liip\MetadataParser\Metadata\PropertyTypeArray; use Liip\MetadataParser\Metadata\PropertyTypePrimitive; use Liip\MetadataParser\Metadata\PropertyTypeUnknown; use RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type\Parser; @@ -28,9 +30,13 @@ final class JMSTypeParser */ private Parser $jmsTypeParser; + private $useArrayDateFormat = true; + public function __construct() { $this->jmsTypeParser = new Parser(); + $this->useArrayDateFormat = null === (new \ReflectionClass(DateTimeOptions::class)) + ->getConstructor()->getParameters()[2]->getType(); } public function parse(string $rawType): PropertyType @@ -57,7 +63,7 @@ final class JMSTypeParser if (0 === \count($typeInfo['params'])) { if (self::TYPE_ARRAY === $typeInfo['name']) { - return new PropertyTypeIterable(new PropertyTypeUnknown(false), false, $nullable); + return self::iterableType(new PropertyTypeUnknown(false), false, $nullable); } if (PropertyTypePrimitive::isTypePrimitive($typeInfo['name'])) { @@ -77,10 +83,10 @@ final class JMSTypeParser $collectionClass = $this->getCollectionClass($typeInfo['name']); if (self::TYPE_ARRAY === $typeInfo['name'] || $collectionClass) { if (1 === \count($typeInfo['params'])) { - return new PropertyTypeIterable($this->parseType($typeInfo['params'][0], true), false, $nullable, $collectionClass); + return self::iterableType($this->parseType($typeInfo['params'][0], true), false, $nullable, $collectionClass); } if (2 === \count($typeInfo['params'])) { - return new PropertyTypeIterable($this->parseType($typeInfo['params'][1], true), true, $nullable, $collectionClass); + return self::iterableType($this->parseType($typeInfo['params'][1], true), true, $nullable, $collectionClass); } throw new InvalidTypeException(sprintf('JMS property type array can\'t have more than 2 parameters (%s)', var_export($typeInfo, true))); @@ -102,7 +108,7 @@ final class JMSTypeParser new DateTimeOptions( $serializeFormat, ($typeInfo['params'][1] ?? null) ?: null, - $deserializeFormats, + $this->useArrayDateFormat ? $deserializeFormats : $deserializeFormats[0], ) ); } @@ -119,4 +125,17 @@ final class JMSTypeParser 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); + } }