From 398a7ab281052b0843d3129e12247a7f8a1c8347 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 10 Jan 2022 15:27:44 +0100 Subject: [PATCH] wip --- .phpstan-dba.cache | 93 +++++++++++++++++++ .../PdoStatementExecuteErrorMethodRule.php | 80 +++++++++++++++- ...PdoStatementExecuteErrorMethodRuleTest.php | 2 - tests/data/pdo-stmt-execute-error.php | 4 +- 4 files changed, 171 insertions(+), 8 deletions(-) diff --git a/.phpstan-dba.cache b/.phpstan-dba.cache index 72b975748..023103766 100644 --- a/.phpstan-dba.cache +++ b/.phpstan-dba.cache @@ -430,6 +430,99 @@ )), ), ), + 'SELECT email, adaid FROM ada WHERE adaid = \'hello world\'' => + array ( + 'error' => NULL, + 'result' => + array ( + 3 => + PHPStan\Type\Constant\ConstantArrayType::__set_state(array( + 'keyType' => + PHPStan\Type\UnionType::__set_state(array( + 'types' => + array ( + 0 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), + 1 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 1, + )), + 2 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'adaid', + 'isClassString' => false, + )), + 3 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'email', + 'isClassString' => false, + )), + ), + )), + 'itemType' => + PHPStan\Type\UnionType::__set_state(array( + 'types' => + array ( + 0 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => 0, + 'max' => 4294967295, + )), + 1 => + PHPStan\Type\StringType::__set_state(array( + )), + ), + )), + 'allArrays' => NULL, + 'keyTypes' => + array ( + 0 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'email', + 'isClassString' => false, + )), + 1 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), + 2 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'adaid', + 'isClassString' => false, + )), + 3 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 1, + )), + ), + 'valueTypes' => + array ( + 0 => + PHPStan\Type\StringType::__set_state(array( + )), + 1 => + PHPStan\Type\StringType::__set_state(array( + )), + 2 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => 0, + 'max' => 4294967295, + )), + 3 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => 0, + 'max' => 4294967295, + )), + ), + 'nextAutoIndex' => 2, + 'optionalKeys' => + array ( + ), + )), + ), + ), 'SELECT email, adaid FROM ada WHERE adaid = 1' => array ( 'error' => NULL, diff --git a/src/Rules/PdoStatementExecuteErrorMethodRule.php b/src/Rules/PdoStatementExecuteErrorMethodRule.php index 32dff9006..7471e1110 100644 --- a/src/Rules/PdoStatementExecuteErrorMethodRule.php +++ b/src/Rules/PdoStatementExecuteErrorMethodRule.php @@ -6,6 +6,7 @@ use PDOStatement; use PhpParser\Node; +use PhpParser\Node\Expr; use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; @@ -13,8 +14,13 @@ use PHPStan\Rules\RuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; +use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantStringType; +use PHPStan\Type\Type; +use PHPStan\Type\VerbosityLevel; use staabm\PHPStanDba\PdoReflection\PdoStatementReflection; use staabm\PHPStanDba\QueryReflection\QueryReflection; +use staabm\PHPStanDba\QueryReflection\QueryReflector; /** * @implements Rule @@ -81,13 +87,13 @@ private function checkErrors(MethodReflection $methodReflection, MethodCall $met ]; } - return $this->checkParameterValues($methodCall, $scope, $queryString, $placeholderCount); + return $this->checkParameterValues($methodCall, $scope, $queryExpr, $queryString, $placeholderCount); } /** * @return RuleError[] */ - private function checkParameterValues(MethodCall $methodCall, Scope $scope, string $queryString, int $placeholderCount): array + private function checkParameterValues(MethodCall $methodCall, Scope $scope, Expr $queryExpr, string $queryString, int $placeholderCount): array { $queryReflection = new QueryReflection(); $args = $methodCall->getArgs(); @@ -97,8 +103,11 @@ private function checkParameterValues(MethodCall $methodCall, Scope $scope, stri if (null === $parameters) { return []; } - $parameterCount = \count($parameters); + if (!$parameterTypes instanceof ConstantArrayType) { + throw new ShouldNotHappenException(); + } + $parameterCount = \count($parameters); if ($parameterCount !== $placeholderCount) { if (1 === $parameterCount) { return [ @@ -125,9 +134,72 @@ private function checkParameterValues(MethodCall $methodCall, Scope $scope, stri } } - return $errors; + if ($errors != []) { + return $errors; + } + + return $this->checkParameterTypes($methodCall, $queryExpr, $parameterTypes, $scope); } + /** + * @return RuleError[] + */ + private function checkParameterTypes(MethodCall $methodCall, Expr $queryExpr, ConstantArrayType $parameterTypes, Scope $scope): array + { + $queryReflection = new QueryReflection(); + $queryString = $queryReflection->resolvePreparedQueryString($queryExpr, $parameterTypes, $scope); + if (null === $queryString) { + return []; + } + + $resultType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_ASSOC); + if (!$resultType instanceof ConstantArrayType) { + return []; + } + + var_dump($queryString); + foreach ($resultType->getKeyTypes() as $keyType) { + var_dump($keyType->describe(VerbosityLevel::value())); + } + + $errors = []; + $keyTypes = $parameterTypes->getKeyTypes(); + $valueTypes = $parameterTypes->getValueTypes(); + + foreach ($keyTypes as $i => $keyType) { + if (!$keyType instanceof ConstantStringType) { + continue; + } + + $columnName = $keyType->getValue(); + ltrim($columnName, ':'); + + var_dump($keyType->describe(VerbosityLevel::precise())); + var_dump($resultType->hasOffsetValueType($keyType)->describe()); + if (!$resultType->hasOffsetValueType($keyType)->yes()) { + // we only know types of columns which are selected. + continue; + } + + var_dump($resultType->getOffsetValueType($keyType)->describe(VerbosityLevel::precise())); + var_dump($valueTypes[$i]->describe(VerbosityLevel::precise())); + if ($resultType->getOffsetValueType($keyType)->isSuperTypeOf($valueTypes[$i])->yes()) { + continue; + } + + $errors[] = RuleErrorBuilder::message( + sprintf( + 'Value %s with type %s is given to execute(), but the query expects %s.', + $placeholderName, + $resultType->getOffsetValueType($keyType)->describe(VerbosityLevel::precise()), + $valueTypes[$i]->describe(VerbosityLevel::precise()) + ) + )->line($methodCall->getLine())->build(); + } + + return $errors; + } + /** * @return 0|positive-int */ diff --git a/tests/PdoStatementExecuteErrorMethodRuleTest.php b/tests/PdoStatementExecuteErrorMethodRuleTest.php index cc43b4aa0..f045142a1 100644 --- a/tests/PdoStatementExecuteErrorMethodRuleTest.php +++ b/tests/PdoStatementExecuteErrorMethodRuleTest.php @@ -37,7 +37,6 @@ public function testSyntaxErrorInQueryRule(): void 'Value :wrongParamName is given to execute(), but the query does not containt this placeholder.', 15, ], - /* [ 'Query expects placeholder :adaid, but it is missing from values given to execute().', 18, @@ -46,7 +45,6 @@ public function testSyntaxErrorInQueryRule(): void 'Value :wrongParamValue is given to execute(), but the query does not containt this placeholder.', 18, ], - */ [ 'Query expects 1 placeholders, but no values are given to execute().', 21, diff --git a/tests/data/pdo-stmt-execute-error.php b/tests/data/pdo-stmt-execute-error.php index a7b1e2a6a..6961ebcd1 100644 --- a/tests/data/pdo-stmt-execute-error.php +++ b/tests/data/pdo-stmt-execute-error.php @@ -14,8 +14,8 @@ public function errors(PDO $pdo) $stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE adaid = :adaid'); $stmt->execute([':wrongParamName' => 1]); - // $stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE adaid = :adaid'); - // $stmt->execute([':adaid' => 'hello world']); // wrong param type + $stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE adaid = :adaid'); + $stmt->execute([':adaid' => 'hello world']); // wrong param type $stmt = $pdo->prepare('SELECT email, adaid FROM ada WHERE adaid = :adaid'); $stmt->execute(); // missing parameter