Skip to content

Commit fa34896

Browse files
staabmclxmstaab
andauthored
Infer default-result type when sql queries cannot be inferred (#373)
Co-authored-by: Markus Staab <[email protected]>
1 parent 40595ed commit fa34896

16 files changed

+2475
-15
lines changed

.phpstan-dba-mysqli.cache

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

.phpstan-dba-pdo-mysql.cache

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

src/Extensions/PdoQueryDynamicReturnTypeExtension.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use PHPStan\Type\MixedType;
1717
use PHPStan\Type\Type;
1818
use PHPStan\Type\TypeCombinator;
19+
use staabm\PHPStanDba\PdoReflection\PdoStatementObjectType;
1920
use staabm\PHPStanDba\PdoReflection\PdoStatementReflection;
2021
use staabm\PHPStanDba\QueryReflection\QueryReflection;
2122
use staabm\PHPStanDba\QueryReflection\QueryReflector;
@@ -103,7 +104,24 @@ private function inferType(MethodCall $methodCall, Expr $queryExpr, Scope $scope
103104
$queryStrings = $queryReflection->resolveQueryStrings($queryExpr, $scope);
104105

105106
$pdoStatementReflection = new PdoStatementReflection();
107+
$genericStatement = $pdoStatementReflection->createGenericStatement($queryStrings, $reflectionFetchType);
106108

107-
return $pdoStatementReflection->createGenericStatement($queryStrings, $reflectionFetchType);
109+
if (null !== $genericStatement) {
110+
return $genericStatement;
111+
}
112+
113+
$queryStrings = $queryReflection->resolveQueryStrings($queryExpr, $scope);
114+
foreach ($queryStrings as $queryString) {
115+
if ('SELECT' !== QueryReflection::getQueryType($queryString)) {
116+
return null;
117+
}
118+
119+
// return unknown type if query contains syntax errors
120+
if (null !== $queryReflection->validateQueryString($queryString)) {
121+
return null;
122+
}
123+
}
124+
125+
return PdoStatementObjectType::createDefaultType($reflectionFetchType);
108126
}
109127
}

src/PdoReflection/PdoStatementObjectType.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@
66

77
use PDOStatement;
88
use PHPStan\Type\ArrayType;
9+
use PHPStan\Type\BenevolentUnionType;
910
use PHPStan\Type\Constant\ConstantArrayType;
1011
use PHPStan\Type\Constant\ConstantArrayTypeBuilder;
1112
use PHPStan\Type\Constant\ConstantIntegerType;
1213
use PHPStan\Type\Constant\ConstantStringType;
14+
use PHPStan\Type\FloatType;
1315
use PHPStan\Type\Generic\GenericObjectType;
16+
use PHPStan\Type\IntegerRangeType;
1417
use PHPStan\Type\IntegerType;
18+
use PHPStan\Type\MixedType;
1519
use PHPStan\Type\ObjectType;
20+
use PHPStan\Type\StringType;
1621
use PHPStan\Type\Type;
22+
use PHPStan\Type\UnionType;
1723
use staabm\PHPStanDba\QueryReflection\QueryReflector;
1824

1925
class PdoStatementObjectType extends GenericObjectType
@@ -94,4 +100,35 @@ private function reduceBothType(Type $bothType, int $fetchType): Type
94100
// not yet supported fetch type - or $fetchType == BOTH
95101
return $bothType;
96102
}
103+
104+
/**
105+
* @param QueryReflector::FETCH_TYPE* $fetchType
106+
*/
107+
public static function createDefaultType(int $fetchType): Type
108+
{
109+
// we assume native PDO is not able to return bool.
110+
$pdoScalar = new UnionType([new IntegerType(), new FloatType(), new StringType()]);
111+
$arrayKey = new BenevolentUnionType([new IntegerType(), new StringType()]);
112+
113+
switch ($fetchType) {
114+
case QueryReflector::FETCH_TYPE_CLASS:
115+
return new GenericObjectType(PDOStatement::class, [new ObjectType('stdClass')]);
116+
case QueryReflector::FETCH_TYPE_KEY_VALUE:
117+
$arrayBuilder = ConstantArrayTypeBuilder::createEmpty();
118+
$arrayBuilder->setOffsetValueType(new ConstantIntegerType(0), new MixedType());
119+
$arrayBuilder->setOffsetValueType(new ConstantIntegerType(1), new MixedType());
120+
121+
return new GenericObjectType(PDOStatement::class, [$arrayBuilder->getArray()]);
122+
case QueryReflector::FETCH_TYPE_NUMERIC:
123+
return new GenericObjectType(PDOStatement::class, [new ArrayType(IntegerRangeType::fromInterval(0, null), $pdoScalar)]);
124+
case QueryReflector::FETCH_TYPE_ASSOC:
125+
return new GenericObjectType(PDOStatement::class, [new ArrayType(new StringType(), $pdoScalar)]);
126+
case QueryReflector::FETCH_TYPE_BOTH:
127+
return new GenericObjectType(PDOStatement::class, [new ArrayType($arrayKey, $pdoScalar)]);
128+
case QueryReflector::FETCH_TYPE_COLUMN:
129+
return new GenericObjectType(PDOStatement::class, [$pdoScalar]);
130+
}
131+
132+
return new GenericObjectType(PDOStatement::class, [new MixedType()]);
133+
}
97134
}

tests/default/DbaInferenceTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public function dataFileAsserts(): iterable
6262

6363
yield from $this->gatherAssertTypes(__DIR__.'/data/pdo-union-result.php');
6464
yield from $this->gatherAssertTypes(__DIR__.'/data/mysqli-union-result.php');
65+
yield from $this->gatherAssertTypes(__DIR__.'/data/pdo-default-fetch-types.php');
6566
yield from $this->gatherAssertTypes(__DIR__.'/data/bug372.php');
6667
}
6768

tests/default/config/.phpstan-dba-mysqli.cache

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

0 commit comments

Comments
 (0)