diff --git a/src/Extensions/PdoStatementExecuteTypeSpecifyingExtension.php b/src/Extensions/PdoStatementExecuteTypeSpecifyingExtension.php index 1e48439f..6d8ddc39 100644 --- a/src/Extensions/PdoStatementExecuteTypeSpecifyingExtension.php +++ b/src/Extensions/PdoStatementExecuteTypeSpecifyingExtension.php @@ -12,6 +12,8 @@ use PHPStan\Analyser\TypeSpecifierAwareExtension; use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Reflection\MethodReflection; +use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\MethodTypeSpecifyingExtension; use PHPStan\Type\Type; use staabm\PHPStanDba\PdoReflection\PdoStatementReflection; @@ -62,9 +64,7 @@ private function inferStatementType(MethodReflection $methodReflection, MethodCa { $args = $methodCall->getArgs(); - if (0 === \count($args)) { - return null; - } + $stmtReflection = new PdoStatementReflection(); $queryExpr = $stmtReflection->findPrepareQueryStringExpression($methodCall); @@ -73,7 +73,32 @@ private function inferStatementType(MethodReflection $methodReflection, MethodCa } $queryReflection = new QueryReflection(); - $parameterTypes = $queryReflection->resolveParameterTypes($args[0]->value, $scope); + + + if (0 === \count($args)) { + $parameterKeys = []; + $parameterValues = []; + + foreach ($stmtReflection->findPrepareBindCalls($methodCall) as $bindCall) { + $bindArgs = $bindCall->getArgs(); + if (\count($bindArgs) >= 2) { + $keyType = $scope->getType($bindArgs[0]->value); + $constantStrings = $keyType->getConstantStrings(); + if ($keyType instanceof ConstantIntegerType) { + $parameterKeys[] = $keyType; + $parameterValues[] = $scope->getType($bindArgs[1]->value); + } elseif ([] !== $constantStrings) { + $parameterKeys[] = $constantStrings[0]; + $parameterValues[] = $scope->getType($bindArgs[1]->value); + } + } + } + + $parameterTypes = new ConstantArrayType($parameterKeys, $parameterValues); + } else { + $parameterTypes = $queryReflection->resolveParameterTypes($args[0]->value, $scope); + } + $queryStrings = $queryReflection->resolvePreparedQueryStrings($queryExpr, $parameterTypes, $scope); $reflectionFetchType = QueryReflection::getRuntimeConfiguration()->getDefaultFetchMode(); diff --git a/tests/default/data/pdo-stmt-execute.php b/tests/default/data/pdo-stmt-execute.php index 4ad74a7c..f95525a8 100644 --- a/tests/default/data/pdo-stmt-execute.php +++ b/tests/default/data/pdo-stmt-execute.php @@ -9,6 +9,7 @@ class Foo { public function execute(PDO $pdo) { + $stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE adaid = :adaid'); $stmt->execute([':adaid' => 1]); foreach ($stmt as $row) { @@ -33,11 +34,12 @@ public function execute(PDO $pdo) assertType('array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', $row); } - $stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE adaid = ? and email = ?'); + $stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE adaid = ? and email = ? '); $stmt->execute([1, 'email@example.org']); foreach ($stmt as $row) { assertType('array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', $row); } + } public function executeWithBindCalls(PDO $pdo) @@ -48,6 +50,43 @@ public function executeWithBindCalls(PDO $pdo) $stmt->bindParam(':test1', $test); $stmt->bindValue(':test2', 1001); $stmt->execute(); + + $stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE adaid = :adaid'); + $stmt->bindValue(':adaid', 1); + $stmt->execute(); + foreach ($stmt as $row) { + assertType('array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', $row); + } + + $stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE adaid = :adaid'); + $stmt->bindValue('adaid', 1); + $stmt->execute(); // prefixed ":" is optional + foreach ($stmt as $row) { + assertType('array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', $row); + } + + $stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE email = :email'); + $stmt->bindValue(':email', 'email@example.org'); + $stmt->execute(); + foreach ($stmt as $row) { + assertType('array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', $row); + } + + $stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE adaid = ?'); + $stmt->bindValue(1, 1); + $stmt->execute(); + foreach ($stmt as $row) { + assertType('array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', $row); + } + + $stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE adaid = ? and email = ? '); + $stmt->bindValue(1, 1); + $stmt->bindValue(2, 'email@example.org'); + $stmt->execute(); + foreach ($stmt as $row) { + assertType('array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', $row); + } + } public function errors(PDO $pdo)