Skip to content

Commit

Permalink
support static call
Browse files Browse the repository at this point in the history
  • Loading branch information
janedbal authored and ondrejmirtes committed Jan 5, 2024
1 parent 892c017 commit 1dcec03
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 51 deletions.
37 changes: 4 additions & 33 deletions src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
namespace PHPStan\Type\Doctrine\QueryBuilder;

use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
Expand All @@ -17,13 +15,12 @@
use PHPStan\Analyser\ScopeFactory;
use PHPStan\DependencyInjection\Container;
use PHPStan\Parser\Parser;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverser;
use PHPStan\Type\UnionType;
use function count;
use function is_array;

class OtherMethodQueryBuilderParser
Expand All @@ -32,54 +29,28 @@ class OtherMethodQueryBuilderParser
/** @var bool */
private $descendIntoOtherMethods;

/** @var ReflectionProvider */
private $reflectionProvider;

/** @var Parser */
private $parser;

/** @var Container */
private $container;

public function __construct(bool $descendIntoOtherMethods, ReflectionProvider $reflectionProvider, Parser $parser, Container $container)
public function __construct(bool $descendIntoOtherMethods, Parser $parser, Container $container)
{
$this->descendIntoOtherMethods = $descendIntoOtherMethods;
$this->reflectionProvider = $reflectionProvider;
$this->parser = $parser;
$this->container = $container;
}

/**
* @return QueryBuilderType[]
* @return list<QueryBuilderType>
*/
public function findQueryBuilderTypesInCalledMethod(Scope $scope, MethodCall $methodCall): array
public function findQueryBuilderTypesInCalledMethod(Scope $scope, MethodReflection $methodReflection): array
{
if (!$this->descendIntoOtherMethods) {
return [];
}

$methodCalledOnType = $scope->getType($methodCall->var);
if (!$methodCall->name instanceof Identifier) {
return [];
}

$methodCalledOnTypeClassNames = $methodCalledOnType->getObjectClassNames();

if (count($methodCalledOnTypeClassNames) !== 1) {
return [];
}

if (!$this->reflectionProvider->hasClass($methodCalledOnTypeClassNames[0])) {
return [];
}

$classReflection = $this->reflectionProvider->getClass($methodCalledOnTypeClassNames[0]);
$methodName = $methodCall->name->toString();
if (!$classReflection->hasNativeMethod($methodName)) {
return [];
}

$methodReflection = $classReflection->getNativeMethod($methodName);
$fileName = $methodReflection->getDeclaringClass()->getFileName();
if ($fileName === null) {
return [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\CallLike;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\ExpressionTypeResolverExtension;
use PHPStan\Type\ObjectType;
Expand All @@ -30,30 +34,15 @@ public function __construct(

public function getType(Expr $expr, Scope $scope): ?Type
{
if (!$expr instanceof MethodCall) {
if (!$expr instanceof MethodCall && !$expr instanceof StaticCall) {
return null;
}

if ($expr->isFirstClassCallable()) {
return null;
}

if (!$expr->name instanceof Identifier) {
return null;
}

$callerType = $scope->getType($expr->var);

foreach ($callerType->getObjectClassReflections() as $callerClassReflection) {
if ($callerClassReflection->is(QueryBuilder::class)) {
return null; // covered by QueryBuilderMethodDynamicReturnTypeExtension
}
if ($callerClassReflection->is(EntityRepository::class) && $expr->name->name === 'createQueryBuilder') {
return null; // covered by EntityRepositoryCreateQueryBuilderDynamicReturnTypeExtension
}
}

$methodReflection = $scope->getMethodReflection($callerType, $expr->name->name);
$methodReflection = $this->getMethodReflection($expr, $scope);

if ($methodReflection === null) {
return null;
Expand All @@ -67,12 +56,44 @@ public function getType(Expr $expr, Scope $scope): ?Type
return null;
}

$queryBuilderTypes = $this->otherMethodQueryBuilderParser->findQueryBuilderTypesInCalledMethod($scope, $expr);
$queryBuilderTypes = $this->otherMethodQueryBuilderParser->findQueryBuilderTypesInCalledMethod($scope, $methodReflection);
if (count($queryBuilderTypes) === 0) {
return null;
}

return TypeCombinator::union(...$queryBuilderTypes);
}

/**
* @param StaticCall|MethodCall $call
*/
private function getMethodReflection(CallLike $call, Scope $scope): ?MethodReflection
{
if (!$call->name instanceof Identifier) {
return null;
}

if ($call instanceof MethodCall) {
$callerType = $scope->getType($call->var);
} else {
if (!$call->class instanceof Name) {
return null;
}
$callerType = $scope->resolveTypeByName($call->class);
}

$methodName = $call->name->name;

foreach ($callerType->getObjectClassReflections() as $callerClassReflection) {
if ($callerClassReflection->is(QueryBuilder::class)) {
return null; // covered by QueryBuilderMethodDynamicReturnTypeExtension
}
if ($callerClassReflection->is(EntityRepository::class) && $methodName === 'createQueryBuilder') {
return null; // covered by EntityRepositoryCreateQueryBuilderDynamicReturnTypeExtension
}
}

return $scope->getMethodReflection($callerType, $methodName);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ public function testDiveIntoCustomEntityRepository(EntityManagerInterface $em):
assertType('Doctrine\ORM\Query<null, QueryResult\Entities\Many>', $queryBuilder->getQuery());
}


public function testStaticCallWorksToo(EntityManagerInterface $em): void
{
$queryBuilder = self::getStaticQueryBuilder($em);

assertType('Doctrine\ORM\Query<null, QueryResult\Entities\Many>', $queryBuilder->getQuery());
}

public function testFirstClassCallableDoesNotFail(EntityManagerInterface $em): void
{
$this->getQueryBuilder(...);
Expand All @@ -70,6 +78,13 @@ private function getQueryBuilder(EntityManagerInterface $em): QueryBuilder
->from(Many::class, 'm');
}

private static function getStaticQueryBuilder(EntityManagerInterface $em): QueryBuilder
{
return $em->createQueryBuilder()
->select('m')
->from(Many::class, 'm');
}

private function getBranchingQueryBuilder(EntityManagerInterface $em): QueryBuilder
{
$queryBuilder = $em->createQueryBuilder()
Expand Down

0 comments on commit 1dcec03

Please sign in to comment.