Skip to content

Commit 1b9f507

Browse files
committed
More tests
1 parent 7c8e586 commit 1b9f507

File tree

6 files changed

+347
-45
lines changed

6 files changed

+347
-45
lines changed

src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php

Lines changed: 66 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,19 @@
88
use PHPStan\Analyser\Scope;
99
use PHPStan\Reflection\MethodReflection;
1010
use PHPStan\Reflection\ParametersAcceptorSelector;
11+
use PHPStan\ShouldNotHappenException;
1112
use PHPStan\Type\ArrayType;
13+
use PHPStan\Type\BooleanType;
14+
use PHPStan\Type\Constant\ConstantArrayType;
1215
use PHPStan\Type\Constant\ConstantIntegerType;
16+
use PHPStan\Type\ConstantTypeHelper;
1317
use PHPStan\Type\DynamicMethodReturnTypeExtension;
18+
use PHPStan\Type\FloatType;
1419
use PHPStan\Type\Generic\GenericObjectType;
1520
use PHPStan\Type\IntegerType;
21+
use PHPStan\Type\MixedType;
22+
use PHPStan\Type\NullType;
23+
use PHPStan\Type\StringType;
1624
use PHPStan\Type\Type;
1725
use PHPStan\Type\TypeCombinator;
1826
use PHPStan\Type\VoidType;
@@ -25,6 +33,7 @@ final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturn
2533
'execute' => 1,
2634
'executeIgnoreQueryCache' => 1,
2735
'executeUsingQueryCache' => 1,
36+
'getOneOrNullResult' => 0,
2837
'getSingleResult' => 0,
2938
];
3039

@@ -45,69 +54,89 @@ public function getTypeFromMethodCall(
4554
): Type
4655
{
4756
$methodName = $methodReflection->getName();
48-
$argIndex = self::METHOD_HYDRATION_MODE_ARG[$methodName] ?? 0;
4957

50-
$isHydrationModeObject = $this->isHydrationModeObject($methodCall, $scope, $argIndex);
58+
if (!isset(self::METHOD_HYDRATION_MODE_ARG[$methodName])) {
59+
throw new ShouldNotHappenException();
60+
}
61+
62+
$argIndex = self::METHOD_HYDRATION_MODE_ARG[$methodName];
63+
$args = $methodCall->getArgs();
5164

52-
if (!$isHydrationModeObject) {
53-
return $this->fallbackType($methodReflection);
65+
if (isset($args[$argIndex])) {
66+
$hydrationMode = $scope->getType($args[$argIndex]->value);
67+
} else {
68+
$parametersAcceptor = ParametersAcceptorSelector::selectSingle(
69+
$methodReflection->getVariants()
70+
);
71+
$parameter = $parametersAcceptor->getParameters()[$argIndex];
72+
$hydrationMode = $parameter->getDefaultValue() ?? new NullType();
5473
}
5574

5675
$queryType = $scope->getType($methodCall->var);
76+
$queryResultType = $this->getQueryResultType($queryType);
77+
78+
return $this->getMethodReturnTypeForHydrationMode(
79+
$methodReflection,
80+
$hydrationMode,
81+
$queryResultType,
82+
);
83+
}
5784

85+
private function getQueryResultType(Type $queryType): Type
86+
{
5887
if (!$queryType instanceof GenericObjectType) {
59-
return $this->fallbackType($methodReflection);
88+
return new MixedType();
6089
}
6190

6291
$types = $queryType->getTypes();
6392

64-
if (!isset($types[0])) {
65-
return $this->fallbackType($methodReflection);
66-
}
67-
68-
$resultType = $types[0];
93+
return $types[0] ?? new MixedType();
94+
}
6995

70-
if ($resultType instanceof VoidType) {
71-
return $this->fallbackType($methodReflection);
96+
private function getMethodReturnTypeForHydrationMode(
97+
MethodReflection $methodReflection,
98+
Type $hydrationMode,
99+
Type $queryResultType
100+
): Type {
101+
if ($queryResultType instanceof VoidType) {
102+
// A void query result type indicates an UPDATE or DELETE query.
103+
// In this case all methods return the number of affected rows.
104+
return new IntegerType();
72105
}
73106

74-
if ($methodName === 'getOneOrNullResult') {
75-
return TypeCombinator::addNull($resultType);
107+
if (!$this->isObjectHydrationMode($hydrationMode)) {
108+
// We support only HYDRATE_OBJECT. For other hydration modes, we
109+
// return the declared return type of the method.
110+
return $this->originalReturnType($methodReflection);
76111
}
77112

78-
if ($methodName === 'getSingleResult') {
79-
return $resultType;
113+
switch ($methodReflection->getName()) {
114+
case 'getSingleResult':
115+
return $queryResultType;
116+
case 'getOneOrNullResult':
117+
return TypeCombinator::addNull($queryResultType);
118+
default:
119+
return new ArrayType(
120+
new MixedType(),
121+
$queryResultType
122+
);
80123
}
81-
82-
return new ArrayType(
83-
new IntegerType(),
84-
$resultType
85-
);
86124
}
87125

88-
private function isHydrationModeObject(
89-
MethodCall $methodCall,
90-
Scope $scope,
91-
int $argIndex
92-
): bool
126+
private function isObjectHydrationMode(Type $type): bool
93127
{
94-
$args = $methodCall->getArgs();
95-
96-
if (!isset($args[$argIndex])) {
97-
return true;
98-
}
99-
100-
$argType = $scope->getType($args[$argIndex]->value);
101-
if (!$argType instanceof ConstantIntegerType) {
128+
if (!$type instanceof ConstantIntegerType) {
102129
return false;
103130
}
104131

105-
return $argType->getValue() === Query::HYDRATE_OBJECT;
132+
return $type->getValue() === AbstractQuery::HYDRATE_OBJECT;
106133
}
107134

108-
private function fallbackType(MethodReflection $methodReflection): Type
135+
private function originalReturnType(MethodReflection $methodReflection): Type
109136
{
110-
$parametersAcceptor = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants());
137+
$parametersAcceptor = ParametersAcceptorSelector::selectSingle(
138+
$methodReflection->getVariants()
139+
);
111140

112141
return $parametersAcceptor->getReturnType();
113142
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Doctrine;
4+
5+
use PHPStan\Testing\TypeInferenceTestCase;
6+
7+
class QueryResultTypeDynamicReturnTypeExtensionTest extends TypeInferenceTestCase
8+
{
9+
10+
/** @return iterable<mixed> */
11+
public function dataFileAsserts(): iterable
12+
{
13+
yield from $this->gatherAssertTypes(__DIR__ . '/data/QueryResult/base.php');
14+
}
15+
16+
/**
17+
* @dataProvider dataFileAsserts
18+
* @param string $assertType
19+
* @param string $file
20+
* @param mixed ...$args
21+
*/
22+
public function testFileAsserts(
23+
string $assertType,
24+
string $file,
25+
...$args
26+
): void
27+
{
28+
$this->assertFileAsserts($assertType, $file, ...$args);
29+
}
30+
31+
/** @return string[] */
32+
public static function getAdditionalConfigFiles(): array
33+
{
34+
return [__DIR__ . '/data/QueryResult/config.neon'];
35+
}
36+
37+
}

tests/Type/Doctrine/Query/QueryResultTypeWalkerTest.php

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,7 @@ public static function getAdditionalConfigFiles(): array
4242

4343
public function setUp(): void
4444
{
45-
$config = Setup::createAnnotationMetadataConfiguration([__DIR__ . '/src'], true, null, null, false);
46-
47-
$conn = [
48-
'driver' => 'pdo_sqlite',
49-
'path' => __DIR__ . '/data/QueryResult/db.sqlite',
50-
];
51-
52-
$this->em = EntityManager::create($conn, $config);
45+
$this->em = require __DIR__ . '/data/QueryResult/entity-manager.php';
5346

5447
$this->descriptorRegistry = self::getContainer()->getByType(DescriptorRegistry::class);
5548
}

0 commit comments

Comments
 (0)