diff --git a/src/Rules/Doctrine/ORM/EntityColumnRule.php b/src/Rules/Doctrine/ORM/EntityColumnRule.php index b5f42bb6..5a9f5fff 100644 --- a/src/Rules/Doctrine/ORM/EntityColumnRule.php +++ b/src/Rules/Doctrine/ORM/EntityColumnRule.php @@ -9,6 +9,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ArrayType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Doctrine\DescriptorNotRegisteredException; use PHPStan\Type\Doctrine\DescriptorRegistry; use PHPStan\Type\Doctrine\ObjectMetadataResolver; @@ -21,10 +22,14 @@ use PHPStan\Type\TypehintHelper; use PHPStan\Type\TypeTraverser; use PHPStan\Type\TypeUtils; +use PHPStan\Type\UnionType; use PHPStan\Type\VerbosityLevel; use Throwable; +use function count; use function get_class; use function in_array; +use function is_array; +use function is_string; use function sprintf; /** @@ -100,6 +105,26 @@ public function processNode(Node $node, Scope $scope): array $writableToPropertyType = $descriptor->getWritableToPropertyType(); $writableToDatabaseType = $descriptor->getWritableToDatabaseType(); + if ($fieldMapping['type'] === 'enum') { + $values = $fieldMapping['options']['values'] ?? null; + if (is_array($values)) { + $enumTypes = []; + foreach ($values as $value) { + if (!is_string($value)) { + $enumTypes = []; + break; + } + + $enumTypes[] = new ConstantStringType($value); + } + + if (count($enumTypes) > 0) { + $writableToPropertyType = new UnionType($enumTypes); + $writableToDatabaseType = new UnionType($enumTypes); + } + } + } + $enumTypeString = $fieldMapping['enumType'] ?? null; if ($enumTypeString !== null) { if ($writableToDatabaseType->isArray()->no() && $writableToPropertyType->isArray()->no()) { diff --git a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php index f0a799e6..a39f6151 100644 --- a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php @@ -18,6 +18,7 @@ use PHPStan\Type\Doctrine\Descriptors\DateTimeType; use PHPStan\Type\Doctrine\Descriptors\DateType; use PHPStan\Type\Doctrine\Descriptors\DecimalType; +use PHPStan\Type\Doctrine\Descriptors\EnumType; use PHPStan\Type\Doctrine\Descriptors\IntegerType; use PHPStan\Type\Doctrine\Descriptors\JsonType; use PHPStan\Type\Doctrine\Descriptors\Ramsey\UuidTypeDescriptor; @@ -26,6 +27,7 @@ use PHPStan\Type\Doctrine\Descriptors\StringType; use PHPStan\Type\Doctrine\ObjectMetadataResolver; use function array_unshift; +use function class_exists; use function strpos; use const PHP_VERSION_ID; @@ -75,6 +77,7 @@ protected function getRule(): Rule new StringType(), new SimpleArrayType(), new UuidTypeDescriptor(FakeTestingUuidType::class), + new EnumType(), new ReflectionDescriptor(CarbonImmutableType::class, $this->createReflectionProvider(), self::getContainer()), new ReflectionDescriptor(CarbonType::class, $this->createReflectionProvider(), self::getContainer()), new ReflectionDescriptor(CustomType::class, $this->createReflectionProvider(), self::getContainer()), @@ -441,4 +444,21 @@ public function testBug306(?string $objectManagerLoader): void ]); } + /** + * @dataProvider dataObjectManagerLoader + */ + public function testBug677(?string $objectManagerLoader): void + { + if (PHP_VERSION_ID < 80100) { + self::markTestSkipped('Test requires PHP 8.1'); + } + if (!class_exists(\Doctrine\DBAL\Types\EnumType::class)) { + self::markTestSkipped('Test requires EnumType.'); + } + + $this->allowNullablePropertyForRequiredField = false; + $this->objectManagerLoader = $objectManagerLoader; + $this->analyse([__DIR__ . '/data/bug-677.php'], []); + } + } diff --git a/tests/Rules/Doctrine/ORM/data/bug-677.php b/tests/Rules/Doctrine/ORM/data/bug-677.php new file mode 100644 index 00000000..f84d1349 --- /dev/null +++ b/tests/Rules/Doctrine/ORM/data/bug-677.php @@ -0,0 +1,33 @@ += 8.1 + +namespace PHPStan\Rules\Doctrine\ORM\Bug677; + +use Doctrine\ORM\Mapping as ORM; + +#[ORM\Entity] +class MyBrokenEntity +{ + public const LOGIN_METHOD_BASIC_AUTH = 'BasicAuth'; + public const LOGIN_METHOD_SSO = 'SSO'; + public const LOGIN_METHOD_SAML = 'SAML'; + + public const LOGIN_METHODS = [ + self::LOGIN_METHOD_BASIC_AUTH, + self::LOGIN_METHOD_SSO, + self::LOGIN_METHOD_SAML, + ]; + + /** + * @var int|null + */ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: 'integer')] + private $id; + + /** + * @var self::LOGIN_METHOD_* + */ + #[ORM\Column(name: 'login_method', type: 'enum', options: ['default' => self::LOGIN_METHOD_BASIC_AUTH, 'values' => self::LOGIN_METHODS])] + private string $loginMethod = self::LOGIN_METHOD_BASIC_AUTH; +}