diff --git a/README.md b/README.md index a93658c5..9ff192b1 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,10 @@ Type descriptors don't have to deal with nullable types, as these are transparen If your custom type's `convertToPHPValue()` and `convertToDatabaseValue()` methods have proper typehints, you don't have to write your own descriptor for it. The `PHPStan\Type\Doctrine\Descriptors\ReflectionDescriptor` can analyse the typehints and do the rest for you. +If parent of your type is one of the Doctrine's non-abstract ones, `ReflectionDescriptor` will reuse its descriptor even for expression resolution (e.g. `AVG(t.cost)`). +For example, if you extend `Doctrine\DBAL\Types\DecimalType`, it will know that sqlite fetches that as `float|int` and other drivers as `numeric-string`. +If you extend only `Doctrine\DBAL\Types\Type`, you should use custom descriptor and optionally implement even `DoctrineTypeDriverAwareDescriptor` to provide driver-specific resolution. + ### Registering type descriptors When you write a custom type descriptor, you have to let PHPStan know about it. Add something like this into your `phpstan.neon`: diff --git a/src/Type/Doctrine/DefaultDescriptorRegistry.php b/src/Type/Doctrine/DefaultDescriptorRegistry.php index 48886caa..2fc81131 100644 --- a/src/Type/Doctrine/DefaultDescriptorRegistry.php +++ b/src/Type/Doctrine/DefaultDescriptorRegistry.php @@ -36,4 +36,15 @@ public function get(string $type): DoctrineTypeDescriptor return $this->descriptors[$typeClass]; } + /** + * @throws DescriptorNotRegisteredException + */ + public function getByClassName(string $className): DoctrineTypeDescriptor + { + if (!isset($this->descriptors[$className])) { + throw new DescriptorNotRegisteredException(); + } + return $this->descriptors[$className]; + } + } diff --git a/src/Type/Doctrine/Descriptors/ReflectionDescriptor.php b/src/Type/Doctrine/Descriptors/ReflectionDescriptor.php index 283d9506..82c44482 100644 --- a/src/Type/Doctrine/Descriptors/ReflectionDescriptor.php +++ b/src/Type/Doctrine/Descriptors/ReflectionDescriptor.php @@ -3,8 +3,12 @@ namespace PHPStan\Type\Doctrine\Descriptors; use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Types\Type as DbalType; +use PHPStan\DependencyInjection\Container; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Reflection\ReflectionProvider; +use PHPStan\Type\Doctrine\DefaultDescriptorRegistry; +use PHPStan\Type\Doctrine\DescriptorNotRegisteredException; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; @@ -13,19 +17,27 @@ class ReflectionDescriptor implements DoctrineTypeDescriptor { - /** @var class-string<\Doctrine\DBAL\Types\Type> */ + /** @var class-string */ private $type; /** @var ReflectionProvider */ private $reflectionProvider; + /** @var Container */ + private $container; + /** - * @param class-string<\Doctrine\DBAL\Types\Type> $type + * @param class-string $type */ - public function __construct(string $type, ReflectionProvider $reflectionProvider) + public function __construct( + string $type, + ReflectionProvider $reflectionProvider, + Container $container + ) { $this->type = $type; $this->reflectionProvider = $reflectionProvider; + $this->container = $container; } public function getType(): string @@ -57,6 +69,24 @@ public function getWritableToDatabaseType(): Type public function getDatabaseInternalType(): Type { + if (!$this->reflectionProvider->hasClass($this->type)) { + return new MixedType(); + } + + $registry = $this->container->getByType(DefaultDescriptorRegistry::class); + $parents = $this->reflectionProvider->getClass($this->type)->getParentClassesNames(); + + foreach ($parents as $dbalTypeParentClass) { + try { + // this assumes that if somebody inherits from DecimalType, + // the real database type remains decimal and we can reuse its descriptor + return $registry->getByClassName($dbalTypeParentClass)->getDatabaseInternalType(); + + } catch (DescriptorNotRegisteredException $e) { + continue; + } + } + return new MixedType(); } diff --git a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php index 57561ef4..46d455a4 100644 --- a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php @@ -76,10 +76,10 @@ protected function getRule(): Rule new StringType(), new SimpleArrayType(), new UuidTypeDescriptor(FakeTestingUuidType::class), - new ReflectionDescriptor(CarbonImmutableType::class, $this->createBroker()), - new ReflectionDescriptor(CarbonType::class, $this->createBroker()), - new ReflectionDescriptor(CustomType::class, $this->createBroker()), - new ReflectionDescriptor(CustomNumericType::class, $this->createBroker()), + new ReflectionDescriptor(CarbonImmutableType::class, $this->createBroker(), self::getContainer()), + new ReflectionDescriptor(CarbonType::class, $this->createBroker(), self::getContainer()), + new ReflectionDescriptor(CustomType::class, $this->createBroker(), self::getContainer()), + new ReflectionDescriptor(CustomNumericType::class, $this->createBroker(), self::getContainer()), ]), $this->createReflectionProvider(), true,