55namespace staabm \PHPStanDba \Extensions ;
66
77use PDOStatement ;
8- use PhpParser \Node ;
9- use PhpParser \Node \Expr ;
10- use PhpParser \Node \Expr \Assign ;
118use PhpParser \Node \Expr \MethodCall ;
12- use PhpParser \Node \FunctionLike ;
13- use PhpParser \NodeFinder ;
149use PHPStan \Analyser \Scope ;
1510use PHPStan \Analyser \SpecifiedTypes ;
1611use PHPStan \Analyser \TypeSpecifier ;
1712use PHPStan \Analyser \TypeSpecifierAwareExtension ;
1813use PHPStan \Analyser \TypeSpecifierContext ;
1914use PHPStan \Reflection \MethodReflection ;
20- use PHPStan \ShouldNotHappenException ;
2115use PHPStan \Type \Generic \GenericObjectType ;
2216use PHPStan \Type \MethodTypeSpecifyingExtension ;
2317use PHPStan \Type \Type ;
18+ use staabm \PHPStanDba \PdoReflection \PdoStatementReflection ;
2419use staabm \PHPStanDba \QueryReflection \QueryReflection ;
2520use staabm \PHPStanDba \QueryReflection \QueryReflector ;
26- use Symplify \Astral \ValueObject \AttributeKey ;
2721
2822final 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}
0 commit comments