Repository: phpstan/phpstan-doctrine (php)

Issue:
\Doctrine\ORM\Query\Expr\Base::count() doesn't handle dynamic arguments
I have been appreciating the recent PHPStan Doctrine query builder improvements. Thank you!

I found an issue with using `count()` for or and and expressions when arguments are added conditionally:

```php
$or = $qb->expr()->orX();

\PHPStan\dumpType($or->count()); // Dumped type: 0

$or->add('a.foo = 1');

\PHPStan\dumpType($or->count()); // Dumped type: 1

if (rand(0, 1)) {
    $or->add('a.bar = 2');
}

\PHPStan\dumpType($or->count()); // Dumped type: 2 (but should be int<1,2>)
```
It turns out that `if ($or->count()) { $qb->andWhere($or); }` can be replaced with `$qb->andWhere($or)` because the empty expression with be reduced, but using `$or->count()` to return `null` instead of executing a query causes this PHPStan error.

Pull request:
BaseExpressionDynamicReturnTypeExtension: support only add, addMultiple
Resolves #496

Patch:
diff --git a/src/Type/Doctrine/QueryBuilder/Expr/BaseExpressionDynamicReturnTypeExtension.php b/src/Type/Doctrine/QueryBuilder/Expr/BaseExpressionDynamicReturnTypeExtension.php
index 1534f11..8564d37 100644
--- a/src/Type/Doctrine/QueryBuilder/Expr/BaseExpressionDynamicReturnTypeExtension.php
+++ b/src/Type/Doctrine/QueryBuilder/Expr/BaseExpressionDynamicReturnTypeExtension.php
@@ -11,6 +11,7 @@ use PHPStan\Type\Doctrine\ArgumentsProcessor;
 use PHPStan\Type\DynamicMethodReturnTypeExtension;
 use PHPStan\Type\Type;
 use function get_class;
+use function in_array;
 use function is_object;
 use function method_exists;
 
@@ -34,7 +35,7 @@ class BaseExpressionDynamicReturnTypeExtension implements DynamicMethodReturnTyp
 
 	public function isMethodSupported(MethodReflection $methodReflection): bool
 	{
-		return true;
+		return in_array($methodReflection->getName(), ['add', 'addMultiple'], true);
 	}
 
 	public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type


Test patch:
diff --git a/tests/Type/Doctrine/QueryBuilder/Expr/BaseExpressionDynamicReturnTypeExtensionTest.php b/tests/Type/Doctrine/QueryBuilder/Expr/BaseExpressionDynamicReturnTypeExtensionTest.php
new file mode 100644
index 0000000..0bc668b
--- /dev/null
+++ b/tests/Type/Doctrine/QueryBuilder/Expr/BaseExpressionDynamicReturnTypeExtensionTest.php
@@ -0,0 +1,35 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\Type\Doctrine\QueryBuilder\Expr;
+
+use PHPStan\Testing\TypeInferenceTestCase;
+
+class BaseExpressionDynamicReturnTypeExtensionTest extends TypeInferenceTestCase
+{
+
+	/** @return iterable<mixed> */
+	public function dataFileAsserts(): iterable
+	{
+		yield from $this->gatherAssertTypes(__DIR__ . '/../../data/QueryResult/baseExpressionAndOr.php');
+	}
+
+	/**
+	 * @dataProvider dataFileAsserts
+	 * @param mixed ...$args
+	 */
+	public function testFileAsserts(
+		string $assertType,
+		string $file,
+		...$args
+	): void
+	{
+		$this->assertFileAsserts($assertType, $file, ...$args);
+	}
+
+	/** @return string[] */
+	public static function getAdditionalConfigFiles(): array
+	{
+		return [__DIR__ . '/../../data/QueryResult/config.neon'];
+	}
+
+}
diff --git a/tests/Type/Doctrine/data/QueryResult/baseExpressionAndOr.php b/tests/Type/Doctrine/data/QueryResult/baseExpressionAndOr.php
new file mode 100644
index 0000000..9fcf71a
--- /dev/null
+++ b/tests/Type/Doctrine/data/QueryResult/baseExpressionAndOr.php
@@ -0,0 +1,16 @@
+<?php declare(strict_types = 1);
+
+namespace QueryResult\CreateQuery;
+
+use Doctrine\ORM\Query\Expr\Andx;
+use function PHPStan\Testing\assertType;
+
+$and = new Andx();
+$count1 = $and->count();
+assertType("int<0, max>", $count1);
+
+$modifiedAnd = $and->add('a = b');
+assertType("Doctrine\ORM\Query\Expr\Andx", $modifiedAnd);
+
+$string = $and->__toString();
+assertType("string", $string);