Skip to content

Commit 42562f5

Browse files
vaielabstaabmEtienne V. Labelle
authored
Fix mixed row inference for PDOStatement::bindValue() + execute() (#772)
Co-authored-by: Markus Staab <maggus.staab@googlemail.com> Co-authored-by: Etienne V. Labelle <evaillancourt@vaielab.com> Co-authored-by: Markus Staab <markus.staab@redaxo.de>
1 parent dcb43d3 commit 42562f5

File tree

5 files changed

+110
-20
lines changed

5 files changed

+110
-20
lines changed

phpstan-baseline.neon

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ parameters:
7272
count: 2
7373
path: src/QueryReflection/QuerySimulation.php
7474

75+
-
76+
message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#'
77+
identifier: phpstanApi.instanceofType
78+
count: 1
79+
path: src/Extensions/PdoStatementExecuteTypeSpecifyingExtension.php
80+
7581
-
7682
message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#'
7783
identifier: phpstanApi.instanceofType

src/Extensions/PdoStatementExecuteTypeSpecifyingExtension.php

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
use PHPStan\Analyser\TypeSpecifierAwareExtension;
1313
use PHPStan\Analyser\TypeSpecifierContext;
1414
use PHPStan\Reflection\MethodReflection;
15+
use PHPStan\Type\Constant\ConstantArrayType;
16+
use PHPStan\Type\Constant\ConstantIntegerType;
17+
use PHPStan\Type\Constant\ConstantStringType;
1518
use PHPStan\Type\MethodTypeSpecifyingExtension;
1619
use PHPStan\Type\Type;
1720
use staabm\PHPStanDba\PdoReflection\PdoStatementReflection;
@@ -62,9 +65,7 @@ private function inferStatementType(MethodReflection $methodReflection, MethodCa
6265
{
6366
$args = $methodCall->getArgs();
6467

65-
if (0 === \count($args)) {
66-
return null;
67-
}
68+
6869

6970
$stmtReflection = new PdoStatementReflection();
7071
$queryExpr = $stmtReflection->findPrepareQueryStringExpression($methodCall);
@@ -73,7 +74,27 @@ private function inferStatementType(MethodReflection $methodReflection, MethodCa
7374
}
7475

7576
$queryReflection = new QueryReflection();
76-
$parameterTypes = $queryReflection->resolveParameterTypes($args[0]->value, $scope);
77+
78+
if (0 === \count($args)) {
79+
$parameterKeys = [];
80+
$parameterValues = [];
81+
82+
foreach ($stmtReflection->findPrepareBindCalls($methodCall) as $bindCall) {
83+
$bindArgs = $bindCall->getArgs();
84+
if (\count($bindArgs) >= 2) {
85+
$keyType = $scope->getType($bindArgs[0]->value);
86+
if ($keyType instanceof ConstantIntegerType || $keyType instanceof ConstantStringType) {
87+
$parameterKeys[] = $keyType;
88+
$parameterValues[] = $scope->getType($bindArgs[1]->value);
89+
}
90+
}
91+
}
92+
93+
$parameterTypes = new ConstantArrayType($parameterKeys, $parameterValues);
94+
} else {
95+
$parameterTypes = $queryReflection->resolveParameterTypes($args[0]->value, $scope);
96+
}
97+
7798
$queryStrings = $queryReflection->resolvePreparedQueryStrings($queryExpr, $parameterTypes, $scope);
7899

79100
$reflectionFetchType = QueryReflection::getRuntimeConfiguration()->getDefaultFetchMode();

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

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

tests/default/config/.phpunit-phpstan-dba-pdo-mysql.cache

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

tests/default/data/pdo-stmt-execute.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class Foo
99
{
1010
public function execute(PDO $pdo)
1111
{
12+
1213
$stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE adaid = :adaid');
1314
$stmt->execute([':adaid' => 1]);
1415
foreach ($stmt as $row) {
@@ -38,6 +39,7 @@ public function execute(PDO $pdo)
3839
foreach ($stmt as $row) {
3940
assertType('array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', $row);
4041
}
42+
4143
}
4244

4345
public function executeWithBindCalls(PDO $pdo)
@@ -48,6 +50,43 @@ public function executeWithBindCalls(PDO $pdo)
4850
$stmt->bindParam(':test1', $test);
4951
$stmt->bindValue(':test2', 1001);
5052
$stmt->execute();
53+
54+
$stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE adaid = :adaid');
55+
$stmt->bindValue(':adaid', 1);
56+
$stmt->execute();
57+
foreach ($stmt as $row) {
58+
assertType('array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', $row);
59+
}
60+
61+
$stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE adaid = :adaid');
62+
$stmt->bindValue('adaid', 1);
63+
$stmt->execute(); // prefixed ":" is optional
64+
foreach ($stmt as $row) {
65+
assertType('array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', $row);
66+
}
67+
68+
$stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE email = :email');
69+
$stmt->bindValue(':email', 'email@example.org');
70+
$stmt->execute();
71+
foreach ($stmt as $row) {
72+
assertType('array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', $row);
73+
}
74+
75+
$stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE adaid = ?');
76+
$stmt->bindValue(1, 1);
77+
$stmt->execute();
78+
foreach ($stmt as $row) {
79+
assertType('array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', $row);
80+
}
81+
82+
$stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE adaid = ? and email = ? ');
83+
$stmt->bindValue(1, 1);
84+
$stmt->bindValue(2, 'email@example.org');
85+
$stmt->execute();
86+
foreach ($stmt as $row) {
87+
assertType('array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', $row);
88+
}
89+
5190
}
5291

5392
public function errors(PDO $pdo)

0 commit comments

Comments
 (0)