Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php-version: ['8.0', '8.1', '8.2', '8.3', '8.4', '8.5']
php-version: ['8.1', '8.2', '8.3', '8.4', '8.5']
include:
- php-version: '8.0'
- php-version: '8.1'
composer-flags: '--prefer-stable --prefer-lowest'
steps:
- name: Check out code into the workspace
Expand Down
1 change: 1 addition & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
'php_unit_mock' => true,
'php_unit_namespaced' => true,
'php_unit_no_expectation_annotation' => true,
'php_unit_data_provider_method_order' => false,
'phpdoc_to_return_type' => true,
'static_lambda' => true,
'ternary_to_null_coalescing' => true,
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

# Version 2.x

# 2.1.0 (unreleased)

* Add support for enums
* Drop support for PHP 8.0

# 2.0.0

* Removed `PropertyTypeArray`, which is superseded by `PropertyTypeIterable`.
Expand Down
8 changes: 4 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@
"issues": "https://github.com/liip/metadata-parser/issues"
},
"require": {
"php": "^8.0",
"php": "^8.1",
"ext-json": "*",
"doctrine/annotations": "^2.0.2",
"psr/log": "^1|^2|^3"
},
"require-dev": {
"doctrine/collections": "^1.6",
"friendsofphp/php-cs-fixer": "v3.68.1",
"friendsofphp/php-cs-fixer": "^v3.94.2",
"jms/serializer": "^2.3 || ^3",
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-phpunit": "^1.0",
"phpstan/phpstan": "^2.1.44",
"phpstan/phpstan-phpunit": "^2.0.16",
"phpunit/phpunit": "^8.5.15 || ^9.5"
},
"suggest": {
Expand Down
2 changes: 0 additions & 2 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,3 @@ parameters:
- PHP_VERSION_ID
paths:
- src/
excludePaths:
- src/ModelParser/JMSParserLegacy.php
4 changes: 3 additions & 1 deletion phpstan.tests.neon
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ parameters:
paths:
- tests/
excludePaths:
- tests/ModelParser/JMSParserTestLegacy.php
# Excluded until the minimum version required from jms/serializer is set to >= 3.31.0
- tests/ModelParser/Model/ClassUsingUnionDiscriminator.php
- tests/ModelParser/Model/ClassUsingUnionTyping.php
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to exclude these files?

Copy link
Copy Markdown
Contributor Author

@Spea Spea Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UnionDiscriminator from JMS exists since version 3.31.0, but this library supports ^2.3 || ^3 of the jms/serializer depencency. So when installing the composer dependencies in the CI workflow with the options --prefer-stable --prefer-lowest, it will install version 3.19.0, which does not include the UnionDiscriminator and thus phpstan would complain about it.

Since those fixture files are not really critical, I thought to just exclude them, but maybe it could also make sense to adjust the minimum required version for jms/serializer?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, thanks. can you add a comment that we exclude these until only a new enough version of jms serializer is supported. thats good enough imho. the comment will be useful to know when we can stop excluding.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - I amended the change to the original commit bfdd9ae

# looks like phpstan php parser has not implemented union types yet
- tests/ModelParser/Fixtures/IntersectionTypeDeclarationModel.php
11 changes: 6 additions & 5 deletions src/Metadata/ClassDiscriminatorMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,7 @@ public function setClassMetadataList(array $classMetadataList): void
{
$this->classMetadataList = [];
foreach ($classMetadataList as $classMetadata) {
if (!$classMetadata instanceof ClassMetadata) {
throw new \InvalidArgumentException(\sprintf('Expected instance of %s', ClassMetadata::class));
}

$this->classMetadataList[$classMetadata->getClassName()] = $classMetadata;
$this->addClassMetadata($classMetadata);
}
}

Expand All @@ -50,4 +46,9 @@ public function getMetadataForClass(string $className): ?ClassMetadata
{
return $this->classMetadataList[$className] ?? null;
}

private function addClassMetadata(ClassMetadata $classMetadata): void
{
$this->classMetadataList[$classMetadata->getClassName()] = $classMetadata;
}
}
1 change: 1 addition & 0 deletions src/Metadata/ClassMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ final class ClassMetadata implements \JsonSerializable
public function __construct(string $className, array $properties, array $constructorParameters = [], array $postDeserializeMethods = [], ?ClassDiscriminatorMetadata $discriminatorMetadata = null)
{
\assert(array_reduce($constructorParameters, static function (bool $carry, $parameter): bool {
/* @phpstan-ignore instanceof.alwaysTrue */
return $carry && $parameter instanceof ParameterMetadata;
}, true));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can only be removed if we trust phpdoc (aka phpstan is running). as this class is not internal i'd like to keep the check.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it will also work fine without phpstan running, as the constructor itself does a foreach over the properties and then calls $this->addProperty($property), and this method is typed to addProperty(PropertyMetadata $property), so PHP itself will throw an error if the type is wrong.

Do you still want me to change it back?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is about the $constructorParameters, not about the $properties. the $constructorParameters are assigned to an array 2 lines after this. i think we should add the check back to make sure the error is reported in the constructor.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it back, but I also had to add a phpstan-ignore here, as otherwise it would result in this error:

 ------ ----------------------------------------------------------------------------------------------------------------------------------------------------
  Line   Metadata/ClassMetadata.php
 ------ ----------------------------------------------------------------------------------------------------------------------------------------------------
  51     Instanceof between Liip\MetadataParser\Metadata\ParameterMetadata and Liip\MetadataParser\Metadata\ParameterMetadata will always evaluate to true.
         🪪  instanceof.alwaysTrue
 ------ ----------------------------------------------------------------------------------------------------------------------------------------------------


Expand Down
7 changes: 5 additions & 2 deletions src/Metadata/DateTimeOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ final class DateTimeOptions implements \JsonSerializable
/**
* Use if different formats should be used for parsing dates than for generating dates.
*
* @var string[]|null
* @var list<string>|null
*/
private $deserializeFormats;

/**
* @note Passing a string for $deserializeFormats is deprecated, please pass an array instead
*
* @param string[]|null $deserializeFormats
* @param list<string>|null $deserializeFormats
*/
public function __construct(?string $format, ?string $zone, ?array $deserializeFormats)
{
Expand All @@ -48,6 +48,9 @@ public function getZone(): ?string
return $this->zone;
}

/**
* @return list<string>|null
*/
public function getDeserializeFormats(): ?array
{
return $this->deserializeFormats;
Expand Down
3 changes: 3 additions & 0 deletions src/Metadata/PropertyTypeDateTime.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ public function getZone(): ?string
return null;
}

/**
* @return list<string>|null
*/
public function getDeserializeFormats(): ?array
{
if ($this->dateTimeOptions) {
Expand Down
88 changes: 88 additions & 0 deletions src/Metadata/PropertyTypeEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

declare(strict_types=1);

namespace Liip\MetadataParser\Metadata;

use Liip\MetadataParser\Exception\InvalidTypeException;

final class PropertyTypeEnum extends AbstractPropertyType
{
private string $className;
private ?string $backingType;

private ?SerializationMode $serializationMode;

public function __construct(string $className, bool $nullable, ?SerializationMode $serializationMode = null)
{
parent::__construct($nullable);
if (!enum_exists($className)) {
throw new InvalidTypeException(\sprintf('Given type "%s" is not a PHP 8.1 enum', $className));
}

$this->className = $className;
$this->backingType = null;
$this->serializationMode = $serializationMode;

$reflEnum = new \ReflectionEnum($className);
$backingType = $reflEnum->getBackingType();
if ($backingType instanceof \ReflectionNamedType) {
$this->backingType = $backingType->getName();
}
}

public function __toString(): string
{
return $this->className.parent::__toString();
}

public function getClassName(): string
{
return $this->className;
}

public function getBackingType(): ?string
{
return $this->backingType;
}

public function isBackedEnum(): bool
{
return null !== $this->backingType;
}

public function getSerializationMode(): ?SerializationMode
{
return $this->serializationMode;
}

public function shouldSerializeAsValue(): bool
{
return $this->isBackedEnum() && SerializationMode::Name !== $this->serializationMode;
}

public function merge(PropertyType $other): PropertyType
{
$nullable = $this->isNullable() && $other->isNullable();

if ($other instanceof PropertyTypeUnknown) {
return new self($this->className, $nullable, $this->serializationMode);
}

if (!$other instanceof self) {
throw new \UnexpectedValueException(\sprintf('Can\'t merge type %s with %s, they must be the same or unknown', self::class, \get_class($other)));
}

if ($this->getClassName() !== $other->getClassName()) {
throw new \UnexpectedValueException(\sprintf('Can\'t merge type %s with %s, they must be equal', self::class, \get_class($other)));
}

if (null !== $this->serializationMode && null !== $other->getSerializationMode() && $this->serializationMode !== $other->getSerializationMode()) {
throw new \UnexpectedValueException(\sprintf('Can\'t merge type %s with conflicting serialization modes "%s" and "%s"', self::class, $this->serializationMode->value, $other->getSerializationMode()->value));
}

$serializationMode = $this->serializationMode ?? $other->getSerializationMode();

return new self($this->className, $nullable, $serializationMode);
}
}
4 changes: 2 additions & 2 deletions src/Metadata/PropertyTypeUnion.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ private function reorderTypes(array $types): array
$secondTypeName = $second->getTypeName();
}

$firstOrder = $order[$firstTypeName] ?? self::DEFAULT_ORDER;
$secondOrder = $order[$secondTypeName] ?? self::DEFAULT_ORDER;
$firstOrder = $order[$firstTypeName ?? ''] ?? self::DEFAULT_ORDER;
$secondOrder = $order[$secondTypeName ?? ''] ?? self::DEFAULT_ORDER;

return $firstOrder <=> $secondOrder;
});
Expand Down
11 changes: 11 additions & 0 deletions src/Metadata/SerializationMode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Liip\MetadataParser\Metadata;

enum SerializationMode: string
{
case Value = 'value';
case Name = 'name';
}
6 changes: 3 additions & 3 deletions src/ModelParser/JMSParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ private function parsePropertyAttributes(RawClassMetadata $classMetadata, \Refle
throw ParseException::propertyTypeNameNull((string) $classMetadata, (string) $property);
}
try {
$type = $this->jmsTypeParser->parse($attribute->name);
$type = $this->jmsTypeParser->parse($attribute->name, $reflection);
} catch (InvalidTypeException $e) {
throw ParseException::propertyTypeError((string) $classMetadata, (string) $property, $e);
}
Expand Down Expand Up @@ -274,15 +274,15 @@ private function parsePropertyAttributes(RawClassMetadata $classMetadata, \Refle
case $attribute instanceof MaxDepth:
$property->setMaxDepth($attribute->depth);
break;
case $attribute instanceof UnionDiscriminator:
case class_exists(UnionDiscriminator::class) && $attribute instanceof UnionDiscriminator:
$types = [];
$isNullable = $this->isNullable($reflection);
if ($isNullable) {
$types[] = new PropertyTypePrimitive('null', true);
}

foreach ($attribute->map as $value) {
$types[] = $this->jmsTypeParser->parse($value, true);
$types[] = $this->jmsTypeParser->parse($value, $reflection, true);
}

$type = new PropertyTypeUnion($types, $isNullable);
Expand Down
Loading
Loading