Skip to content

Commit 012cf14

Browse files
authored
ReflectionDescriptor: deduce database internal type based on parent
1 parent 4a1ece8 commit 012cf14

File tree

4 files changed

+52
-7
lines changed

4 files changed

+52
-7
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ Type descriptors don't have to deal with nullable types, as these are transparen
195195

196196
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.
197197

198+
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)`).
199+
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`.
200+
If you extend only `Doctrine\DBAL\Types\Type`, you should use custom descriptor and optionally implement even `DoctrineTypeDriverAwareDescriptor` to provide driver-specific resolution.
201+
198202
### Registering type descriptors
199203

200204
When you write a custom type descriptor, you have to let PHPStan know about it. Add something like this into your `phpstan.neon`:

src/Type/Doctrine/DefaultDescriptorRegistry.php

+11
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,15 @@ public function get(string $type): DoctrineTypeDescriptor
3636
return $this->descriptors[$typeClass];
3737
}
3838

39+
/**
40+
* @throws DescriptorNotRegisteredException
41+
*/
42+
public function getByClassName(string $className): DoctrineTypeDescriptor
43+
{
44+
if (!isset($this->descriptors[$className])) {
45+
throw new DescriptorNotRegisteredException();
46+
}
47+
return $this->descriptors[$className];
48+
}
49+
3950
}

src/Type/Doctrine/Descriptors/ReflectionDescriptor.php

+33-3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33
namespace PHPStan\Type\Doctrine\Descriptors;
44

55
use Doctrine\DBAL\Platforms\AbstractPlatform;
6+
use Doctrine\DBAL\Types\Type as DbalType;
7+
use PHPStan\DependencyInjection\Container;
68
use PHPStan\Reflection\ParametersAcceptorSelector;
79
use PHPStan\Reflection\ReflectionProvider;
10+
use PHPStan\Type\Doctrine\DefaultDescriptorRegistry;
11+
use PHPStan\Type\Doctrine\DescriptorNotRegisteredException;
812
use PHPStan\Type\MixedType;
913
use PHPStan\Type\ObjectType;
1014
use PHPStan\Type\Type;
@@ -13,19 +17,27 @@
1317
class ReflectionDescriptor implements DoctrineTypeDescriptor
1418
{
1519

16-
/** @var class-string<\Doctrine\DBAL\Types\Type> */
20+
/** @var class-string<DbalType> */
1721
private $type;
1822

1923
/** @var ReflectionProvider */
2024
private $reflectionProvider;
2125

26+
/** @var Container */
27+
private $container;
28+
2229
/**
23-
* @param class-string<\Doctrine\DBAL\Types\Type> $type
30+
* @param class-string<DbalType> $type
2431
*/
25-
public function __construct(string $type, ReflectionProvider $reflectionProvider)
32+
public function __construct(
33+
string $type,
34+
ReflectionProvider $reflectionProvider,
35+
Container $container
36+
)
2637
{
2738
$this->type = $type;
2839
$this->reflectionProvider = $reflectionProvider;
40+
$this->container = $container;
2941
}
3042

3143
public function getType(): string
@@ -57,6 +69,24 @@ public function getWritableToDatabaseType(): Type
5769

5870
public function getDatabaseInternalType(): Type
5971
{
72+
if (!$this->reflectionProvider->hasClass($this->type)) {
73+
return new MixedType();
74+
}
75+
76+
$registry = $this->container->getByType(DefaultDescriptorRegistry::class);
77+
$parents = $this->reflectionProvider->getClass($this->type)->getParentClassesNames();
78+
79+
foreach ($parents as $dbalTypeParentClass) {
80+
try {
81+
// this assumes that if somebody inherits from DecimalType,
82+
// the real database type remains decimal and we can reuse its descriptor
83+
return $registry->getByClassName($dbalTypeParentClass)->getDatabaseInternalType();
84+
85+
} catch (DescriptorNotRegisteredException $e) {
86+
continue;
87+
}
88+
}
89+
6090
return new MixedType();
6191
}
6292

tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php

+4-4
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ protected function getRule(): Rule
7777
new StringType(),
7878
new SimpleArrayType(),
7979
new UuidTypeDescriptor(FakeTestingUuidType::class),
80-
new ReflectionDescriptor(CarbonImmutableType::class, $this->createBroker()),
81-
new ReflectionDescriptor(CarbonType::class, $this->createBroker()),
82-
new ReflectionDescriptor(CustomType::class, $this->createBroker()),
83-
new ReflectionDescriptor(CustomNumericType::class, $this->createBroker()),
80+
new ReflectionDescriptor(CarbonImmutableType::class, $this->createBroker(), self::getContainer()),
81+
new ReflectionDescriptor(CarbonType::class, $this->createBroker(), self::getContainer()),
82+
new ReflectionDescriptor(CustomType::class, $this->createBroker(), self::getContainer()),
83+
new ReflectionDescriptor(CustomNumericType::class, $this->createBroker(), self::getContainer()),
8484
]),
8585
$this->createReflectionProvider(),
8686
true,

0 commit comments

Comments
 (0)