Skip to content

Commit

Permalink
ReflectionDescriptor: deduce database internal type based on parent
Browse files Browse the repository at this point in the history
  • Loading branch information
janedbal committed Jun 25, 2024
1 parent 4a1ece8 commit 012cf14
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 7 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:
Expand Down
11 changes: 11 additions & 0 deletions src/Type/Doctrine/DefaultDescriptorRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}

}
36 changes: 33 additions & 3 deletions src/Type/Doctrine/Descriptors/ReflectionDescriptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -13,19 +17,27 @@
class ReflectionDescriptor implements DoctrineTypeDescriptor
{

/** @var class-string<\Doctrine\DBAL\Types\Type> */
/** @var class-string<DbalType> */
private $type;

/** @var ReflectionProvider */
private $reflectionProvider;

/** @var Container */
private $container;

/**
* @param class-string<\Doctrine\DBAL\Types\Type> $type
* @param class-string<DbalType> $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
Expand Down Expand Up @@ -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();
}

Expand Down
8 changes: 4 additions & 4 deletions tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,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,
Expand Down

0 comments on commit 012cf14

Please sign in to comment.