Skip to content

Commit ed42d9c

Browse files
staabmclxmstaab
andauthored
report unresolvable queries/params in debug-mode (#164)
Co-authored-by: Markus Staab <[email protected]>
1 parent 9c5bc16 commit ed42d9c

17 files changed

+421
-18
lines changed

.phpstan-dba.cache

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

src/DbaException.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
namespace staabm\PHPStanDba;
44

5-
final class DbaException extends \RuntimeException
5+
class DbaException extends \RuntimeException
66
{
77
}

src/QueryReflection/QueryReflection.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use PHPStan\Type\UnionType;
1616
use staabm\PHPStanDba\DbaException;
1717
use staabm\PHPStanDba\Error;
18+
use staabm\PHPStanDba\UnresolvableQueryException;
1819

1920
final class QueryReflection
2021
{
@@ -67,6 +68,8 @@ public function getResultType(string $queryString, int $fetchType): ?Type
6768
}
6869

6970
/**
71+
* @throws UnresolvableQueryException
72+
*
7073
* @return iterable<string>
7174
*/
7275
public function resolvePreparedQueryStrings(Expr $queryExpr, Type $parameterTypes, Scope $scope): iterable
@@ -94,6 +97,9 @@ public function resolvePreparedQueryStrings(Expr $queryExpr, Type $parameterType
9497
}
9598
}
9699

100+
/**
101+
* @throws UnresolvableQueryException
102+
*/
97103
public function resolvePreparedQueryString(Expr $queryExpr, Type $parameterTypes, Scope $scope): ?string
98104
{
99105
$queryString = $this->resolveQueryString($queryExpr, $scope);
@@ -111,6 +117,8 @@ public function resolvePreparedQueryString(Expr $queryExpr, Type $parameterTypes
111117
}
112118

113119
/**
120+
* @throws UnresolvableQueryException
121+
*
114122
* @return iterable<string>
115123
*/
116124
public function resolveQueryStrings(Expr $queryExpr, Scope $scope): iterable
@@ -131,6 +139,9 @@ public function resolveQueryStrings(Expr $queryExpr, Scope $scope): iterable
131139
}
132140
}
133141

142+
/**
143+
* @throws UnresolvableQueryException
144+
*/
134145
public function resolveQueryString(Expr $queryExpr, Scope $scope): ?string
135146
{
136147
if ($queryExpr instanceof Concat) {
@@ -164,6 +175,8 @@ public static function getQueryType(string $query): ?string
164175
}
165176

166177
/**
178+
* @throws UnresolvableQueryException
179+
*
167180
* @return array<string|int, scalar|null>|null
168181
*/
169182
public function resolveParameters(Type $parameterTypes): ?array

src/QueryReflection/QuerySimulation.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@
1515
use PHPStan\Type\Type;
1616
use PHPStan\Type\TypeUtils;
1717
use PHPStan\Type\UnionType;
18+
use PHPStan\Type\VerbosityLevel;
1819
use staabm\PHPStanDba\DbaException;
20+
use staabm\PHPStanDba\UnresolvableQueryException;
1921

2022
/**
2123
* @internal
2224
*/
2325
final class QuerySimulation
2426
{
27+
/**
28+
* @throws UnresolvableQueryException
29+
*/
2530
public static function simulateParamValueType(Type $paramType): ?string
2631
{
2732
if ($paramType instanceof ConstantScalarType) {
@@ -71,6 +76,10 @@ public static function simulateParamValueType(Type $paramType): ?string
7176

7277
// all types which we can't simulate and render a query unresolvable at analysis time
7378
if ($paramType instanceof MixedType || $paramType instanceof StringType || $paramType instanceof IntersectionType) {
79+
if (QueryReflection::getRuntimeConfiguration()->isDebugEnabled()) {
80+
throw new UnresolvableQueryException('Cannot simulate parameter value for type: '.$paramType->describe(VerbosityLevel::precise()));
81+
}
82+
7483
return null;
7584
}
7685

src/Rules/PdoStatementExecuteMethodRule.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use staabm\PHPStanDba\PdoReflection\PdoStatementReflection;
1616
use staabm\PHPStanDba\QueryReflection\PlaceholderValidation;
1717
use staabm\PHPStanDba\QueryReflection\QueryReflection;
18+
use staabm\PHPStanDba\UnresolvableQueryException;
1819

1920
/**
2021
* @implements Rule<MethodCall>
@@ -43,7 +44,7 @@ public function processNode(Node $methodCall, Scope $scope): array
4344
return [];
4445
}
4546

46-
if ('execute' !== $methodReflection->getName()) {
47+
if ('execute' !== strtolower($methodReflection->getName())) {
4748
return [];
4849
}
4950

@@ -68,7 +69,13 @@ private function checkErrors(MethodReflection $methodReflection, MethodCall $met
6869
$parameters = [];
6970
} else {
7071
$parameterTypes = $scope->getType($args[0]->value);
71-
$parameters = $queryReflection->resolveParameters($parameterTypes) ?? [];
72+
try {
73+
$parameters = $queryReflection->resolveParameters($parameterTypes) ?? [];
74+
} catch (UnresolvableQueryException $exception) {
75+
return [
76+
RuleErrorBuilder::message($exception->asRuleMessage())->tip(UnresolvableQueryException::RULE_TIP)->line($methodCall->getLine())->build(),
77+
];
78+
}
7279
}
7380

7481
$errors = [];

src/Rules/SyntaxErrorInPreparedStatementMethodRule.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use PHPStan\Type\ObjectType;
1717
use staabm\PHPStanDba\QueryReflection\PlaceholderValidation;
1818
use staabm\PHPStanDba\QueryReflection\QueryReflection;
19+
use staabm\PHPStanDba\UnresolvableQueryException;
1920

2021
/**
2122
* @implements Rule<CallLike>
@@ -97,7 +98,13 @@ private function checkErrors(CallLike $callLike, Scope $scope): array
9798
$parameterTypes = $scope->getType($args[1]->value);
9899

99100
$queryReflection = new QueryReflection();
100-
$parameters = $queryReflection->resolveParameters($parameterTypes) ?? [];
101+
try {
102+
$parameters = $queryReflection->resolveParameters($parameterTypes) ?? [];
103+
} catch (UnresolvableQueryException $exception) {
104+
return [
105+
RuleErrorBuilder::message($exception->asRuleMessage())->tip(UnresolvableQueryException::RULE_TIP)->line($callLike->getLine())->build(),
106+
];
107+
}
101108

102109
$errors = [];
103110
$placeholderReflection = new PlaceholderValidation();

src/Rules/SyntaxErrorInQueryFunctionRule.php

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use PHPStan\Rules\RuleErrorBuilder;
1313
use staabm\PHPStanDba\QueryReflection\QueryReflection;
1414
use staabm\PHPStanDba\Tests\SyntaxErrorInQueryFunctionRuleTest;
15+
use staabm\PHPStanDba\UnresolvableQueryException;
1516

1617
/**
1718
* @implements Rule<FuncCall>
@@ -77,13 +78,19 @@ public function processNode(Node $node, Scope $scope): array
7778
}
7879

7980
$queryReflection = new QueryReflection();
80-
foreach ($queryReflection->resolveQueryStrings($args[$queryArgPosition]->value, $scope) as $queryString) {
81-
$queryError = $queryReflection->validateQueryString($queryString);
82-
if (null !== $queryError) {
83-
return [
84-
RuleErrorBuilder::message($queryError->asRuleMessage())->line($node->getLine())->build(),
85-
];
81+
try {
82+
foreach ($queryReflection->resolveQueryStrings($args[$queryArgPosition]->value, $scope) as $queryString) {
83+
$queryError = $queryReflection->validateQueryString($queryString);
84+
if (null !== $queryError) {
85+
return [
86+
RuleErrorBuilder::message($queryError->asRuleMessage())->line($node->getLine())->build(),
87+
];
88+
}
8689
}
90+
} catch (UnresolvableQueryException $exception) {
91+
return [
92+
RuleErrorBuilder::message($exception->asRuleMessage())->tip(UnresolvableQueryException::RULE_TIP)->line($node->getLine())->build(),
93+
];
8794
}
8895

8996
return [];

src/Rules/SyntaxErrorInQueryMethodRule.php

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPStan\Rules\Rule;
1111
use PHPStan\Rules\RuleErrorBuilder;
1212
use staabm\PHPStanDba\QueryReflection\QueryReflection;
13+
use staabm\PHPStanDba\UnresolvableQueryException;
1314

1415
/**
1516
* @implements Rule<MethodCall>
@@ -68,15 +69,21 @@ public function processNode(Node $node, Scope $scope): array
6869
return [];
6970
}
7071

71-
$queryReflection = new QueryReflection();
72-
$queryStrings = $queryReflection->resolveQueryStrings($args[$queryArgPosition]->value, $scope);
73-
foreach ($queryStrings as $queryString) {
74-
$queryError = $queryReflection->validateQueryString($queryString);
75-
if (null !== $queryError) {
76-
return [
77-
RuleErrorBuilder::message($queryError->asRuleMessage())->line($node->getLine())->build(),
78-
];
72+
try {
73+
$queryReflection = new QueryReflection();
74+
$queryStrings = $queryReflection->resolveQueryStrings($args[$queryArgPosition]->value, $scope);
75+
foreach ($queryStrings as $queryString) {
76+
$queryError = $queryReflection->validateQueryString($queryString);
77+
if (null !== $queryError) {
78+
return [
79+
RuleErrorBuilder::message($queryError->asRuleMessage())->line($node->getLine())->build(),
80+
];
81+
}
7982
}
83+
} catch (UnresolvableQueryException $exception) {
84+
return [
85+
RuleErrorBuilder::message($exception->asRuleMessage())->tip(UnresolvableQueryException::RULE_TIP)->line($node->getLine())->build(),
86+
];
8087
}
8188

8289
return [];

src/UnresolvableQueryException.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace staabm\PHPStanDba;
4+
5+
final class UnresolvableQueryException extends DbaException
6+
{
7+
public const RULE_TIP = 'Make sure all variables involved have a non-mixed type and array-types are specified.';
8+
9+
public function asRuleMessage(): string
10+
{
11+
return 'Unresolvable Query: '.$this->getMessage().'.';
12+
}
13+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace staabm\PHPStanDba\Tests;
4+
5+
use PHPStan\Rules\Rule;
6+
use staabm\PHPStanDba\QueryReflection\QueryReflection;
7+
use staabm\PHPStanDba\Rules\PdoStatementExecuteMethodRule;
8+
use staabm\PHPStanDba\UnresolvableQueryException;
9+
use Symplify\PHPStanExtensions\Testing\AbstractServiceAwareRuleTestCase;
10+
11+
/**
12+
* @extends AbstractServiceAwareRuleTestCase<PdoStatementExecuteMethodRule>
13+
*/
14+
class UnresolvablePdoStatementRuleTest extends AbstractServiceAwareRuleTestCase
15+
{
16+
protected function setUp(): void
17+
{
18+
QueryReflection::getRuntimeConfiguration()->debugMode(true);
19+
}
20+
21+
protected function tearDown(): void
22+
{
23+
QueryReflection::getRuntimeConfiguration()->debugMode(false);
24+
}
25+
26+
protected function getRule(): Rule
27+
{
28+
return $this->getRuleFromConfig(PdoStatementExecuteMethodRule::class, __DIR__.'/../config/dba.neon');
29+
}
30+
31+
public function testSyntaxErrorInQueryRule(): void
32+
{
33+
require_once __DIR__.'/data/unresolvable-pdo-statement.php';
34+
35+
$this->analyse([__DIR__.'/data/unresolvable-pdo-statement.php'], [
36+
[
37+
'Unresolvable Query: Cannot simulate parameter value for type: mixed.',
38+
13,
39+
UnresolvableQueryException::RULE_TIP,
40+
],
41+
]);
42+
}
43+
}

0 commit comments

Comments
 (0)