Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for infering the result type of queries in EntityManager::createQuery() #232

Merged
merged 3 commits into from
Jan 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ parameters:
- Doctrine\ORM\Mapping\ClassMetadata
- Doctrine\ORM\Mapping\ClassMetadataInfo
- Doctrine\Persistence\Mapping\ClassMetadata
- Doctrine\ORM\AbstractQuery
- Doctrine\ORM\Query
stubFiles:
- stubs/Criteria.stub
- stubs/DocumentManager.stub
Expand All @@ -38,6 +40,7 @@ parameters:
- stubs/ORM/AbstractQuery.stub
- stubs/ORM/Mapping/ClassMetadata.stub
- stubs/ORM/Mapping/ClassMetadataInfo.stub
- stubs/ORM/Query.stub
- stubs/Persistence/Mapping/ClassMetadata.stub
- stubs/ServiceDocumentRepository.stub

Expand Down Expand Up @@ -120,6 +123,16 @@ services:
class: PHPStan\Type\Doctrine\Query\QueryGetDqlDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: PHPStan\Type\Doctrine\CreateQueryDynamicReturnTypeExtension
arguments:
objectMetadataResolver: @PHPStan\Type\Doctrine\ObjectMetadataResolver
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: PHPStan\Type\Doctrine\Query\QueryResultDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: PHPStan\Type\Doctrine\QueryBuilder\Expr\ExpressionBuilderDynamicReturnTypeExtension
arguments:
Expand Down
101 changes: 101 additions & 0 deletions src/Type/Doctrine/CreateQueryDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Doctrine;

use Doctrine\Common\CommonException;
use Doctrine\DBAL\DBALException;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\ORMException;
use Doctrine\ORM\Query;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Doctrine\Query\QueryResultTypeBuilder;
use PHPStan\Type\Doctrine\Query\QueryResultTypeWalker;
use PHPStan\Type\Doctrine\Query\QueryType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeTraverser;
use PHPStan\Type\UnionType;

/**
* Infers TResult in Query<TResult> on EntityManagerInterface::createQuery()
*/
final class CreateQueryDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{

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

/** @var DescriptorRegistry */
private $descriptorRegistry;

public function __construct(ObjectMetadataResolver $objectMetadataResolver, DescriptorRegistry $descriptorRegistry)
{
$this->objectMetadataResolver = $objectMetadataResolver;
$this->descriptorRegistry = $descriptorRegistry;
}

public function getClass(): string
{
return EntityManagerInterface::class;
}

public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'createQuery';
}

public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
): Type
{
$queryStringArgIndex = 0;
$args = $methodCall->getArgs();

if (!isset($args[$queryStringArgIndex])) {
return new GenericObjectType(
Query::class,
[new MixedType()]
);
}

$argType = $scope->getType($args[$queryStringArgIndex]->value);

return TypeTraverser::map($argType, function (Type $type, callable $traverse): Type {
if ($type instanceof UnionType || $type instanceof IntersectionType) {
return $traverse($type);
}
if ($type instanceof ConstantStringType) {
$queryString = $type->getValue();

$em = $this->objectMetadataResolver->getObjectManager();
if (!$em instanceof EntityManagerInterface) {
return new QueryType($queryString, null);
}

$typeBuilder = new QueryResultTypeBuilder();

try {
$query = $em->createQuery($queryString);
QueryResultTypeWalker::walk($query, $typeBuilder, $this->descriptorRegistry);
} catch (ORMException | DBALException | CommonException $e) {
return new QueryType($queryString, null);
}

return new QueryType($queryString, $typeBuilder->getResultType());
}
return new GenericObjectType(
Query::class,
[new MixedType()]
);
});
}

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/ArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ public function getWritableToDatabaseType(): Type
return new \PHPStan\Type\ArrayType(new MixedType(), new MixedType());
}

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

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/BigIntType.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ public function getWritableToDatabaseType(): Type
return TypeCombinator::union(new \PHPStan\Type\StringType(), new \PHPStan\Type\IntegerType());
}

public function getDatabaseInternalType(): Type
{
return new \PHPStan\Type\IntegerType();
}

}
6 changes: 6 additions & 0 deletions src/Type/Doctrine/Descriptors/BinaryType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PHPStan\Type\MixedType;
use PHPStan\Type\ResourceType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;

class BinaryType implements DoctrineTypeDescriptor
Expand All @@ -24,4 +25,9 @@ public function getWritableToDatabaseType(): Type
return new MixedType();
}

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

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/BlobType.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
return new MixedType();
}

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

}
9 changes: 9 additions & 0 deletions src/Type/Doctrine/Descriptors/BooleanType.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PHPStan\Type\Doctrine\Descriptors;

use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;

class BooleanType implements DoctrineTypeDescriptor
{
Expand All @@ -22,4 +23,12 @@ public function getWritableToDatabaseType(): Type
return new \PHPStan\Type\BooleanType();
}

public function getDatabaseInternalType(): Type
{
return TypeCombinator::union(
new \PHPStan\Type\Constant\ConstantIntegerType(0),
new \PHPStan\Type\Constant\ConstantIntegerType(1)
);
}

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/DateImmutableType.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
return new ObjectType(DateTimeImmutable::class);
}

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

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/DateIntervalType.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
return new ObjectType(DateInterval::class);
}

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

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/DateTimeImmutableType.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
return new ObjectType(DateTimeImmutable::class);
}

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

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/DateTimeType.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
return new \PHPStan\Type\ObjectType(DateTimeInterface::class);
}

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

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/DateTimeTzImmutableType.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
return new ObjectType(DateTimeImmutable::class);
}

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

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/DateTimeTzType.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
return new \PHPStan\Type\ObjectType(DateTimeInterface::class);
}

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

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/DateType.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
return new \PHPStan\Type\ObjectType(DateTimeInterface::class);
}

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

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/DecimalType.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ public function getWritableToDatabaseType(): Type
return TypeCombinator::union(new \PHPStan\Type\StringType(), new \PHPStan\Type\FloatType(), new \PHPStan\Type\IntegerType());
}

public function getDatabaseInternalType(): Type
{
return TypeCombinator::union(new \PHPStan\Type\FloatType(), new \PHPStan\Type\IntegerType());
}

}
2 changes: 2 additions & 0 deletions src/Type/Doctrine/Descriptors/DoctrineTypeDescriptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ public function getWritableToPropertyType(): Type;

public function getWritableToDatabaseType(): Type;

public function getDatabaseInternalType(): Type;

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/FloatType.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ public function getWritableToDatabaseType(): Type
return TypeCombinator::union(new \PHPStan\Type\FloatType(), new \PHPStan\Type\IntegerType());
}

public function getDatabaseInternalType(): Type
{
return TypeCombinator::union(new \PHPStan\Type\FloatType(), new \PHPStan\Type\IntegerType());
}

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/GuidType.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ public function getWritableToDatabaseType(): Type
return new StringType();
}

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

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/IntegerType.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,9 @@ public function getWritableToDatabaseType(): Type
return new \PHPStan\Type\IntegerType();
}

public function getDatabaseInternalType(): Type
{
return new \PHPStan\Type\IntegerType();
}

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/JsonArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ public function getWritableToDatabaseType(): Type
return new \PHPStan\Type\ArrayType(new MixedType(), new MixedType());
}

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

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/JsonType.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,9 @@ public function getWritableToDatabaseType(): Type
return self::getJsonType();
}

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

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/ObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ public function getWritableToDatabaseType(): Type
return new ObjectWithoutClassType();
}

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

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/Ramsey/UuidTypeDescriptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,9 @@ public function getWritableToDatabaseType(): Type
);
}

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

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/ReflectionDescriptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,9 @@ public function getWritableToDatabaseType(): Type
return TypeCombinator::removeNull($type);
}

public function getDatabaseInternalType(): Type
{
return new \PHPStan\Type\MixedType();
}

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/SimpleArrayType.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,9 @@ public function getWritableToDatabaseType(): Type
return new \PHPStan\Type\ArrayType(new MixedType(), new MixedType());
}

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

}
5 changes: 5 additions & 0 deletions src/Type/Doctrine/Descriptors/SmallIntType.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,9 @@ public function getWritableToDatabaseType(): Type
return new \PHPStan\Type\IntegerType();
}

public function getDatabaseInternalType(): Type
{
return new \PHPStan\Type\IntegerType();
}

}
Loading