Skip to content

Commit 9bcdb59

Browse files
staabmclxmstaab
andauthored
support conditional number of placeholders (#187)
Co-authored-by: Markus Staab <[email protected]>
1 parent a6c1787 commit 9bcdb59

File tree

5 files changed

+95
-188
lines changed

5 files changed

+95
-188
lines changed

.phpunit-phpstan-dba.cache

Lines changed: 0 additions & 146 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/QueryReflection/PlaceholderValidation.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,16 @@ private function checkErrors(string $queryString, array $parameters): iterable
3939
$queryReflection = new QueryReflection();
4040
$placeholderCount = $queryReflection->countPlaceholders($queryString);
4141

42-
if (0 === \count($parameters)) {
42+
$parameterCount = \count($parameters);
43+
$minParameterCount = 0;
44+
foreach ($parameters as $parameter) {
45+
if ($parameter->isOptional) {
46+
continue;
47+
}
48+
++$minParameterCount;
49+
}
50+
51+
if (0 === $parameterCount && 0 === $minParameterCount) {
4352
if (0 === $placeholderCount) {
4453
return;
4554
}
@@ -58,7 +67,7 @@ private function checkErrors(string $queryString, array $parameters): iterable
5867
}
5968

6069
/**
61-
* @param non-empty-array<string|int, Parameter> $parameters
70+
* @param array<string|int, Parameter> $parameters
6271
*
6372
* @return iterable<string>
6473
*/

src/QueryReflection/QueryReflection.php

Lines changed: 62 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ public function getResultType(string $queryString, int $fetchType): ?Type
6969
}
7070

7171
/**
72-
* @throws UnresolvableQueryException
73-
*
7472
* @return iterable<string>
73+
*
74+
* @throws UnresolvableQueryException
7575
*/
7676
public function resolvePreparedQueryStrings(Expr $queryExpr, Type $parameterTypes, Scope $scope): iterable
7777
{
@@ -118,9 +118,9 @@ public function resolvePreparedQueryString(Expr $queryExpr, Type $parameterTypes
118118
}
119119

120120
/**
121-
* @throws UnresolvableQueryException
122-
*
123121
* @return iterable<string>
122+
*
123+
* @throws UnresolvableQueryException
124124
*/
125125
public function resolveQueryStrings(Expr $queryExpr, Scope $scope): iterable
126126
{
@@ -203,55 +203,78 @@ public static function getQueryType(string $query): ?string
203203
/**
204204
* Resolves prepared statement parameter types.
205205
*
206-
* @throws UnresolvableQueryException
207-
*
208206
* @return array<string|int, Parameter>|null
207+
*
208+
* @throws UnresolvableQueryException
209209
*/
210210
public function resolveParameters(Type $parameterTypes): ?array
211211
{
212212
$parameters = [];
213213

214-
if ($parameterTypes instanceof ConstantArrayType) {
215-
$keyTypes = $parameterTypes->getKeyTypes();
216-
$valueTypes = $parameterTypes->getValueTypes();
217-
$optionalKeys = $parameterTypes->getOptionalKeys();
218-
219-
foreach ($keyTypes as $i => $keyType) {
220-
$isOptional = \in_array($i, $optionalKeys, true);
221-
222-
if ($keyType instanceof ConstantStringType) {
223-
$placeholderName = $keyType->getValue();
224-
225-
if ('' === $placeholderName) {
226-
throw new ShouldNotHappenException('Empty placeholder name');
227-
}
228-
229-
$param = new Parameter(
230-
$placeholderName,
231-
$valueTypes[$i],
232-
QuerySimulation::simulateParamValueType($valueTypes[$i], true),
233-
$isOptional
234-
);
235-
236-
$parameters[$param->name] = $param;
237-
} elseif ($keyType instanceof ConstantIntegerType) {
238-
$param = new Parameter(
239-
null,
240-
$valueTypes[$i],
241-
QuerySimulation::simulateParamValueType($valueTypes[$i], true),
242-
$isOptional
243-
);
244-
245-
$parameters[$keyType->getValue()] = $param;
246-
}
214+
if ($parameterTypes instanceof UnionType) {
215+
foreach (TypeUtils::getConstantArrays($parameterTypes) as $constantArray) {
216+
$parameters = $parameters + $this->resolveConstantArray($constantArray, true);
247217
}
248218

249219
return $parameters;
250220
}
251221

222+
if ($parameterTypes instanceof ConstantArrayType) {
223+
return $this->resolveConstantArray($parameterTypes, false);
224+
}
225+
252226
return null;
253227
}
254228

229+
/**
230+
* @return array<string|int, Parameter>
231+
*
232+
* @throws UnresolvableQueryException
233+
*/
234+
private function resolveConstantArray(ConstantArrayType $parameterTypes, bool $forceOptional): array
235+
{
236+
$parameters = [];
237+
238+
$keyTypes = $parameterTypes->getKeyTypes();
239+
$valueTypes = $parameterTypes->getValueTypes();
240+
$optionalKeys = $parameterTypes->getOptionalKeys();
241+
242+
foreach ($keyTypes as $i => $keyType) {
243+
$isOptional = \in_array($i, $optionalKeys, true);
244+
if ($forceOptional) {
245+
$isOptional = true;
246+
}
247+
248+
if ($keyType instanceof ConstantStringType) {
249+
$placeholderName = $keyType->getValue();
250+
251+
if ('' === $placeholderName) {
252+
throw new ShouldNotHappenException('Empty placeholder name');
253+
}
254+
255+
$param = new Parameter(
256+
$placeholderName,
257+
$valueTypes[$i],
258+
QuerySimulation::simulateParamValueType($valueTypes[$i], true),
259+
$isOptional
260+
);
261+
262+
$parameters[$param->name] = $param;
263+
} elseif ($keyType instanceof ConstantIntegerType) {
264+
$param = new Parameter(
265+
null,
266+
$valueTypes[$i],
267+
QuerySimulation::simulateParamValueType($valueTypes[$i], true),
268+
$isOptional
269+
);
270+
271+
$parameters[$keyType->getValue()] = $param;
272+
}
273+
}
274+
275+
return $parameters;
276+
}
277+
255278
/**
256279
* @param array<string|int, Parameter> $parameters
257280
*/

tests/SyntaxErrorInPreparedStatementMethodRuleTest.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ public function testSyntaxErrorInQueryRule(): void
5757
'Value :gesperrt is given, but the query does not contain this placeholder.',
5858
137,
5959
],
60+
[
61+
'Query expects placeholder :name, but it is missing from values given.',
62+
307,
63+
],
64+
[
65+
'Query expects 2 placeholders, but 0-1 values are given.',
66+
307,
67+
],
6068
]);
6169
}
6270
}

tests/data/syntax-error-in-prepared-statement.php

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,8 @@ public function noErrorOnLockedRead(Connection $connection, int $limit, int $off
283283
', [':gesperrt' => 1, ':limit' => $limit, ':offset' => $offset]);
284284
}
285285

286-
public function noErrorInBug174(Connection $connection, string $name, ?int $gesperrt = null, ?int $adaid = null) {
286+
public function noErrorInBug174(Connection $connection, string $name, ?int $gesperrt = null, ?int $adaid = null)
287+
{
287288
$sql = 'SELECT adaid FROM ada WHERE email = :name';
288289
$args = ['name' => $name];
289290
if (null !== $gesperrt) {
@@ -293,4 +294,16 @@ public function noErrorInBug174(Connection $connection, string $name, ?int $gesp
293294

294295
$connection->preparedQuery($sql, $args);
295296
}
297+
298+
public function conditionalNumberOfPlaceholders(Connection $connection, string $name, ?int $gesperrt = null, ?int $adaid = null)
299+
{
300+
$sql = 'SELECT adaid FROM ada WHERE email = :name';
301+
$args = [];
302+
if (null !== $gesperrt) {
303+
$sql .= ' AND gesperrt = :gesperrt';
304+
$args['gesperrt'] = $gesperrt;
305+
}
306+
307+
$connection->preparedQuery($sql, $args);
308+
}
296309
}

0 commit comments

Comments
 (0)