Skip to content

Commit 83a5b51

Browse files
staabmclxmstaab
andauthored
doctrine-dbal: implement union query types (#295)
Co-authored-by: Markus Staab <[email protected]>
1 parent c9c166e commit 83a5b51

13 files changed

+682
-189
lines changed

.phpstan-dba-mysqli.cache

Lines changed: 409 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/DoctrineReflection/DoctrineReflection.php

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace staabm\PHPStanDba\DoctrineReflection;
66

7+
use Doctrine\DBAL\Result;
8+
use Doctrine\DBAL\Statement;
79
use PHPStan\Reflection\MethodReflection;
810
use PHPStan\Type\ArrayType;
911
use PHPStan\Type\Constant\ConstantArrayType;
@@ -16,12 +18,13 @@
1618
use PHPStan\Type\IntegerType;
1719
use PHPStan\Type\Type;
1820
use PHPStan\Type\TypeCombinator;
21+
use staabm\PHPStanDba\QueryReflection\QueryReflection;
1922
use staabm\PHPStanDba\QueryReflection\QueryReflector;
2023
use Traversable;
2124

2225
final class DoctrineReflection
2326
{
24-
public function fetchResultType(MethodReflection $methodReflection, Type $resultRowType): ?Type
27+
public function reduceResultType(MethodReflection $methodReflection, Type $resultRowType): ?Type
2528
{
2629
$usedMethod = strtolower($methodReflection->getName());
2730

@@ -107,4 +110,95 @@ public function fetchResultType(MethodReflection $methodReflection, Type $result
107110

108111
return null;
109112
}
113+
114+
/**
115+
* @param iterable<string> $queryStrings
116+
* @param QueryReflector::FETCH_TYPE* $reflectionFetchType
117+
*/
118+
public function createGenericStatement(iterable $queryStrings, int $reflectionFetchType): ?Type
119+
{
120+
$genericObjects = [];
121+
122+
foreach ($queryStrings as $queryString) {
123+
$queryReflection = new QueryReflection();
124+
125+
$resultType = $queryReflection->getResultType($queryString, $reflectionFetchType);
126+
if (null === $resultType) {
127+
return null;
128+
}
129+
130+
$genericObjects[] = new GenericObjectType(Statement::class, [$resultType]);
131+
}
132+
133+
if (\count($genericObjects) > 1) {
134+
return TypeCombinator::union(...$genericObjects);
135+
}
136+
if (1 === \count($genericObjects)) {
137+
return $genericObjects[0];
138+
}
139+
140+
return null;
141+
}
142+
143+
/**
144+
* @param iterable<string> $queryStrings
145+
* @param QueryReflector::FETCH_TYPE* $reflectionFetchType
146+
*/
147+
public function createGenericResult(iterable $queryStrings, int $reflectionFetchType): ?Type
148+
{
149+
$genericObjects = [];
150+
151+
foreach ($queryStrings as $queryString) {
152+
$queryReflection = new QueryReflection();
153+
154+
$resultType = $queryReflection->getResultType($queryString, $reflectionFetchType);
155+
if (null === $resultType) {
156+
return null;
157+
}
158+
159+
$genericObjects[] = new GenericObjectType(Result::class, [$resultType]);
160+
}
161+
162+
if (\count($genericObjects) > 1) {
163+
return TypeCombinator::union(...$genericObjects);
164+
}
165+
if (1 === \count($genericObjects)) {
166+
return $genericObjects[0];
167+
}
168+
169+
return null;
170+
}
171+
172+
/**
173+
* @param iterable<string> $queryStrings
174+
*/
175+
public function createFetchType(iterable $queryStrings, MethodReflection $methodReflection): ?Type
176+
{
177+
$queryReflection = new QueryReflection();
178+
179+
$fetchTypes = [];
180+
foreach ($queryStrings as $queryString) {
181+
$resultType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_BOTH);
182+
183+
if (null === $resultType) {
184+
return null;
185+
}
186+
187+
$fetchResultType = $this->reduceResultType($methodReflection, $resultType);
188+
if (null === $fetchResultType) {
189+
return null;
190+
}
191+
192+
$fetchTypes[] = $fetchResultType;
193+
}
194+
195+
if (\count($fetchTypes) > 1) {
196+
return TypeCombinator::union(...$fetchTypes);
197+
}
198+
if (1 === \count($fetchTypes)) {
199+
return $fetchTypes[0];
200+
}
201+
202+
return null;
203+
}
110204
}

src/Extensions/DoctrineConnectionExecuteQueryDynamicReturnTypeExtension.php

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,15 @@
77
use Composer\InstalledVersions;
88
use Composer\Semver\VersionParser;
99
use Doctrine\DBAL\Connection;
10-
use Doctrine\DBAL\Result;
1110
use PhpParser\Node\Expr;
1211
use PhpParser\Node\Expr\MethodCall;
1312
use PHPStan\Analyser\Scope;
1413
use PHPStan\Reflection\MethodReflection;
1514
use PHPStan\Reflection\ParametersAcceptorSelector;
1615
use PHPStan\Type\DynamicMethodReturnTypeExtension;
17-
use PHPStan\Type\Generic\GenericObjectType;
1816
use PHPStan\Type\MixedType;
1917
use PHPStan\Type\Type;
18+
use staabm\PHPStanDba\DoctrineReflection\DoctrineReflection;
2019
use staabm\PHPStanDba\QueryReflection\QueryReflection;
2120
use staabm\PHPStanDba\QueryReflection\QueryReflector;
2221

@@ -71,25 +70,16 @@ private function inferType(Expr $queryExpr, ?Expr $paramsExpr, Scope $scope): ?T
7170
{
7271
if (null === $paramsExpr) {
7372
$queryReflection = new QueryReflection();
74-
$queryString = $queryReflection->resolveQueryString($queryExpr, $scope);
75-
if (null === $queryString) {
76-
return null;
77-
}
73+
$queryStrings = $queryReflection->resolveQueryStrings($queryExpr, $scope);
7874
} else {
7975
$parameterTypes = $scope->getType($paramsExpr);
8076

8177
$queryReflection = new QueryReflection();
82-
$queryString = $queryReflection->resolvePreparedQueryString($queryExpr, $parameterTypes, $scope);
83-
if (null === $queryString) {
84-
return null;
85-
}
78+
$queryStrings = $queryReflection->resolvePreparedQueryStrings($queryExpr, $parameterTypes, $scope);
8679
}
8780

88-
$resultType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_BOTH);
89-
if ($resultType) {
90-
return new GenericObjectType(Result::class, [$resultType]);
91-
}
81+
$doctrineReflection = new DoctrineReflection();
9282

93-
return null;
83+
return $doctrineReflection->createGenericResult($queryStrings, QueryReflector::FETCH_TYPE_BOTH);
9484
}
9585
}

src/Extensions/DoctrineConnectionFetchDynamicReturnTypeExtension.php

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
use PHPStan\Type\Type;
1818
use staabm\PHPStanDba\DoctrineReflection\DoctrineReflection;
1919
use staabm\PHPStanDba\QueryReflection\QueryReflection;
20-
use staabm\PHPStanDba\QueryReflection\QueryReflector;
2120

2221
final class DoctrineConnectionFetchDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
2322
{
@@ -82,33 +81,16 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
8281

8382
private function inferType(MethodReflection $methodReflection, Expr $queryExpr, ?Expr $paramsExpr, Scope $scope): ?Type
8483
{
84+
$queryReflection = new QueryReflection();
85+
$doctrineReflection = new DoctrineReflection();
86+
8587
if (null === $paramsExpr) {
86-
$queryReflection = new QueryReflection();
87-
$queryString = $queryReflection->resolveQueryString($queryExpr, $scope);
88-
if (null === $queryString) {
89-
return null;
90-
}
88+
$queryString = $queryReflection->resolveQueryStrings($queryExpr, $scope);
9189
} else {
9290
$parameterTypes = $scope->getType($paramsExpr);
93-
94-
$queryReflection = new QueryReflection();
95-
$queryString = $queryReflection->resolvePreparedQueryString($queryExpr, $parameterTypes, $scope);
96-
if (null === $queryString) {
97-
return null;
98-
}
99-
}
100-
101-
$resultType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_BOTH);
102-
103-
if ($resultType) {
104-
$doctrineReflection = new DoctrineReflection();
105-
$fetchResultType = $doctrineReflection->fetchResultType($methodReflection, $resultType);
106-
107-
if (null !== $fetchResultType) {
108-
return $fetchResultType;
109-
}
91+
$queryString = $queryReflection->resolvePreparedQueryStrings($queryExpr, $parameterTypes, $scope);
11092
}
11193

112-
return null;
94+
return $doctrineReflection->createFetchType($queryString, $methodReflection);
11395
}
11496
}

src/Extensions/DoctrineConnectionPrepareDynamicReturnTypeExtension.php

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,15 @@
77
use Composer\InstalledVersions;
88
use Composer\Semver\VersionParser;
99
use Doctrine\DBAL\Connection;
10-
use Doctrine\DBAL\Statement;
1110
use PhpParser\Node\Expr;
1211
use PhpParser\Node\Expr\MethodCall;
1312
use PHPStan\Analyser\Scope;
1413
use PHPStan\Reflection\MethodReflection;
1514
use PHPStan\Reflection\ParametersAcceptorSelector;
1615
use PHPStan\Type\DynamicMethodReturnTypeExtension;
17-
use PHPStan\Type\Generic\GenericObjectType;
1816
use PHPStan\Type\MixedType;
1917
use PHPStan\Type\Type;
18+
use staabm\PHPStanDba\DoctrineReflection\DoctrineReflection;
2019
use staabm\PHPStanDba\QueryReflection\QueryReflection;
2120
use staabm\PHPStanDba\QueryReflection\QueryReflector;
2221

@@ -65,16 +64,10 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
6564
private function inferType(Expr $queryExpr, Scope $scope): ?Type
6665
{
6766
$queryReflection = new QueryReflection();
68-
$queryString = $queryReflection->resolveQueryString($queryExpr, $scope);
69-
if (null === $queryString) {
70-
return null;
71-
}
67+
$queryStrings = $queryReflection->resolveQueryStrings($queryExpr, $scope);
7268

73-
$resultType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_BOTH);
74-
if ($resultType) {
75-
return new GenericObjectType(Statement::class, [$resultType]);
76-
}
69+
$doctrineReflection = new DoctrineReflection();
7770

78-
return null;
71+
return $doctrineReflection->createGenericStatement($queryStrings, QueryReflector::FETCH_TYPE_BOTH);
7972
}
8073
}

src/Extensions/DoctrineConnectionQueryDynamicReturnTypeExtension.php

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,15 @@
77
use Composer\InstalledVersions;
88
use Composer\Semver\VersionParser;
99
use Doctrine\DBAL\Connection;
10-
use Doctrine\DBAL\Result;
1110
use PhpParser\Node\Expr;
1211
use PhpParser\Node\Expr\MethodCall;
1312
use PHPStan\Analyser\Scope;
1413
use PHPStan\Reflection\MethodReflection;
1514
use PHPStan\Reflection\ParametersAcceptorSelector;
1615
use PHPStan\Type\DynamicMethodReturnTypeExtension;
17-
use PHPStan\Type\Generic\GenericObjectType;
1816
use PHPStan\Type\MixedType;
1917
use PHPStan\Type\Type;
18+
use staabm\PHPStanDba\DoctrineReflection\DoctrineReflection;
2019
use staabm\PHPStanDba\QueryReflection\QueryReflection;
2120
use staabm\PHPStanDba\QueryReflection\QueryReflector;
2221

@@ -65,16 +64,10 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
6564
private function inferType(Expr $queryExpr, Scope $scope): ?Type
6665
{
6766
$queryReflection = new QueryReflection();
68-
$queryString = $queryReflection->resolveQueryString($queryExpr, $scope);
69-
if (null === $queryString) {
70-
return null;
71-
}
67+
$queryStrings = $queryReflection->resolveQueryStrings($queryExpr, $scope);
7268

73-
$resultType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_BOTH);
74-
if ($resultType) {
75-
return new GenericObjectType(Result::class, [$resultType]);
76-
}
69+
$doctrineReflection = new DoctrineReflection();
7770

78-
return null;
71+
return $doctrineReflection->createGenericResult($queryStrings, QueryReflector::FETCH_TYPE_BOTH);
7972
}
8073
}

src/Extensions/DoctrineResultDynamicReturnTypeExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
8282
}
8383

8484
$doctrineReflection = new DoctrineReflection();
85-
$fetchResultType = $doctrineReflection->fetchResultType($methodReflection, $resultRowType);
85+
$fetchResultType = $doctrineReflection->reduceResultType($methodReflection, $resultRowType);
8686
if (null !== $fetchResultType) {
8787
return $fetchResultType;
8888
}

src/Extensions/DoctrineStatementExecuteDynamicReturnTypeExtension.php

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@
66

77
use Composer\InstalledVersions;
88
use Composer\Semver\VersionParser;
9-
use Doctrine\DBAL\Result;
109
use Doctrine\DBAL\Statement;
1110
use PhpParser\Node\Expr;
1211
use PhpParser\Node\Expr\MethodCall;
1312
use PHPStan\Analyser\Scope;
1413
use PHPStan\Reflection\MethodReflection;
1514
use PHPStan\Reflection\ParametersAcceptorSelector;
1615
use PHPStan\Type\DynamicMethodReturnTypeExtension;
17-
use PHPStan\Type\Generic\GenericObjectType;
1816
use PHPStan\Type\Type;
17+
use staabm\PHPStanDba\DoctrineReflection\DoctrineReflection;
1918
use staabm\PHPStanDba\PdoReflection\PdoStatementReflection;
2019
use staabm\PHPStanDba\QueryReflection\QueryReflection;
2120
use staabm\PHPStanDba\QueryReflection\QueryReflector;
@@ -60,6 +59,8 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method
6059

6160
private function inferType(MethodReflection $methodReflection, MethodCall $methodCall, Expr $paramsExpr, Scope $scope): ?Type
6261
{
62+
$doctrineReflection = new DoctrineReflection();
63+
6364
$parameterTypes = $scope->getType($paramsExpr);
6465

6566
$stmtReflection = new PdoStatementReflection();
@@ -69,16 +70,8 @@ private function inferType(MethodReflection $methodReflection, MethodCall $metho
6970
}
7071

7172
$queryReflection = new QueryReflection();
72-
$queryString = $queryReflection->resolvePreparedQueryString($queryExpr, $parameterTypes, $scope);
73-
if (null === $queryString) {
74-
return null;
75-
}
76-
77-
$resultType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_BOTH);
78-
if ($resultType) {
79-
return new GenericObjectType(Result::class, [$resultType]);
80-
}
73+
$queryStrings = $queryReflection->resolvePreparedQueryStrings($queryExpr, $parameterTypes, $scope);
8174

82-
return null;
75+
return $doctrineReflection->createGenericResult($queryStrings, QueryReflector::FETCH_TYPE_BOTH);
8376
}
8477
}

0 commit comments

Comments
 (0)