Skip to content

Commit

Permalink
WIP: php 7.4 support
Browse files Browse the repository at this point in the history
  • Loading branch information
Neur0toxine committed Mar 11, 2024
1 parent 8ced4dc commit f53f545
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 26 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
21 changes: 20 additions & 1 deletion src/Component/Serializer/Generator/DeserializerGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,15 @@ private function generateCodeForField(
);
}

private function isArrayTraversable(PropertyTypeArray $array): bool
{
if (method_exists($array, 'isCollection')) {
return $array->isCollection();
}

return $array->isTraversable();
}

/**
* @param array<string, positive-int> $stack
*/
Expand All @@ -266,13 +275,23 @@ private function generateInnerCodeForFieldType(

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());
Expand Down
81 changes: 61 additions & 20 deletions src/Component/Serializer/Parser/JMSCore/Type/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,21 @@ final class Parser implements ParserInterface
*/
private Lexer $lexer;

private ?Token $token = null;

Check failure on line 19 in src/Component/Serializer/Parser/JMSCore/Type/Parser.php

View workflow job for this annotation

GitHub Actions / PHPStan

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

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();
Expand All @@ -33,7 +39,7 @@ public function parse(string $string): array
/**
* @return mixed
*/
private function visit()
private function visit(bool $fetchingParam = false)
{
$this->lexer->moveNext();

Expand All @@ -43,30 +49,59 @@ private function visit()
);
}

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);

Check failure on line 74 in src/Component/Serializer/Parser/JMSCore/Type/Parser.php

View workflow job for this annotation

GitHub Actions / PHPStan

Parameter #1 $string of function strlen expects string, int|string given.
$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)) {
return $this->visitArrayType();
}

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),

Check failure on line 104 in src/Component/Serializer/Parser/JMSCore/Type/Parser.php

View workflow job for this annotation

GitHub Actions / PHPStan

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.
));
}

Expand All @@ -75,21 +110,21 @@ private function visit()
*/
private function visitSimpleType()
{
$value = $this->lexer->token->value;
$value = $this->token->value;

Check failure on line 113 in src/Component/Serializer/Parser/JMSCore/Type/Parser.php

View workflow job for this annotation

GitHub Actions / PHPStan

Cannot access property $value on RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type\Token|null.

return ['name' => $value, 'params' => []];
}

private function visitCompoundType(): array
{
$this->root = false;
$name = $this->lexer->token->value;
$name = $this->token->value;

Check failure on line 121 in src/Component/Serializer/Parser/JMSCore/Type/Parser.php

View workflow job for this annotation

GitHub Actions / PHPStan

Cannot access property $value on RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type\Token|null.
$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;
Expand Down Expand Up @@ -139,16 +174,22 @@ private function match(int $token): void
);
}

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;
}

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),

Check failure on line 192 in src/Component/Serializer/Parser/JMSCore/Type/Parser.php

View workflow job for this annotation

GitHub Actions / PHPStan

Parameter #1 $value of method RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type\Parser::getConstant() expects int, int|string|null given.
$this->getConstant($token),
));
}
Expand Down
64 changes: 64 additions & 0 deletions src/Component/Serializer/Parser/JMSCore/Type/Token.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?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

Check failure on line 15 in src/Component/Serializer/Parser/JMSCore/Type/Token.php

View workflow job for this annotation

GitHub Actions / PHPStan

Method RetailCrm\Api\Component\Serializer\Parser\JMSCore\Type\Token::fromArray() has parameter $source with no value type specified in iterable type array.
{
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
*
* @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);
}
}
27 changes: 23 additions & 4 deletions src/Component/Serializer/Parser/JMSTypeParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
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;
Expand All @@ -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
Expand All @@ -57,7 +63,7 @@ private function parseType(array $typeInfo, bool $isSubType = false): PropertyTy

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'])) {
Expand All @@ -77,10 +83,10 @@ private function parseType(array $typeInfo, bool $isSubType = false): PropertyTy
$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)));
Expand All @@ -102,7 +108,7 @@ private function parseType(array $typeInfo, bool $isSubType = false): PropertyTy
new DateTimeOptions(
$serializeFormat,
($typeInfo['params'][1] ?? null) ?: null,
$deserializeFormats,
$this->useArrayDateFormat ? $deserializeFormats : $deserializeFormats[0],
)
);
}
Expand All @@ -119,4 +125,17 @@ private function getCollectionClass(string $name): ?string
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);
}
}

0 comments on commit f53f545

Please sign in to comment.