Skip to content

Commit

Permalink
Support calls over Andx/Orx objects
Browse files Browse the repository at this point in the history
  • Loading branch information
janedbal committed Nov 20, 2023
1 parent c94f43b commit 5c0ba09
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 1 deletion.
7 changes: 6 additions & 1 deletion extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,12 @@ services:
argumentsProcessor: @doctrineQueryBuilderArgumentsProcessor
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension

-
class: PHPStan\Type\Doctrine\QueryBuilder\Expr\BaseExpressionDynamicReturnTypeExtension
arguments:
argumentsProcessor: @doctrineQueryBuilderArgumentsProcessor
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: PHPStan\Rules\Doctrine\ORM\PropertiesExtension
tags:
Expand Down
3 changes: 3 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ parameters:
-
message: '~^Variable method call on object\.$~'
path: src/Type/Doctrine/QueryBuilder/Expr/ExpressionBuilderDynamicReturnTypeExtension.php
-
message: '~^Variable method call on object\.$~'
path: src/Type/Doctrine/QueryBuilder/Expr/BaseExpressionDynamicReturnTypeExtension.php
-
message: '~^Variable property access on PhpParser\\Node\\Stmt\\Declare_\|PhpParser\\Node\\Stmt\\Namespace_\.$~'
path: src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Doctrine\QueryBuilder\Expr;

use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Rules\Doctrine\ORM\DynamicQueryBuilderArgumentException;
use PHPStan\Type\Doctrine\ArgumentsProcessor;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Type;
use function get_class;
use function is_object;
use function method_exists;

class BaseExpressionDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{

/** @var ArgumentsProcessor */
private $argumentsProcessor;

public function __construct(
ArgumentsProcessor $argumentsProcessor
)
{
$this->argumentsProcessor = $argumentsProcessor;
}

public function getClass(): string
{
return 'Doctrine\ORM\Query\Expr\Base';
}

public function isMethodSupported(MethodReflection $methodReflection): bool
{
return true;
}

public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
{
$defaultReturnType = ParametersAcceptorSelector::selectFromArgs($scope, $methodCall->getArgs(), $methodReflection->getVariants())->getReturnType();

try {
$args = $this->argumentsProcessor->processArgs($scope, $methodReflection->getName(), $methodCall->getArgs());
} catch (DynamicQueryBuilderArgumentException $e) {
return $defaultReturnType;
}

$calledOnType = $scope->getType($methodCall->var);
if (!$calledOnType instanceof ExprType) {
return $defaultReturnType;
}

$expr = $calledOnType->getExprObject();

if (!method_exists($expr, $methodReflection->getName())) {
return $defaultReturnType;
}

$exprValue = $expr->{$methodReflection->getName()}(...$args);
if (is_object($exprValue)) {
return new ExprType(get_class($exprValue), $exprValue);
}

return $scope->getTypeFromValue($exprValue);
}

}
5 changes: 5 additions & 0 deletions tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ public function testRuleBranches(): void
$this->analyse([__DIR__ . '/data/query-builder-branches-dql.php'], $errors);
}

public function testAndOrVariants(): void
{
$this->analyse([__DIR__ . '/data/query-builder-dql-and-or-variants.php'], []);
}

public function testBranchingPerformance(): void
{
$this->analyse([__DIR__ . '/data/query-builder-branches-performance.php'], [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Doctrine\ORM;

use Doctrine\ORM\EntityManager;

class TestAndxWithConsequentMethodCall
{

/** @var EntityManager */
private $entityManager;

public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}

public function emptyConstructorConsequentCall(): void
{
$expr = (new \Doctrine\ORM\Query\Expr\Andx())->add('1 = 1');
$queryBuilder = $this->entityManager->createQueryBuilder();
$queryBuilder->select('e')
->from(MyEntity::class, 'e')
->andWhere($expr);
$queryBuilder->getQuery();
}

public function usedConstructor(): void
{
$expr = new \Doctrine\ORM\Query\Expr\Andx('1 = 1');
$queryBuilder = $this->entityManager->createQueryBuilder();
$queryBuilder->select('e')
->from(MyEntity::class, 'e')
->andWhere($expr);
$queryBuilder->getQuery();
}

public function createdFromQueryBuilder(): void
{
$queryBuilder = $this->entityManager->createQueryBuilder();
$queryBuilder->select('e')
->from(MyEntity::class, 'e')
->andWhere($queryBuilder->expr()->andX('1 = 1'));
$queryBuilder->getQuery();
}

}
10 changes: 10 additions & 0 deletions tests/Rules/Doctrine/ORM/data/query-builder-dql.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,16 @@ public function qbCustomExprMethodSyntaxError(): void
$queryBuilder->getQuery();
}

public function qbExprMethod(): void
{
$expr = (new \Doctrine\ORM\Query\Expr\Andx())->add('1 = 1');
$queryBuilder = $this->entityManager->createQueryBuilder();
$queryBuilder->select('e')
->from(MyEntity::class, 'e')
->andWhere($expr);
$queryBuilder->getQuery();
}

}

class CustomExpr extends \Doctrine\ORM\Query\Expr
Expand Down

0 comments on commit 5c0ba09

Please sign in to comment.