Skip to content

Commit f7ce984

Browse files
committed
ObjectRepositoryDynamicReturnTypeExtension - do not override return types for existing native methods
1 parent c5343f0 commit f7ce984

File tree

3 files changed

+55
-2
lines changed

3 files changed

+55
-2
lines changed

Diff for: src/Type/Doctrine/ObjectRepositoryDynamicReturnTypeExtension.php

+39-2
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,29 @@
44

55
use PhpParser\Node\Expr\MethodCall;
66
use PHPStan\Analyser\Scope;
7+
use PHPStan\Broker\Broker;
8+
use PHPStan\Reflection\BrokerAwareExtension;
79
use PHPStan\Reflection\MethodReflection;
10+
use PHPStan\Reflection\ParametersAcceptorSelector;
811
use PHPStan\Type\ArrayType;
912
use PHPStan\Type\IntegerType;
1013
use PHPStan\Type\MixedType;
1114
use PHPStan\Type\ObjectType;
1215
use PHPStan\Type\Type;
1316
use PHPStan\Type\TypeCombinator;
17+
use PHPStan\Type\TypeWithClassName;
1418

15-
class ObjectRepositoryDynamicReturnTypeExtension implements \PHPStan\Type\DynamicMethodReturnTypeExtension
19+
class ObjectRepositoryDynamicReturnTypeExtension implements \PHPStan\Type\DynamicMethodReturnTypeExtension, BrokerAwareExtension
1620
{
1721

22+
/** @var Broker */
23+
private $broker;
24+
25+
public function setBroker(Broker $broker): void
26+
{
27+
$this->broker = $broker;
28+
}
29+
1830
public function getClass(): string
1931
{
2032
return 'Doctrine\Common\Persistence\ObjectRepository';
@@ -36,10 +48,35 @@ public function getTypeFromMethodCall(
3648
): Type
3749
{
3850
$calledOnType = $scope->getType($methodCall->var);
39-
if (!$calledOnType instanceof ObjectRepositoryType) {
51+
if (!$calledOnType instanceof TypeWithClassName) {
4052
return new MixedType();
4153
}
54+
4255
$methodName = $methodReflection->getName();
56+
$repositoryClassReflection = $this->broker->getClass($calledOnType->getClassName());
57+
if (
58+
(
59+
(
60+
strpos($methodName, 'findBy') === 0
61+
&& strlen($methodName) > strlen('findBy')
62+
) || (
63+
strpos($methodName, 'findOneBy') === 0
64+
&& strlen($methodName) > strlen('findOneBy')
65+
)
66+
)
67+
&& $repositoryClassReflection->hasNativeMethod($methodName)
68+
) {
69+
return ParametersAcceptorSelector::selectFromArgs(
70+
$scope,
71+
$methodCall->args,
72+
$repositoryClassReflection->getNativeMethod($methodName)->getVariants()
73+
)->getReturnType();
74+
}
75+
76+
if (!$calledOnType instanceof ObjectRepositoryType) {
77+
return new MixedType();
78+
}
79+
4380
$entityType = new ObjectType($calledOnType->getEntityClass());
4481

4582
if ($methodName === 'find' || strpos($methodName, 'findOneBy') === 0) {

Diff for: tests/DoctrineIntegration/ORM/data/customRepositoryUsage-2.json

+10
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,15 @@
1313
"message": "Call to an undefined method PHPStan\\DoctrineIntegration\\ORM\\CustomRepositoryUsage\\MyEntity::nonexistent().",
1414
"line": 47,
1515
"ignorable": true
16+
},
17+
{
18+
"message": "Cannot call method test() on int.",
19+
"line": 52,
20+
"ignorable": true
21+
},
22+
{
23+
"message": "Cannot call method test() on int.",
24+
"line": 53,
25+
"ignorable": true
1626
}
1727
]

Diff for: tests/DoctrineIntegration/ORM/data/customRepositoryUsage.php

+6
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ public function genericRepository(): void
4646
$entity->doSomethingElse();
4747
$entity->nonexistent();
4848
}
49+
50+
public function callExistingMethodOnRepository(): void
51+
{
52+
$this->repository->findOneByBlabla()->test();
53+
$this->anotherRepository->findOneByBlabla()->test();
54+
}
4955
}
5056

5157
/**

0 commit comments

Comments
 (0)