diff --git a/README.md b/README.md index a39b5af46..0b00915d0 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ This extension provides following features: * `PDO->query` knows the array shape of the returned results and therefore can return a generic `PDOStatement` * `mysqli->query` knows the array shape of the returned results and therefore can return a generic `mysqli_result` * `SyntaxErrorInQueryMethodRule` can inspect sql queries and detect syntax errors - `SyntaxErrorInQueryFunctionRule` can do the same for functions +* `mysqli_real_escape_string` and `mysqli->real_escape_string` dynamic return type extensions [see the unit-testsuite](https://github.com/staabm/phpstan-dba/tree/main/tests/data) to get a feeling about the current featureset. diff --git a/config/extensions.neon b/config/extensions.neon index 52cf5578d..d030186db 100644 --- a/config/extensions.neon +++ b/config/extensions.neon @@ -14,3 +14,9 @@ services: tags: - phpstan.broker.dynamicMethodReturnTypeExtension - phpstan.broker.dynamicFunctionReturnTypeExtension + + - + class: staabm\PHPStanDba\Extensions\MysqliEscapeStringDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - phpstan.broker.dynamicFunctionReturnTypeExtension diff --git a/src/Extensions/MysqliEscapeStringDynamicReturnTypeExtension.php b/src/Extensions/MysqliEscapeStringDynamicReturnTypeExtension.php new file mode 100644 index 000000000..6f8be9daa --- /dev/null +++ b/src/Extensions/MysqliEscapeStringDynamicReturnTypeExtension.php @@ -0,0 +1,80 @@ +getName(); + } + + public function isFunctionSupported(FunctionReflection $functionReflection): bool + { + return 'mysqli_real_escape_string' === $functionReflection->getName(); + } + + public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type + { + $args = $functionCall->getArgs(); + if (\count($args) < 2) { + return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType(); + } + + $argType = $scope->getType($args[1]->value); + + return $this->inferType($argType); + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + $args = $methodCall->getArgs(); + if (0 === \count($args)) { + return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); + } + + $argType = $scope->getType($args[0]->value); + + return $this->inferType($argType); + } + + private function inferType(Type $argType): Type + { + $intersection = [new StringType()]; + + if ($argType->isNumericString()->yes()) { + // a numeric string is by definition non-empty. therefore don't combine the e accessories + $intersection[] = new AccessoryNumericStringType(); + } elseif ($argType->isNonEmptyString()->yes()) { + $intersection[] = new AccessoryNonEmptyStringType(); + } + + if (\count($intersection) > 1) { + return new IntersectionType($intersection); + } + + return new StringType(); + } +} diff --git a/tests/data/mysqli.php b/tests/data/mysqli.php index a8dbdbdbd..94122cb3f 100644 --- a/tests/data/mysqli.php +++ b/tests/data/mysqli.php @@ -48,4 +48,26 @@ public function fnQuery(mysqli $mysqli, string $query) $result = mysqli_query($mysqli, $query); assertType('bool|mysqli_result', $result); } + + /** + * @param numeric $n + * @param non-empty-string $nonE + * @param numeric-string $numericString + */ + public function escape(mysqli $mysqli, int $i, float $f, $n, string $s, $nonE, string $numericString) + { + assertType('numeric-string', mysqli_real_escape_string($mysqli, (string) $i)); + assertType('numeric-string', mysqli_real_escape_string($mysqli, (string) $f)); + assertType('numeric-string', mysqli_real_escape_string($mysqli, (string) $n)); + assertType('numeric-string', mysqli_real_escape_string($mysqli, $numericString)); + assertType('non-empty-string', mysqli_real_escape_string($mysqli, $nonE)); + assertType('string', mysqli_real_escape_string($mysqli, $s)); + + assertType('numeric-string', $mysqli->real_escape_string((string) $i)); + assertType('numeric-string', $mysqli->real_escape_string((string) $f)); + assertType('numeric-string', $mysqli->real_escape_string((string) $n)); + assertType('numeric-string', $mysqli->real_escape_string($numericString)); + assertType('non-empty-string', $mysqli->real_escape_string($nonE)); + assertType('string', $mysqli->real_escape_string($s)); + } }