Skip to content

Commit 9cd1fe4

Browse files
staabmclxmstaab
andauthored
extracted QueryReflection->resolvePreparedQuerystring() (#54)
Co-authored-by: Markus Staab <m.staab@complex-it.de>
1 parent 4f840e8 commit 9cd1fe4

File tree

2 files changed

+93
-82
lines changed

2 files changed

+93
-82
lines changed

src/Extensions/PdoExecuteTypeSpecifyingExtension.php

Lines changed: 5 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@
1818
use PHPStan\Analyser\TypeSpecifierContext;
1919
use PHPStan\Reflection\MethodReflection;
2020
use PHPStan\ShouldNotHappenException;
21-
use PHPStan\Type\Constant\ConstantArrayType;
22-
use PHPStan\Type\Constant\ConstantIntegerType;
23-
use PHPStan\Type\Constant\ConstantStringType;
24-
use PHPStan\Type\ConstantScalarType;
2521
use PHPStan\Type\Generic\GenericObjectType;
2622
use PHPStan\Type\MethodTypeSpecifyingExtension;
2723
use PHPStan\Type\Type;
@@ -76,27 +72,26 @@ private function inferStatementType(MethodCall $methodCall, Scope $scope): ?Type
7672
return null;
7773
}
7874

79-
$parameterTypes = $scope->getType($args[0]->value);
80-
$parameters = $this->resolveParameters($parameterTypes);
8175
$queryExpr = $this->findQueryStringExpression($methodCall);
8276
if (null === $queryExpr) {
8377
return null;
8478
}
8579

8680
// resolve query parameter from "prepare"
8781
if ($queryExpr instanceof MethodCall) {
88-
$args = $queryExpr->getArgs();
89-
$queryExpr = $args[0]->value;
82+
$queryArgs = $queryExpr->getArgs();
83+
$queryExpr = $queryArgs[0]->value;
9084
}
9185

86+
$parameterTypes = $scope->getType($args[0]->value);
87+
9288
$queryReflection = new QueryReflection();
93-
$queryString = $queryReflection->resolveQueryString($queryExpr, $scope);
89+
$queryString = $queryReflection->resolvePreparedQueryString($queryExpr, $parameterTypes, $scope);
9490
if (null === $queryString) {
9591
return null;
9692
}
9793

9894
$reflectionFetchType = QueryReflector::FETCH_TYPE_BOTH;
99-
$queryString = $this->replaceParameters($queryString, $parameters);
10095
$resultType = $queryReflection->getResultType($queryString, $reflectionFetchType);
10196

10297
if ($resultType) {
@@ -106,73 +101,6 @@ private function inferStatementType(MethodCall $methodCall, Scope $scope): ?Type
106101
return null;
107102
}
108103

109-
/**
110-
* @param array<string|int, scalar|null> $parameters
111-
*/
112-
private function replaceParameters(string $queryString, array $parameters): string
113-
{
114-
$replaceFirst = function (string $haystack, string $needle, string $replace) {
115-
$pos = strpos($haystack, $needle);
116-
if (false !== $pos) {
117-
return substr_replace($haystack, $replace, $pos, \strlen($needle));
118-
}
119-
120-
return $haystack;
121-
};
122-
123-
foreach ($parameters as $placeholderKey => $value) {
124-
if (\is_string($value)) {
125-
// XXX escaping
126-
$value = "'".$value."'";
127-
} elseif (null === $value) {
128-
$value = 'NULL';
129-
} else {
130-
$value = (string) $value;
131-
}
132-
133-
if (\is_int($placeholderKey)) {
134-
$queryString = $replaceFirst($queryString, '?', $value);
135-
} else {
136-
$queryString = str_replace($placeholderKey, $value, $queryString);
137-
}
138-
}
139-
140-
return $queryString;
141-
}
142-
143-
/**
144-
* @return array<string|int, scalar|null>
145-
*/
146-
private function resolveParameters(Type $parameterTypes): array
147-
{
148-
$parameters = [];
149-
150-
if ($parameterTypes instanceof ConstantArrayType) {
151-
$keyTypes = $parameterTypes->getKeyTypes();
152-
$valueTypes = $parameterTypes->getValueTypes();
153-
154-
foreach ($keyTypes as $i => $keyType) {
155-
if ($keyType instanceof ConstantStringType) {
156-
$placeholderName = $keyType->getValue();
157-
158-
if (!str_starts_with($placeholderName, ':')) {
159-
$placeholderName = ':'.$placeholderName;
160-
}
161-
162-
if ($valueTypes[$i] instanceof ConstantScalarType) {
163-
$parameters[$placeholderName] = $valueTypes[$i]->getValue();
164-
}
165-
} elseif ($keyType instanceof ConstantIntegerType) {
166-
if ($valueTypes[$i] instanceof ConstantScalarType) {
167-
$parameters[$keyType->getValue()] = $valueTypes[$i]->getValue();
168-
}
169-
}
170-
}
171-
}
172-
173-
return $parameters;
174-
}
175-
176104
private function findQueryStringExpression(MethodCall $methodCall): ?Expr
177105
{
178106
// todo: use astral simpleNameResolver

src/QueryReflection/QueryReflection.php

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
use PhpParser\Node\Expr\BinaryOp\Concat;
99
use PHPStan\Analyser\Scope;
1010
use PHPStan\Type\BooleanType;
11+
use PHPStan\Type\Constant\ConstantArrayType;
12+
use PHPStan\Type\Constant\ConstantIntegerType;
13+
use PHPStan\Type\Constant\ConstantStringType;
1114
use PHPStan\Type\ConstantScalarType;
1215
use PHPStan\Type\FloatType;
1316
use PHPStan\Type\IntegerType;
@@ -77,11 +80,24 @@ private function builtSimulatedQuery(string $queryString): ?string
7780
return $queryString;
7881
}
7982

80-
public function resolveQueryString(Expr $expr, Scope $scope): ?string
83+
public function resolvePreparedQueryString(Expr $queryExpr, Type $parameterTypes, Scope $scope): ?string
8184
{
82-
if ($expr instanceof Concat) {
83-
$left = $expr->left;
84-
$right = $expr->right;
85+
$queryString = $this->resolveQueryString($queryExpr, $scope);
86+
87+
if (null === $queryString) {
88+
return null;
89+
}
90+
91+
$parameters = $this->resolveParameters($parameterTypes);
92+
93+
return $this->replaceParameters($queryString, $parameters);
94+
}
95+
96+
public function resolveQueryString(Expr $queryExpr, Scope $scope): ?string
97+
{
98+
if ($queryExpr instanceof Concat) {
99+
$left = $queryExpr->left;
100+
$right = $queryExpr->right;
85101

86102
$leftString = $this->resolveQueryString($left, $scope);
87103
$rightString = $this->resolveQueryString($right, $scope);
@@ -93,7 +109,7 @@ public function resolveQueryString(Expr $expr, Scope $scope): ?string
93109
return $leftString.$rightString;
94110
}
95111

96-
$type = $scope->getType($expr);
112+
$type = $scope->getType($queryExpr);
97113
if ($type instanceof ConstantScalarType) {
98114
return (string) $type->getValue();
99115
}
@@ -135,6 +151,73 @@ private function getQueryType(string $query): ?string
135151
return null;
136152
}
137153

154+
/**
155+
* @return array<string|int, scalar|null>
156+
*/
157+
private function resolveParameters(Type $parameterTypes): array
158+
{
159+
$parameters = [];
160+
161+
if ($parameterTypes instanceof ConstantArrayType) {
162+
$keyTypes = $parameterTypes->getKeyTypes();
163+
$valueTypes = $parameterTypes->getValueTypes();
164+
165+
foreach ($keyTypes as $i => $keyType) {
166+
if ($keyType instanceof ConstantStringType) {
167+
$placeholderName = $keyType->getValue();
168+
169+
if (!str_starts_with($placeholderName, ':')) {
170+
$placeholderName = ':'.$placeholderName;
171+
}
172+
173+
if ($valueTypes[$i] instanceof ConstantScalarType) {
174+
$parameters[$placeholderName] = $valueTypes[$i]->getValue();
175+
}
176+
} elseif ($keyType instanceof ConstantIntegerType) {
177+
if ($valueTypes[$i] instanceof ConstantScalarType) {
178+
$parameters[$keyType->getValue()] = $valueTypes[$i]->getValue();
179+
}
180+
}
181+
}
182+
}
183+
184+
return $parameters;
185+
}
186+
187+
/**
188+
* @param array<string|int, scalar|null> $parameters
189+
*/
190+
private function replaceParameters(string $queryString, array $parameters): string
191+
{
192+
$replaceFirst = function (string $haystack, string $needle, string $replace) {
193+
$pos = strpos($haystack, $needle);
194+
if (false !== $pos) {
195+
return substr_replace($haystack, $replace, $pos, \strlen($needle));
196+
}
197+
198+
return $haystack;
199+
};
200+
201+
foreach ($parameters as $placeholderKey => $value) {
202+
if (\is_string($value)) {
203+
// XXX escaping
204+
$value = "'".$value."'";
205+
} elseif (null === $value) {
206+
$value = 'NULL';
207+
} else {
208+
$value = (string) $value;
209+
}
210+
211+
if (\is_int($placeholderKey)) {
212+
$queryString = $replaceFirst($queryString, '?', $value);
213+
} else {
214+
$queryString = str_replace($placeholderKey, $value, $queryString);
215+
}
216+
}
217+
218+
return $queryString;
219+
}
220+
138221
private static function reflector(): QueryReflector
139222
{
140223
if (null === self::$reflector) {

0 commit comments

Comments
 (0)