Skip to content

Commit 018ea3c

Browse files
staabmclxmstaab
andauthored
added MysqliEscapeStringDynamicReturnTypeExtension (#28)
Co-authored-by: Markus Staab <[email protected]>
1 parent 88cdf9d commit 018ea3c

File tree

4 files changed

+109
-0
lines changed

4 files changed

+109
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ This extension provides following features:
55
* `PDO->query` knows the array shape of the returned results and therefore can return a generic `PDOStatement`
66
* `mysqli->query` knows the array shape of the returned results and therefore can return a generic `mysqli_result`
77
* `SyntaxErrorInQueryMethodRule` can inspect sql queries and detect syntax errors - `SyntaxErrorInQueryFunctionRule` can do the same for functions
8+
* `mysqli_real_escape_string` and `mysqli->real_escape_string` dynamic return type extensions
89

910
[see the unit-testsuite](https://github.com/staabm/phpstan-dba/tree/main/tests/data) to get a feeling about the current featureset.
1011

config/extensions.neon

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,9 @@ services:
1414
tags:
1515
- phpstan.broker.dynamicMethodReturnTypeExtension
1616
- phpstan.broker.dynamicFunctionReturnTypeExtension
17+
18+
-
19+
class: staabm\PHPStanDba\Extensions\MysqliEscapeStringDynamicReturnTypeExtension
20+
tags:
21+
- phpstan.broker.dynamicMethodReturnTypeExtension
22+
- phpstan.broker.dynamicFunctionReturnTypeExtension
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace staabm\PHPStanDba\Extensions;
6+
7+
use mysqli;
8+
use PhpParser\Node\Expr\FuncCall;
9+
use PhpParser\Node\Expr\MethodCall;
10+
use PHPStan\Analyser\Scope;
11+
use PHPStan\Reflection\FunctionReflection;
12+
use PHPStan\Reflection\MethodReflection;
13+
use PHPStan\Reflection\ParametersAcceptorSelector;
14+
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
15+
use PHPStan\Type\Accessory\AccessoryNumericStringType;
16+
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
17+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
18+
use PHPStan\Type\IntersectionType;
19+
use PHPStan\Type\StringType;
20+
use PHPStan\Type\Type;
21+
22+
final class MysqliEscapeStringDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension, DynamicFunctionReturnTypeExtension
23+
{
24+
public function getClass(): string
25+
{
26+
return mysqli::class;
27+
}
28+
29+
public function isMethodSupported(MethodReflection $methodReflection): bool
30+
{
31+
return 'real_escape_string' === $methodReflection->getName();
32+
}
33+
34+
public function isFunctionSupported(FunctionReflection $functionReflection): bool
35+
{
36+
return 'mysqli_real_escape_string' === $functionReflection->getName();
37+
}
38+
39+
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): Type
40+
{
41+
$args = $functionCall->getArgs();
42+
if (\count($args) < 2) {
43+
return ParametersAcceptorSelector::selectSingle($functionReflection->getVariants())->getReturnType();
44+
}
45+
46+
$argType = $scope->getType($args[1]->value);
47+
48+
return $this->inferType($argType);
49+
}
50+
51+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
52+
{
53+
$args = $methodCall->getArgs();
54+
if (0 === \count($args)) {
55+
return ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
56+
}
57+
58+
$argType = $scope->getType($args[0]->value);
59+
60+
return $this->inferType($argType);
61+
}
62+
63+
private function inferType(Type $argType): Type
64+
{
65+
$intersection = [new StringType()];
66+
67+
if ($argType->isNumericString()->yes()) {
68+
// a numeric string is by definition non-empty. therefore don't combine the e accessories
69+
$intersection[] = new AccessoryNumericStringType();
70+
} elseif ($argType->isNonEmptyString()->yes()) {
71+
$intersection[] = new AccessoryNonEmptyStringType();
72+
}
73+
74+
if (\count($intersection) > 1) {
75+
return new IntersectionType($intersection);
76+
}
77+
78+
return new StringType();
79+
}
80+
}

tests/data/mysqli.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,26 @@ public function fnQuery(mysqli $mysqli, string $query)
4848
$result = mysqli_query($mysqli, $query);
4949
assertType('bool|mysqli_result', $result);
5050
}
51+
52+
/**
53+
* @param numeric $n
54+
* @param non-empty-string $nonE
55+
* @param numeric-string $numericString
56+
*/
57+
public function escape(mysqli $mysqli, int $i, float $f, $n, string $s, $nonE, string $numericString)
58+
{
59+
assertType('numeric-string', mysqli_real_escape_string($mysqli, (string) $i));
60+
assertType('numeric-string', mysqli_real_escape_string($mysqli, (string) $f));
61+
assertType('numeric-string', mysqli_real_escape_string($mysqli, (string) $n));
62+
assertType('numeric-string', mysqli_real_escape_string($mysqli, $numericString));
63+
assertType('non-empty-string', mysqli_real_escape_string($mysqli, $nonE));
64+
assertType('string', mysqli_real_escape_string($mysqli, $s));
65+
66+
assertType('numeric-string', $mysqli->real_escape_string((string) $i));
67+
assertType('numeric-string', $mysqli->real_escape_string((string) $f));
68+
assertType('numeric-string', $mysqli->real_escape_string((string) $n));
69+
assertType('numeric-string', $mysqli->real_escape_string($numericString));
70+
assertType('non-empty-string', $mysqli->real_escape_string($nonE));
71+
assertType('string', $mysqli->real_escape_string($s));
72+
}
5173
}

0 commit comments

Comments
 (0)