Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/1.3.x' into 1.4.x
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Nov 17, 2023
2 parents 49f5137 + 0aca96d commit abf994a
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 0 deletions.
3 changes: 3 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ services:
-
class: PHPStan\Type\Doctrine\Descriptors\ArrayType
tags: [phpstan.doctrine.typeDescriptor]
-
class: PHPStan\Type\Doctrine\Descriptors\AsciiStringType
tags: [phpstan.doctrine.typeDescriptor]
-
class: PHPStan\Type\Doctrine\Descriptors\BigIntType
tags: [phpstan.doctrine.typeDescriptor]
Expand Down
31 changes: 31 additions & 0 deletions src/Type/Doctrine/Descriptors/AsciiStringType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Doctrine\Descriptors;

use PHPStan\Type\StringType;
use PHPStan\Type\Type;

class AsciiStringType implements DoctrineTypeDescriptor
{

public function getType(): string
{
return \Doctrine\DBAL\Types\AsciiStringType::class;
}

public function getWritableToPropertyType(): Type
{
return new StringType();
}

public function getWritableToDatabaseType(): Type
{
return new StringType();
}

public function getDatabaseInternalType(): Type
{
return new StringType();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,21 @@
class QueryBuilderGetQueryDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{

/**
* Those are critical methods where we need to understand arguments passed to them, the rest is allowed to be more dynamic
* - this list reflects what is implemented in QueryResultTypeWalker
*/
private const METHODS_AFFECTING_RESULT_TYPE = [
'add',
'select',
'addselect',
'from',
'join',
'innerjoin',
'leftjoin',
'indexby',
];

/** @var ObjectMetadataResolver */
private $objectMetadataResolver;

Expand Down Expand Up @@ -139,6 +154,9 @@ public function getTypeFromMethodCall(
try {
$args = $this->argumentsProcessor->processArgs($scope, $methodName, $calledMethodCall->getArgs());
} catch (DynamicQueryBuilderArgumentException $e) {
if (!in_array($lowerMethodName, self::METHODS_AFFECTING_RESULT_TYPE, true)) {
continue;
}
return $defaultReturnType;
}

Expand Down
13 changes: 13 additions & 0 deletions tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,19 @@ public function testBranchingPerformance(): void
]);
}

public function testDynamicWhere(): void
{
$this->analyse([__DIR__ . '/data/query-builder-dynamic.php'], [
['Could not analyse QueryBuilder with dynamic arguments.', 40],
['Could not analyse QueryBuilder with dynamic arguments.', 45],
['Could not analyse QueryBuilder with dynamic arguments.', 51],
['Could not analyse QueryBuilder with dynamic arguments.', 56],
['Could not analyse QueryBuilder with dynamic arguments.', 61],
['Could not analyse QueryBuilder with dynamic arguments.', 66],
['Could not analyse QueryBuilder with dynamic arguments.', 71],
]);
}

public static function getAdditionalConfigFiles(): array
{
return [
Expand Down
78 changes: 78 additions & 0 deletions tests/Rules/Doctrine/ORM/data/query-builder-dynamic.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php declare(strict_types = 1);

namespace PHPStan\Rules\Doctrine\ORM;

use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\Query\Expr\From;

class DynamicCalls
{
public function testDynamicMethodCall(
EntityManagerInterface $em,
Andx $and,
Criteria $criteria,
string $string
): void
{
$em->createQueryBuilder()
->select('m')
->from(MyEntity::class, 'm')
->andWhere($and)
->setParameter($string, $string)
->setParameters([$string])
->orWhere($string)
->addOrderBy($string)
->addGroupBy($string)
->addCriteria($criteria)
->getQuery();

$em->createQueryBuilder()
->select('m')
->add('from', new From(MyEntity::class, 'm', null), true)
->where($string)
->orderBy($string)
->groupBy($string)
->getQuery();

// all below are disallowed dynamic
$em->createQueryBuilder()
->select('m')
->from($string, 'm')
->getQuery();

$em->createQueryBuilder()
->select('m')
->from(MyEntity::class, 'm')
->indexBy($string, $string)
->getQuery();

$em->createQueryBuilder()
->select('m')
->from(MyEntity::class, 'm', $string)
->getQuery();

$em->createQueryBuilder()
->select([$string])
->from(MyEntity::class, 'm')
->getQuery();

$em->createQueryBuilder()
->select(['m'])
->from(MyEntity::class, $string)
->getQuery();

$em->createQueryBuilder()
->addSelect($string)
->from(MyEntity::class, 'm')
->getQuery();

$em->createQueryBuilder()
->addSelect('m')
->from(MyEntity::class, 'm')
->join($string, $string)
->getQuery();
}

}
95 changes: 95 additions & 0 deletions tests/Type/Doctrine/data/QueryResult/queryBuilderGetQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

namespace QueryResult\CreateQuery;

use Doctrine\Common\Collections\Criteria;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\Query\Expr\From;
use Doctrine\ORM\QueryBuilder;
use QueryResult\Entities\Many;
use function PHPStan\Testing\assertType;
Expand Down Expand Up @@ -135,7 +138,99 @@ public function testQueryResultTypeIsVoidWithDeleteOrUpdate(EntityManagerInterfa
->getQuery();

assertType('Doctrine\ORM\Query<void, void>', $query);
}


public function testDynamicMethodCall(
EntityManagerInterface $em,
Andx $and,
Criteria $criteria,
string $string
): void
{
$result = $em->createQueryBuilder()
->select('m')
->from(Many::class, 'm')
->andWhere($and)
->setParameter($string, $string)
->setParameters([$string])
->orWhere($string)
->addOrderBy($string)
->addGroupBy($string)
->addCriteria($criteria)
->getQuery()
->getResult();

assertType('list<QueryResult\Entities\Many>', $result);

$result = $em->createQueryBuilder()
->select(['m.stringNullColumn'])
->add('from', new From(Many::class, 'm', null), true)
->where($string)
->orderBy($string)
->groupBy($string)
->getQuery()
->getResult();

assertType('list<array{stringNullColumn: string|null}>', $result);

$result = $em->createQueryBuilder()
->select(['m.intColumn', 'm.stringNullColumn'])
->from($string, 'm')
->getQuery()
->getResult();

assertType('mixed', $result);

$result = $em->createQueryBuilder()
->select(['m.intColumn', 'm.stringNullColumn'])
->from(Many::class, 'm')
->indexBy($string, $string)
->getQuery()
->getResult();

assertType('mixed', $result);

$result = $em->createQueryBuilder()
->select('m')
->from(Many::class, 'm', $string)
->getQuery()
->getResult();

assertType('mixed', $result);

$result = $em->createQueryBuilder()
->select([$string, 'm.stringNullColumn'])
->from(Many::class, 'm')
->getQuery()
->getResult();

assertType('mixed', $result);

$result = $em->createQueryBuilder()
->select(['m.stringNullColumn'])
->from(Many::class, $string)
->getQuery()
->getResult();

assertType('mixed', $result);

$result = $em->createQueryBuilder()
->addSelect($string)
->from(Many::class, 'm')
->getQuery()
->getResult();

assertType('mixed', $result);

$result = $em->createQueryBuilder()
->addSelect('m')
->from(Many::class, 'm')
->join($string, $string)
->getQuery()
->getResult();

assertType('mixed', $result);
}

public function testQueryTypeIsInferredOnAcrossMethods(EntityManagerInterface $em): void
Expand Down

0 comments on commit abf994a

Please sign in to comment.