Skip to content

Commit 0ff467c

Browse files
authored
[TypeDeclaration] Handle getRepository() return via local variable in ReturnTypeFromGetRepositoryDocblockRector (#8148)
1 parent 909090d commit 0ff467c

3 files changed

Lines changed: 126 additions & 3 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromGetRepositoryDocblockRector\Fixture;
4+
5+
use Doctrine\ORM\EntityManagerInterface;
6+
use Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromGetRepositoryDocblockRector\Source\EventRepository;
7+
8+
final class GetRepositoryViaVariable
9+
{
10+
public function __construct(private EntityManagerInterface $em)
11+
{
12+
}
13+
14+
/**
15+
* @return EventRepository
16+
*/
17+
public function getRepository()
18+
{
19+
/** @var EventRepository $repo */
20+
$repo = $this->em->getRepository(Event::class);
21+
22+
$repo->setDispatcher($this->dispatcher);
23+
24+
return $repo;
25+
}
26+
}
27+
28+
?>
29+
-----
30+
<?php
31+
32+
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromGetRepositoryDocblockRector\Fixture;
33+
34+
use Doctrine\ORM\EntityManagerInterface;
35+
use Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromGetRepositoryDocblockRector\Source\EventRepository;
36+
37+
final class GetRepositoryViaVariable
38+
{
39+
public function __construct(private EntityManagerInterface $em)
40+
{
41+
}
42+
43+
public function getRepository(): \Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromGetRepositoryDocblockRector\Source\EventRepository
44+
{
45+
/** @var EventRepository $repo */
46+
$repo = $this->em->getRepository(Event::class);
47+
48+
$repo->setDispatcher($this->dispatcher);
49+
50+
return $repo;
51+
}
52+
}
53+
54+
?>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromGetRepositoryDocblockRector\Fixture;
4+
5+
use Doctrine\ORM\EntityManagerInterface;
6+
use Rector\Tests\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromGetRepositoryDocblockRector\Source\EventRepository;
7+
8+
final class SkipReassignedVariable
9+
{
10+
public function __construct(private EntityManagerInterface $em)
11+
{
12+
}
13+
14+
/**
15+
* @return EventRepository
16+
*/
17+
public function getRepository()
18+
{
19+
$repo = $this->em->getRepository(Event::class);
20+
$repo = someOther();
21+
22+
return $repo;
23+
}
24+
}

rules/TypeDeclaration/Rector/ClassMethod/ReturnTypeFromGetRepositoryDocblockRector.php

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
namespace Rector\TypeDeclaration\Rector\ClassMethod;
66

77
use PhpParser\Node;
8+
use PhpParser\Node\Expr\Assign;
89
use PhpParser\Node\Expr\MethodCall;
10+
use PhpParser\Node\Expr\Variable;
911
use PhpParser\Node\Name;
1012
use PhpParser\Node\Stmt\ClassMethod;
1113
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
@@ -141,18 +143,61 @@ private function isGetRepositoryReturnOnly(ClassMethod $classMethod): bool
141143
return false;
142144
}
143145

144-
$methodCall = $returns[0]->expr;
145-
if (! $methodCall instanceof MethodCall) {
146-
return false;
146+
$returnedExpr = $returns[0]->expr;
147+
148+
// direct return: return $this->em->getRepository(...);
149+
if ($returnedExpr instanceof MethodCall) {
150+
return $this->isGetRepositoryMethodCall($returnedExpr);
151+
}
152+
153+
// indirect return: $repo = $this->em->getRepository(...); ...; return $repo;
154+
if ($returnedExpr instanceof Variable) {
155+
$assignedMethodCall = $this->findVariableAssignMethodCall($classMethod, $returnedExpr);
156+
if (! $assignedMethodCall instanceof MethodCall) {
157+
return false;
158+
}
159+
160+
return $this->isGetRepositoryMethodCall($assignedMethodCall);
147161
}
148162

163+
return false;
164+
}
165+
166+
private function isGetRepositoryMethodCall(MethodCall $methodCall): bool
167+
{
149168
if (! $this->isName($methodCall->name, 'getRepository')) {
150169
return false;
151170
}
152171

153172
return $this->isEntityManagerType($methodCall);
154173
}
155174

175+
private function findVariableAssignMethodCall(ClassMethod $classMethod, Variable $variable): ?MethodCall
176+
{
177+
/** @var Assign[] $assigns */
178+
$assigns = $this->betterNodeFinder->findInstanceOf($classMethod, Assign::class);
179+
180+
$methodCall = null;
181+
foreach ($assigns as $assign) {
182+
if (! $assign->var instanceof Variable) {
183+
continue;
184+
}
185+
186+
if (! $this->nodeComparator->areNodesEqual($assign->var, $variable)) {
187+
continue;
188+
}
189+
190+
// reassigned to something else, cannot be sure of the type
191+
if (! $assign->expr instanceof MethodCall) {
192+
return null;
193+
}
194+
195+
$methodCall = $assign->expr;
196+
}
197+
198+
return $methodCall;
199+
}
200+
156201
private function isEntityManagerType(MethodCall $methodCall): bool
157202
{
158203
$callerType = $this->nodeTypeResolver->getType($methodCall->var);

0 commit comments

Comments
 (0)