diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e07441b1..2515d7ca 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -72,6 +72,12 @@ parameters: count: 2 path: src/QueryReflection/QuerySimulation.php + - + message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' + identifier: phpstanApi.instanceofType + count: 1 + path: src/Extensions/PdoStatementExecuteTypeSpecifyingExtension.php + - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType diff --git a/src/Extensions/PdoStatementExecuteTypeSpecifyingExtension.php b/src/Extensions/PdoStatementExecuteTypeSpecifyingExtension.php index 1e48439f..310b275b 100644 --- a/src/Extensions/PdoStatementExecuteTypeSpecifyingExtension.php +++ b/src/Extensions/PdoStatementExecuteTypeSpecifyingExtension.php @@ -12,6 +12,9 @@ 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\Constant\ConstantStringType; use PHPStan\Type\MethodTypeSpecifyingExtension; use PHPStan\Type\Type; use staabm\PHPStanDba\PdoReflection\PdoStatementReflection; @@ -62,9 +65,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 +74,27 @@ 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); + if ($keyType instanceof ConstantIntegerType || $keyType instanceof ConstantStringType) { + $parameterKeys[] = $keyType; + $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/config/.phpunit-phpstan-dba-mysqli.cache b/tests/default/config/.phpunit-phpstan-dba-mysqli.cache index e884f353..6e4bc6d0 100644 --- a/tests/default/config/.phpunit-phpstan-dba-mysqli.cache +++ b/tests/default/config/.phpunit-phpstan-dba-mysqli.cache @@ -310,10 +310,6 @@ FROM ada' => array ( 'type-description' => 'array{email: string, 0: string}', ), - 3 => - array ( - 'type-description' => 'array{email: string}', - ), ), ), 'SELECT email adaid WHERE gesperrt freigabe1u1 FROM ada' => @@ -332,10 +328,6 @@ FROM ada' => array ( 'type-description' => 'array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', ), - 3 => - array ( - 'type-description' => 'array{email: string, adaid: int<-32768, 32767>}', - ), ), ), 'SELECT email, adaid FROM ada LIMIT 1' => @@ -378,6 +370,16 @@ FROM ada' => ), ), ), + 'SELECT email, adaid FROM ada WHERE adaid = \'email@example.org\' and email = \'1\'' => + array ( + 'result' => + array ( + 5 => + array ( + 'type-description' => 'array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', + ), + ), + ), 'SELECT email, adaid FROM ada WHERE adaid = 1' => array ( 'result' => @@ -492,6 +494,16 @@ FROM ada' => ), ), ), + 'SELECT email, adaid FROM ada WHERE email = NULL AND email = \'1001\'' => + array ( + 'result' => + array ( + 5 => + array ( + 'type-description' => 'array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', + ), + ), + ), 'SELECT email, adaid FROM ada WHERE email=\'foo\'' => array ( 'result' => diff --git a/tests/default/config/.phpunit-phpstan-dba-pdo-mysql.cache b/tests/default/config/.phpunit-phpstan-dba-pdo-mysql.cache index 9541a51e..8e5caf13 100644 --- a/tests/default/config/.phpunit-phpstan-dba-pdo-mysql.cache +++ b/tests/default/config/.phpunit-phpstan-dba-pdo-mysql.cache @@ -310,10 +310,6 @@ FROM ada' => array ( 'type-description' => 'array{email: string, 0: string}', ), - 3 => - array ( - 'type-description' => 'array{email: string}', - ), ), ), 'SELECT email adaid WHERE gesperrt freigabe1u1 FROM ada' => @@ -332,10 +328,6 @@ FROM ada' => array ( 'type-description' => 'array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', ), - 3 => - array ( - 'type-description' => 'array{email: string, adaid: int<-32768, 32767>}', - ), ), ), 'SELECT email, adaid FROM ada LIMIT 1' => @@ -378,6 +370,16 @@ FROM ada' => ), ), ), + 'SELECT email, adaid FROM ada WHERE adaid = \'email@example.org\' and email = \'1\'' => + array ( + 'result' => + array ( + 5 => + array ( + 'type-description' => 'array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', + ), + ), + ), 'SELECT email, adaid FROM ada WHERE adaid = 1' => array ( 'result' => @@ -492,6 +494,16 @@ FROM ada' => ), ), ), + 'SELECT email, adaid FROM ada WHERE email = NULL AND email = \'1001\'' => + array ( + 'result' => + array ( + 5 => + array ( + 'type-description' => 'array{email: string, 0: string, adaid: int<-32768, 32767>, 1: int<-32768, 32767>}', + ), + ), + ), 'SELECT email, adaid FROM ada WHERE email=\'foo\'' => array ( 'result' => diff --git a/tests/default/data/pdo-stmt-execute.php b/tests/default/data/pdo-stmt-execute.php index 4ad74a7c..060b36a0 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) { @@ -38,6 +39,7 @@ public function execute(PDO $pdo) 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)