Skip to content

Commit e7d7e58

Browse files
staabmclxmstaab
andauthored
extracted PdoStatementReflection (#59)
Co-authored-by: Markus Staab <[email protected]>
1 parent 6231e31 commit e7d7e58

File tree

2 files changed

+119
-92
lines changed

2 files changed

+119
-92
lines changed

src/Extensions/PdoExecuteTypeSpecifyingExtension.php

Lines changed: 5 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,23 @@
55
namespace staabm\PHPStanDba\Extensions;
66

77
use PDOStatement;
8-
use PhpParser\Node;
9-
use PhpParser\Node\Expr;
10-
use PhpParser\Node\Expr\Assign;
118
use PhpParser\Node\Expr\MethodCall;
12-
use PhpParser\Node\FunctionLike;
13-
use PhpParser\NodeFinder;
149
use PHPStan\Analyser\Scope;
1510
use PHPStan\Analyser\SpecifiedTypes;
1611
use PHPStan\Analyser\TypeSpecifier;
1712
use PHPStan\Analyser\TypeSpecifierAwareExtension;
1813
use PHPStan\Analyser\TypeSpecifierContext;
1914
use PHPStan\Reflection\MethodReflection;
20-
use PHPStan\ShouldNotHappenException;
2115
use PHPStan\Type\Generic\GenericObjectType;
2216
use PHPStan\Type\MethodTypeSpecifyingExtension;
2317
use PHPStan\Type\Type;
18+
use staabm\PHPStanDba\PdoReflection\PdoStatementReflection;
2419
use staabm\PHPStanDba\QueryReflection\QueryReflection;
2520
use staabm\PHPStanDba\QueryReflection\QueryReflector;
26-
use Symplify\Astral\ValueObject\AttributeKey;
2721

2822
final class PdoExecuteTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
2923
{
3024
private TypeSpecifier $typeSpecifier;
31-
private NodeFinder $nodeFinder;
32-
33-
public function __construct()
34-
{
35-
$this->nodeFinder = new NodeFinder();
36-
}
3725

3826
public function getClass(): string
3927
{
@@ -56,33 +44,28 @@ public function specifyTypes(MethodReflection $methodReflection, MethodCall $nod
5644
$methodCall = $node;
5745
$stmtType = $scope->getType($methodCall->var);
5846

59-
$inferedType = $this->inferStatementType($methodCall, $scope);
47+
$inferedType = $this->inferStatementType($methodReflection, $methodCall, $scope);
6048
if (null !== $inferedType) {
6149
return $this->typeSpecifier->create($methodCall->var, $inferedType, TypeSpecifierContext::createTruthy(), true);
6250
}
6351

6452
return $this->typeSpecifier->create($methodCall->var, $stmtType, TypeSpecifierContext::createTruthy());
6553
}
6654

67-
private function inferStatementType(MethodCall $methodCall, Scope $scope): ?Type
55+
private function inferStatementType(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
6856
{
6957
$args = $methodCall->getArgs();
7058

7159
if (0 === \count($args)) {
7260
return null;
7361
}
7462

75-
$queryExpr = $this->findQueryStringExpression($methodCall);
63+
$stmtReflection = new PdoStatementReflection();
64+
$queryExpr = $stmtReflection->findPrepareQueryStringExpression($methodReflection, $methodCall);
7665
if (null === $queryExpr) {
7766
return null;
7867
}
7968

80-
// resolve query parameter from "prepare"
81-
if ($queryExpr instanceof MethodCall) {
82-
$queryArgs = $queryExpr->getArgs();
83-
$queryExpr = $queryArgs[0]->value;
84-
}
85-
8669
$parameterTypes = $scope->getType($args[0]->value);
8770

8871
$queryReflection = new QueryReflection();
@@ -100,74 +83,4 @@ private function inferStatementType(MethodCall $methodCall, Scope $scope): ?Type
10083

10184
return null;
10285
}
103-
104-
private function findQueryStringExpression(MethodCall $methodCall): ?Expr
105-
{
106-
// todo: use astral simpleNameResolver
107-
$nameResolver = function ($node) {
108-
if (\is_string($node->name)) {
109-
return $node->name;
110-
}
111-
if ($node->name instanceof Node\Identifier) {
112-
return $node->name->toString();
113-
}
114-
};
115-
116-
$current = $methodCall;
117-
while (null !== $current) {
118-
/** @var Assign|null $assign */
119-
$assign = $this->findFirstPreviousOfNode($current, function ($node) {
120-
return $node instanceof Assign;
121-
});
122-
123-
if (null !== $assign && $nameResolver($assign->var) === $nameResolver($methodCall->var)) {
124-
return $assign->expr;
125-
}
126-
127-
$current = $assign;
128-
}
129-
130-
return null;
131-
}
132-
133-
/**
134-
* @param callable(Node $node):bool $filter
135-
*/
136-
private function findFirstPreviousOfNode(Node $node, callable $filter): ?Node
137-
{
138-
// move to previous expression
139-
$previousStatement = $node->getAttribute(AttributeKey::PREVIOUS);
140-
if (null !== $previousStatement) {
141-
if (!$previousStatement instanceof Node) {
142-
throw new ShouldNotHappenException();
143-
}
144-
$foundNode = $this->findFirst([$previousStatement], $filter);
145-
// we found what we need
146-
if (null !== $foundNode) {
147-
return $foundNode;
148-
}
149-
150-
return $this->findFirstPreviousOfNode($previousStatement, $filter);
151-
}
152-
153-
$parent = $node->getAttribute(AttributeKey::PARENT);
154-
if ($parent instanceof FunctionLike) {
155-
return null;
156-
}
157-
158-
if ($parent instanceof Node) {
159-
return $this->findFirstPreviousOfNode($parent, $filter);
160-
}
161-
162-
return null;
163-
}
164-
165-
/**
166-
* @param Node|Node[] $nodes
167-
* @param callable(Node $node):bool $filter
168-
*/
169-
private function findFirst(Node|array $nodes, callable $filter): ?Node
170-
{
171-
return $this->nodeFinder->findFirst($nodes, $filter);
172-
}
17386
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace staabm\PHPStanDba\PdoReflection;
6+
7+
use PDOStatement;
8+
use PhpParser\Node;
9+
use PhpParser\Node\Expr;
10+
use PhpParser\Node\Expr\Assign;
11+
use PhpParser\Node\Expr\MethodCall;
12+
use PhpParser\Node\FunctionLike;
13+
use PhpParser\NodeFinder;
14+
use PHPStan\Reflection\MethodReflection;
15+
use PHPStan\ShouldNotHappenException;
16+
use Symplify\Astral\ValueObject\AttributeKey;
17+
18+
final class PdoStatementReflection
19+
{
20+
private NodeFinder $nodeFinder;
21+
22+
public function __construct()
23+
{
24+
$this->nodeFinder = new NodeFinder();
25+
}
26+
27+
public function findPrepareQueryStringExpression(MethodReflection $methodReflection, MethodCall $methodCall): ?Expr
28+
{
29+
if ('execute' !== $methodReflection->getName() || PdoStatement::class !== $methodReflection->getDeclaringClass()->getName()) {
30+
throw new ShouldNotHappenException();
31+
}
32+
33+
$queryExpr = $this->findQueryStringExpression($methodCall);
34+
35+
// resolve query parameter from "prepare"
36+
if ($queryExpr instanceof MethodCall) {
37+
$queryArgs = $queryExpr->getArgs();
38+
39+
return $queryArgs[0]->value;
40+
}
41+
42+
return null;
43+
}
44+
45+
private function findQueryStringExpression(MethodCall $methodCall): ?Expr
46+
{
47+
// todo: use astral simpleNameResolver
48+
$nameResolver = function ($node) {
49+
if (\is_string($node->name)) {
50+
return $node->name;
51+
}
52+
if ($node->name instanceof Node\Identifier) {
53+
return $node->name->toString();
54+
}
55+
};
56+
57+
$current = $methodCall;
58+
while (null !== $current) {
59+
/** @var Assign|null $assign */
60+
$assign = $this->findFirstPreviousOfNode($current, function ($node) {
61+
return $node instanceof Assign;
62+
});
63+
64+
if (null !== $assign && $nameResolver($assign->var) === $nameResolver($methodCall->var)) {
65+
return $assign->expr;
66+
}
67+
68+
$current = $assign;
69+
}
70+
71+
return null;
72+
}
73+
74+
/**
75+
* @param callable(Node $node):bool $filter
76+
*/
77+
private function findFirstPreviousOfNode(Node $node, callable $filter): ?Node
78+
{
79+
// move to previous expression
80+
$previousStatement = $node->getAttribute(AttributeKey::PREVIOUS);
81+
if (null !== $previousStatement) {
82+
if (!$previousStatement instanceof Node) {
83+
throw new ShouldNotHappenException();
84+
}
85+
$foundNode = $this->findFirst([$previousStatement], $filter);
86+
// we found what we need
87+
if (null !== $foundNode) {
88+
return $foundNode;
89+
}
90+
91+
return $this->findFirstPreviousOfNode($previousStatement, $filter);
92+
}
93+
94+
$parent = $node->getAttribute(AttributeKey::PARENT);
95+
if ($parent instanceof FunctionLike) {
96+
return null;
97+
}
98+
99+
if ($parent instanceof Node) {
100+
return $this->findFirstPreviousOfNode($parent, $filter);
101+
}
102+
103+
return null;
104+
}
105+
106+
/**
107+
* @param Node|Node[] $nodes
108+
* @param callable(Node $node):bool $filter
109+
*/
110+
private function findFirst(Node|array $nodes, callable $filter): ?Node
111+
{
112+
return $this->nodeFinder->findFirst($nodes, $filter);
113+
}
114+
}

0 commit comments

Comments
 (0)