Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit adf1826

Browse files
Add missing descriptors for SmallFloatType and EnumType
1 parent a1a9efb commit adf1826

File tree

10 files changed

+244
-17
lines changed

10 files changed

+244
-17
lines changed

‎extension.neon‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,9 @@ services:
353353
-
354354
class: PHPStan\Type\Doctrine\Descriptors\DecimalType
355355
tags: [phpstan.doctrine.typeDescriptor]
356+
-
357+
class: PHPStan\Type\Doctrine\Descriptors\EnumType
358+
tags: [phpstan.doctrine.typeDescriptor]
356359
-
357360
class: PHPStan\Type\Doctrine\Descriptors\FloatType
358361
tags: [phpstan.doctrine.typeDescriptor]
@@ -374,6 +377,9 @@ services:
374377
-
375378
class: PHPStan\Type\Doctrine\Descriptors\SimpleArrayType
376379
tags: [phpstan.doctrine.typeDescriptor]
380+
-
381+
class: PHPStan\Type\Doctrine\Descriptors\SmallFloatType
382+
tags: [phpstan.doctrine.typeDescriptor]
377383
-
378384
class: PHPStan\Type\Doctrine\Descriptors\SmallIntType
379385
tags: [phpstan.doctrine.typeDescriptor]

‎phpstan-baseline-dbal-4.neon‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
parameters:
2+
ignoreErrors:
3+
-
4+
message: '#^Class Doctrine\\DBAL\\Types\\EnumType not found\.$#'
5+
identifier: class.notFound
6+
count: 1
7+
path: src/Type/Doctrine/Descriptors/EnumType.php
8+
9+
-
10+
message: '#^Method PHPStan\\Type\\Doctrine\\Descriptors\\EnumType\:\:getType\(\) should return class\-string\<Doctrine\\DBAL\\Types\\Type\> but returns string\.$#'
11+
identifier: return.type
12+
count: 1
13+
path: src/Type/Doctrine/Descriptors/EnumType.php
14+
15+
-
16+
message: '#^Class Doctrine\\DBAL\\Types\\SmallFloatType not found\.$#'
17+
identifier: class.notFound
18+
count: 1
19+
path: src/Type/Doctrine/Descriptors/SmallFloatType.php
20+
21+
-
22+
message: '#^Method PHPStan\\Type\\Doctrine\\Descriptors\\SmallFloatType\:\:getType\(\) should return class\-string\<Doctrine\\DBAL\\Types\\Type\> but returns string\.$#'
23+
identifier: return.type
24+
count: 1
25+
path: src/Type/Doctrine/Descriptors/SmallFloatType.php
26+
27+
-
28+
message: '#^Class Doctrine\\DBAL\\Types\\EnumType not found\.$#'
29+
identifier: class.notFound
30+
count: 1
31+
path: src/Type/Doctrine/Query/QueryResultTypeWalker.php
32+
33+

‎phpstan-baseline.neon‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,3 +317,9 @@ parameters:
317317
identifier: new.deprecatedClass
318318
count: 1
319319
path: tests/Type/Doctrine/DBAL/pdo.php
320+
321+
-
322+
message: '#^Parameter references internal interface Doctrine\\ORM\\Query\\AST\\Phase2OptimizableConditional in its type\.$#'
323+
identifier: parameter.internalInterface
324+
count: 2
325+
path: src/Type/Doctrine/Query/QueryResultTypeWalker.php

‎phpstan.neon‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ includes:
44
- phpstan-baseline.neon
55
- phpstan-baseline-deprecations.neon
66
- phpstan-baseline-dbal-3.neon
7+
- phpstan-baseline-dbal-4.neon
78
- compatibility/orm-3-baseline.php
89
- vendor/phpstan/phpstan-strict-rules/rules.neon
910
- vendor/phpstan/phpstan-deprecation-rules/rules.neon
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Doctrine\Descriptors;
4+
5+
use PHPStan\Type\StringType;
6+
use PHPStan\Type\Type;
7+
8+
class EnumType implements DoctrineTypeDescriptor
9+
{
10+
11+
public function getType(): string
12+
{
13+
return \Doctrine\DBAL\Types\EnumType::class;
14+
}
15+
16+
public function getWritableToPropertyType(): Type
17+
{
18+
return new StringType();
19+
}
20+
21+
public function getWritableToDatabaseType(): Type
22+
{
23+
return new StringType();
24+
}
25+
26+
public function getDatabaseInternalType(): Type
27+
{
28+
return new StringType();
29+
}
30+
31+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Doctrine\Descriptors;
4+
5+
class SmallFloatType extends FloatType
6+
{
7+
8+
public function getType(): string
9+
{
10+
return \Doctrine\DBAL\Types\SmallFloatType::class;
11+
}
12+
13+
}

‎src/Type/Doctrine/Query/QueryResultTypeWalker.php‎

Lines changed: 74 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Type\Doctrine\Query;
44

55
use BackedEnum;
6+
use Doctrine\DBAL\Types\EnumType as DbalEnumType;
67
use Doctrine\DBAL\Types\StringType as DbalStringType;
78
use Doctrine\DBAL\Types\Type as DbalType;
89
use Doctrine\ORM\EntityManagerInterface;
@@ -26,6 +27,7 @@
2627
use PHPStan\Type\Constant\ConstantBooleanType;
2728
use PHPStan\Type\Constant\ConstantFloatType;
2829
use PHPStan\Type\Constant\ConstantIntegerType;
30+
use PHPStan\Type\Constant\ConstantStringType;
2931
use PHPStan\Type\ConstantTypeHelper;
3032
use PHPStan\Type\Doctrine\DescriptorNotRegisteredException;
3133
use PHPStan\Type\Doctrine\DescriptorRegistry;
@@ -53,6 +55,7 @@
5355
use function get_class;
5456
use function gettype;
5557
use function in_array;
58+
use function is_array;
5659
use function is_int;
5760
use function is_numeric;
5861
use function is_object;
@@ -286,13 +289,13 @@ public function walkPathExpression($pathExpr): string
286289

287290
switch ($pathExpr->type) {
288291
case AST\PathExpression::TYPE_STATE_FIELD:
289-
[$typeName, $enumType] = $this->getTypeOfField($class, $fieldName);
292+
[$typeName, $enumType, $enumValues] = $this->getTypeOfField($class, $fieldName);
290293

291294
$nullable = $this->isQueryComponentNullable($dqlAlias)
292295
|| $class->isNullable($fieldName)
293296
|| $this->hasAggregateWithoutGroupBy();
294297

295-
$fieldType = $this->resolveDatabaseInternalType($typeName, $enumType, $nullable);
298+
$fieldType = $this->resolveDatabaseInternalType($typeName, $enumType, $enumValues, $nullable);
296299

297300
return $this->marshalType($fieldType);
298301

@@ -326,12 +329,12 @@ public function walkPathExpression($pathExpr): string
326329
}
327330

328331
$targetFieldName = $identifierFieldNames[0];
329-
[$typeName, $enumType] = $this->getTypeOfField($targetClass, $targetFieldName);
332+
[$typeName, $enumType, $enumValues] = $this->getTypeOfField($targetClass, $targetFieldName);
330333

331334
$nullable = ($joinColumn['nullable'] ?? true)
332335
|| $this->hasAggregateWithoutGroupBy();
333336

334-
$fieldType = $this->resolveDatabaseInternalType($typeName, $enumType, $nullable);
337+
$fieldType = $this->resolveDatabaseInternalType($typeName, $enumType, $enumValues, $nullable);
335338

336339
return $this->marshalType($fieldType);
337340

@@ -685,7 +688,7 @@ public function walkFunction($function): string
685688
return $this->marshalType(new MixedType());
686689
}
687690

688-
[$typeName, $enumType] = $this->getTypeOfField($targetClass, $targetFieldName);
691+
[$typeName, $enumType, $enumValues] = $this->getTypeOfField($targetClass, $targetFieldName);
689692

690693
if (!isset($assoc['joinColumns'])) {
691694
return $this->marshalType(new MixedType());
@@ -708,7 +711,7 @@ public function walkFunction($function): string
708711
|| $this->isQueryComponentNullable($dqlAlias)
709712
|| $this->hasAggregateWithoutGroupBy();
710713

711-
$fieldType = $this->resolveDatabaseInternalType($typeName, $enumType, $nullable);
714+
$fieldType = $this->resolveDatabaseInternalType($typeName, $enumType, $enumValues, $nullable);
712715

713716
return $this->marshalType($fieldType);
714717

@@ -1206,13 +1209,13 @@ public function walkSelectExpression($selectExpression): string
12061209
assert(array_key_exists('metadata', $qComp));
12071210
$class = $qComp['metadata'];
12081211

1209-
[$typeName, $enumType] = $this->getTypeOfField($class, $fieldName);
1212+
[$typeName, $enumType, $enumValues] = $this->getTypeOfField($class, $fieldName);
12101213

12111214
$nullable = $this->isQueryComponentNullable($dqlAlias)
12121215
|| $class->isNullable($fieldName)
12131216
|| $this->hasAggregateWithoutGroupBy();
12141217

1215-
$type = $this->resolveDoctrineType($typeName, $enumType, $nullable);
1218+
$type = $this->resolveDoctrineType($typeName, $enumType, $enumValues, $nullable);
12161219

12171220
$this->typeBuilder->addScalar($resultAlias, $type);
12181221

@@ -1235,11 +1238,12 @@ public function walkSelectExpression($selectExpression): string
12351238
if (
12361239
$expr instanceof TypedExpression
12371240
&& !$expr->getReturnType() instanceof DbalStringType // StringType is no-op, so using TypedExpression with that does nothing
1241+
&& !$expr->getReturnType() instanceof DbalEnumType // EnumType is also no-op
12381242
) {
12391243
$dbalTypeName = DbalType::getTypeRegistry()->lookupName($expr->getReturnType());
12401244
$type = TypeCombinator::intersect( // e.g. count is typed as int, but we infer int<0, max>
12411245
$type,
1242-
$this->resolveDoctrineType($dbalTypeName, null, TypeCombinator::containsNull($type)),
1246+
$this->resolveDoctrineType($dbalTypeName, null, null, TypeCombinator::containsNull($type)),
12431247
);
12441248

12451249
if ($this->hasAggregateWithoutGroupBy() && !$expr instanceof AST\Functions\CountFunction) {
@@ -1997,7 +2001,7 @@ private function isQueryComponentNullable(string $dqlAlias): bool
19972001

19982002
/**
19992003
* @param ClassMetadata<object> $class
2000-
* @return array{string, ?class-string<BackedEnum>} Doctrine type name and enum type of field
2004+
* @return array{string, ?class-string<BackedEnum>, ?list<string>} Doctrine type name, enum type of field, enum values
20012005
*/
20022006
private function getTypeOfField(ClassMetadata $class, string $fieldName): array
20032007
{
@@ -2015,11 +2019,45 @@ private function getTypeOfField(ClassMetadata $class, string $fieldName): array
20152019
$enumType = null;
20162020
}
20172021

2018-
return [$type, $enumType];
2022+
return [$type, $enumType, $this->detectEnumValues($type, $metadata)];
20192023
}
20202024

2021-
/** @param ?class-string<BackedEnum> $enumType */
2022-
private function resolveDoctrineType(string $typeName, ?string $enumType = null, bool $nullable = false): Type
2025+
/**
2026+
* @param mixed $metadata
2027+
*
2028+
* @return list<string>|null
2029+
*/
2030+
private function detectEnumValues(string $typeName, $metadata): ?array
2031+
{
2032+
if ($typeName !== 'enum') {
2033+
return null;
2034+
}
2035+
2036+
$values = $metadata['options']['values'] ?? [];
2037+
2038+
if (!is_array($values) || count($values) === 0) {
2039+
return null;
2040+
}
2041+
2042+
foreach ($values as $value) {
2043+
if (!is_string($value)) {
2044+
return null;
2045+
}
2046+
}
2047+
2048+
return array_values($values);
2049+
}
2050+
2051+
/**
2052+
* @param ?class-string<BackedEnum> $enumType
2053+
* @param ?list<string> $enumValues
2054+
*/
2055+
private function resolveDoctrineType(
2056+
string $typeName,
2057+
?string $enumType = null,
2058+
?array $enumValues = null,
2059+
bool $nullable = false
2060+
): Type
20232061
{
20242062
try {
20252063
$type = $this->descriptorRegistry
@@ -2036,8 +2074,14 @@ private function resolveDoctrineType(string $typeName, ?string $enumType = null,
20362074
), ...TypeUtils::getAccessoryTypes($type));
20372075
}
20382076
}
2077+
2078+
if ($enumValues !== null) {
2079+
$enumValuesType = TypeCombinator::union(...array_map(static fn (string $value) => new ConstantStringType($value), $enumValues));
2080+
$type = TypeCombinator::intersect($enumValuesType, $type);
2081+
}
2082+
20392083
if ($type instanceof NeverType) {
2040-
$type = new MixedType();
2084+
$type = new MixedType();
20412085
}
20422086
} catch (DescriptorNotRegisteredException $e) {
20432087
if ($enumType !== null) {
@@ -2051,11 +2095,19 @@ private function resolveDoctrineType(string $typeName, ?string $enumType = null,
20512095
$type = TypeCombinator::addNull($type);
20522096
}
20532097

2054-
return $type;
2098+
return $type;
20552099
}
20562100

2057-
/** @param ?class-string<BackedEnum> $enumType */
2058-
private function resolveDatabaseInternalType(string $typeName, ?string $enumType = null, bool $nullable = false): Type
2101+
/**
2102+
* @param ?class-string<BackedEnum> $enumType
2103+
* @param ?list<string> $enumValues
2104+
*/
2105+
private function resolveDatabaseInternalType(
2106+
string $typeName,
2107+
?string $enumType = null,
2108+
?array $enumValues = null,
2109+
bool $nullable = false
2110+
): Type
20592111
{
20602112
try {
20612113
$descriptor = $this->descriptorRegistry->get($typeName);
@@ -2074,6 +2126,11 @@ private function resolveDatabaseInternalType(string $typeName, ?string $enumType
20742126
$type = TypeCombinator::intersect($enumType, $type);
20752127
}
20762128

2129+
if ($enumValues !== null) {
2130+
$enumValuesType = TypeCombinator::union(...array_map(static fn (string $value) => new ConstantStringType($value), $enumValues));
2131+
$type = TypeCombinator::intersect($enumValuesType, $type);
2132+
}
2133+
20772134
if ($nullable) {
20782135
$type = TypeCombinator::addNull($type);
20792136
}

‎tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
use PHPStan\Type\StringType;
3636
use PHPStan\Type\Type;
3737
use PHPStan\Type\TypeCombinator;
38+
use PHPStan\Type\UnionType;
3839
use PHPStan\Type\VerbosityLevel;
3940
use QueryResult\Entities\Embedded;
4041
use QueryResult\Entities\JoinedChild;
@@ -44,6 +45,7 @@
4445
use QueryResult\Entities\One;
4546
use QueryResult\Entities\OneId;
4647
use QueryResult\Entities\SingleTableChild;
48+
use QueryResult\EntitiesDbal42\Dbal4Entity;
4749
use QueryResult\EntitiesEnum\EntityWithEnum;
4850
use QueryResult\EntitiesEnum\IntEnum;
4951
use QueryResult\EntitiesEnum\StringEnum;
@@ -187,6 +189,15 @@ public static function setUpBeforeClass(): void
187189
$em->persist($entityWithEnum);
188190
}
189191

192+
if (InstalledVersions::satisfies(new VersionParser(), 'doctrine/dbal', '>=4.2')) {
193+
assert(class_exists(Dbal4Entity::class));
194+
195+
$dbal4Entity = new Dbal4Entity();
196+
$dbal4Entity->enum = 'a';
197+
$dbal4Entity->smallfloat = 1.1;
198+
$em->persist($dbal4Entity);
199+
}
200+
190201
$em->flush();
191202
}
192203

@@ -1532,6 +1543,29 @@ private function yieldConditionalDataset(): iterable
15321543
];
15331544
}
15341545

1546+
if (InstalledVersions::satisfies(new VersionParser(), 'doctrine/dbal', '>=4.2')) {
1547+
yield 'enum and smallfloat' => [
1548+
$this->constantArray([
1549+
[
1550+
new ConstantStringType('enum'),
1551+
new UnionType([
1552+
new ConstantStringType('a'),
1553+
new ConstantStringType('b'),
1554+
new ConstantStringType('c'),
1555+
]),
1556+
],
1557+
[
1558+
new ConstantStringType('smallfloat'),
1559+
new FloatType(),
1560+
],
1561+
]),
1562+
'
1563+
SELECT e.enum, e.smallfloat
1564+
FROM QueryResult\EntitiesDbal42\Dbal4Entity e
1565+
',
1566+
];
1567+
}
1568+
15351569
$ormVersion = InstalledVersions::getVersion('doctrine/orm');
15361570
$hasOrm3 = $ormVersion !== null && strpos($ormVersion, '3.') === 0;
15371571

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /