Skip to content

Commit

Permalink
Fix support for enumType on array fields
Browse files Browse the repository at this point in the history
  • Loading branch information
stof authored and ondrejmirtes committed Aug 23, 2024
1 parent a9bb990 commit 4d17bed
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 31 deletions.
66 changes: 50 additions & 16 deletions src/Rules/Doctrine/ORM/EntityColumnRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypehintHelper;
use PHPStan\Type\TypeTraverser;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\VerbosityLevel;
use Throwable;
use function get_class;
Expand Down Expand Up @@ -115,25 +116,58 @@ public function processNode(Node $node, Scope $scope): array

$enumTypeString = $fieldMapping['enumType'] ?? null;
if ($enumTypeString !== null) {
if ($this->reflectionProvider->hasClass($enumTypeString)) {
$enumReflection = $this->reflectionProvider->getClass($enumTypeString);
$backedEnumType = $enumReflection->getBackedEnumType();
if ($backedEnumType !== null) {
if (!$backedEnumType->equals($writableToDatabaseType) || !$backedEnumType->equals($writableToPropertyType)) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Property %s::$%s type mapping mismatch: backing type %s of enum %s does not match database type %s.',
$className,
$propertyName,
$backedEnumType->describe(VerbosityLevel::typeOnly()),
$enumReflection->getDisplayName(),
$writableToDatabaseType->describe(VerbosityLevel::typeOnly())
))->identifier('doctrine.enumType')->build();
if ($writableToDatabaseType->isArray()->no() && $writableToPropertyType->isArray()->no()) {
if ($this->reflectionProvider->hasClass($enumTypeString)) {
$enumReflection = $this->reflectionProvider->getClass($enumTypeString);
$backedEnumType = $enumReflection->getBackedEnumType();
if ($backedEnumType !== null) {
if (!$backedEnumType->equals($writableToDatabaseType) || !$backedEnumType->equals($writableToPropertyType)) {
$errors[] = RuleErrorBuilder::message(sprintf(
'Property %s::$%s type mapping mismatch: backing type %s of enum %s does not match database type %s.',
$className,
$propertyName,
$backedEnumType->describe(VerbosityLevel::typeOnly()),
$enumReflection->getDisplayName(),
$writableToDatabaseType->describe(VerbosityLevel::typeOnly())
))->identifier('doctrine.enumType')->build();
}
}
}
$enumType = new ObjectType($enumTypeString);
$writableToPropertyType = $enumType;
$writableToDatabaseType = $enumType;
} else {
$enumType = new ObjectType($enumTypeString);
if ($this->reflectionProvider->hasClass($enumTypeString)) {
$enumReflection = $this->reflectionProvider->getClass($enumTypeString);
$backedEnumType = $enumReflection->getBackedEnumType();
if ($backedEnumType !== null) {
if (!$backedEnumType->equals($writableToDatabaseType->getIterableValueType()) || !$backedEnumType->equals($writableToPropertyType->getIterableValueType())) {
$errors[] = RuleErrorBuilder::message(
sprintf(
'Property %s::$%s type mapping mismatch: backing type %s of enum %s does not match value type %s of the database type %s.',
$className,
$propertyName,
$backedEnumType->describe(VerbosityLevel::typeOnly()),
$enumReflection->getDisplayName(),
$writableToDatabaseType->getIterableValueType()->describe(VerbosityLevel::typeOnly()),
$writableToDatabaseType->describe(VerbosityLevel::typeOnly())
)
)->identifier('doctrine.enumType')->build();
}
}
}

$writableToPropertyType = TypeCombinator::intersect(new ArrayType(
$writableToPropertyType->getIterableKeyType(),
$enumType
), ...TypeUtils::getAccessoryTypes($writableToPropertyType));
$writableToDatabaseType = TypeCombinator::intersect(new ArrayType(
$writableToDatabaseType->getIterableKeyType(),
$enumType
), ...TypeUtils::getAccessoryTypes($writableToDatabaseType));

}
$enumType = new ObjectType($enumTypeString);
$writableToPropertyType = $enumType;
$writableToDatabaseType = $enumType;
}

$identifiers = [];
Expand Down
35 changes: 24 additions & 11 deletions src/Type/Doctrine/Query/QueryResultTypeWalker.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use PHPStan\ShouldNotHappenException;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Accessory\AccessoryNumericStringType;
use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantFloatType;
Expand All @@ -39,6 +40,7 @@
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeTraverser;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\UnionType;
use function array_key_exists;
use function array_map;
Expand Down Expand Up @@ -2009,17 +2011,28 @@ private function getTypeOfField(ClassMetadata $class, string $fieldName): array
/** @param ?class-string<BackedEnum> $enumType */
private function resolveDoctrineType(string $typeName, ?string $enumType = null, bool $nullable = false): Type
{
if ($enumType !== null) {
$type = new ObjectType($enumType);
} else {
try {
$type = $this->descriptorRegistry
->get($typeName)
->getWritableToPropertyType();
if ($type instanceof NeverType) {
$type = new MixedType();
try {
$type = $this->descriptorRegistry
->get($typeName)
->getWritableToPropertyType();

if ($enumType !== null) {
if ($type->isArray()->no()) {
$type = new ObjectType($enumType);
} else {
$type = TypeCombinator::intersect(new ArrayType(
$type->getIterableKeyType(),
new ObjectType($enumType)
), ...TypeUtils::getAccessoryTypes($type));
}
} catch (DescriptorNotRegisteredException $e) {
}
if ($type instanceof NeverType) {
$type = new MixedType();
}
} catch (DescriptorNotRegisteredException $e) {
if ($enumType !== null) {
$type = new ObjectType($enumType);
} else {
$type = new MixedType();
}
}
Expand All @@ -2028,7 +2041,7 @@ private function resolveDoctrineType(string $typeName, ?string $enumType = null,
$type = TypeCombinator::addNull($type);
}

return $type;
return $type;
}

/** @param ?class-string<BackedEnum> $enumType */
Expand Down
18 changes: 15 additions & 3 deletions tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -391,15 +391,27 @@ public function testEnumType(?string $objectManagerLoader): void
$this->analyse([__DIR__ . '/data-attributes/enum-type.php'], [
[
'Property PHPStan\Rules\Doctrine\ORMAttributes\Foo::$type2 type mapping mismatch: database can contain PHPStan\Rules\Doctrine\ORMAttributes\FooEnum but property expects PHPStan\Rules\Doctrine\ORMAttributes\BarEnum.',
35,
42,
],
[
'Property PHPStan\Rules\Doctrine\ORMAttributes\Foo::$type2 type mapping mismatch: property can contain PHPStan\Rules\Doctrine\ORMAttributes\BarEnum but database expects PHPStan\Rules\Doctrine\ORMAttributes\FooEnum.',
35,
42,
],
[
'Property PHPStan\Rules\Doctrine\ORMAttributes\Foo::$type3 type mapping mismatch: backing type string of enum PHPStan\Rules\Doctrine\ORMAttributes\FooEnum does not match database type int.',
38,
45,
],
[
'Property PHPStan\Rules\Doctrine\ORMAttributes\Foo::$type5 type mapping mismatch: database can contain array<int, PHPStan\Rules\Doctrine\ORMAttributes\FooEnum> but property expects PHPStan\Rules\Doctrine\ORMAttributes\FooEnum.',
51,
],
[
'Property PHPStan\Rules\Doctrine\ORMAttributes\Foo::$type5 type mapping mismatch: property can contain PHPStan\Rules\Doctrine\ORMAttributes\FooEnum but database expects array<PHPStan\Rules\Doctrine\ORMAttributes\FooEnum>.',
51,
],
[
'Property PHPStan\Rules\Doctrine\ORMAttributes\Foo::$type7 type mapping mismatch: backing type int of enum PHPStan\Rules\Doctrine\ORMAttributes\BazEnum does not match value type string of the database type array<string>.',
63,
],
]);
}
Expand Down
21 changes: 21 additions & 0 deletions tests/Rules/Doctrine/ORM/data-attributes/enum-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ enum BarEnum: string {

}

enum BazEnum: int {

case ONE = 1;
case TWO = 2;

}

#[ORM\Entity]
class Foo
{
Expand All @@ -40,4 +47,18 @@ class Foo
#[ORM\Column]
public FooEnum $type4;

#[ORM\Column(type: "simple_array", enumType: FooEnum::class)]
public FooEnum $type5;

/**
* @var list<FooEnum>
*/
#[ORM\Column(type: "simple_array", enumType: FooEnum::class)]
public array $type6;

/**
* @var list<BazEnum>
*/
#[ORM\Column(type: "simple_array", enumType: BazEnum::class)]
public array $type7;
}
6 changes: 5 additions & 1 deletion tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
use PHPStan\Doctrine\Driver\DriverDetector;
use PHPStan\Php\PhpVersion;
use PHPStan\Testing\PHPStanTestCase;
use PHPStan\Type\Accessory\AccessoryArrayListType;
use PHPStan\Type\Accessory\AccessoryNumericStringType;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
use PHPStan\Type\Constant\ConstantFloatType;
use PHPStan\Type\Constant\ConstantIntegerType;
Expand Down Expand Up @@ -181,6 +183,7 @@ public static function setUpBeforeClass(): void
$entityWithEnum->stringEnumColumn = StringEnum::A;
$entityWithEnum->intEnumColumn = IntEnum::A;
$entityWithEnum->intEnumOnStringColumn = IntEnum::A;
$entityWithEnum->stringEnumListColumn = [StringEnum::A, StringEnum::B];
$em->persist($entityWithEnum);
}

Expand Down Expand Up @@ -1499,9 +1502,10 @@ private function yieldConditionalDataset(): iterable
$this->constantArray([
[new ConstantStringType('stringEnumColumn'), new ObjectType(StringEnum::class)],
[new ConstantStringType('intEnumColumn'), new ObjectType(IntEnum::class)],
[new ConstantStringType('stringEnumListColumn'), AccessoryArrayListType::intersectWith(new ArrayType(new IntegerType(), new ObjectType(StringEnum::class)))],
]),
'
SELECT e.stringEnumColumn, e.intEnumColumn
SELECT e.stringEnumColumn, e.intEnumColumn, e.stringEnumListColumn
FROM QueryResult\EntitiesEnum\EntityWithEnum e
',
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,10 @@ class EntityWithEnum
* @Column(type="string", enumType="QueryResult\EntitiesEnum\IntEnum")
*/
public $intEnumOnStringColumn;

/**
* @var list<StringEnum>
* @Column(type="simple_array", enumType="QueryResult\EntitiesEnum\StringEnum")
*/
public $stringEnumListColumn;
}

0 comments on commit 4d17bed

Please sign in to comment.