Skip to content

Commit

Permalink
Dynamic return type extension for EntityRepository methods via a gene…
Browse files Browse the repository at this point in the history
…ric type created by EM::getRepository()
  • Loading branch information
ondrejmirtes committed Oct 4, 2017
1 parent a27368f commit 5fa20b8
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 0 deletions.
8 changes: 8 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,11 @@ services:
class: PHPStan\Type\Doctrine\EntityManagerFindDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: PHPStan\Type\Doctrine\EntityManagerGetRepositoryDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
-
class: PHPStan\Type\Doctrine\EntityRepositoryDynamicReturnTypeExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Doctrine;

use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\Type;

class EntityManagerGetRepositoryDynamicReturnTypeExtension implements \PHPStan\Type\DynamicMethodReturnTypeExtension
{

public static function getClass(): string
{
return \Doctrine\ORM\EntityManager::class;
}

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

public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
): Type
{
if (count($methodCall->args) === 0) {
return $methodReflection->getReturnType();
}
$arg = $methodCall->args[0]->value;
if (!($arg instanceof \PhpParser\Node\Expr\ClassConstFetch)) {
return $methodReflection->getReturnType();
}

$class = $arg->class;
if (!($class instanceof \PhpParser\Node\Name)) {
return $methodReflection->getReturnType();
}

$class = (string) $class;
if ($class === 'static') {
return $methodReflection->getReturnType();
}

if ($class === 'self') {
$class = $scope->getClassReflection()->getName();
}

return new EntityRepositoryType($class);
}

}
51 changes: 51 additions & 0 deletions src/Type/Doctrine/EntityRepositoryDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Doctrine;

use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\ArrayType;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;

class EntityRepositoryDynamicReturnTypeExtension implements \PHPStan\Type\DynamicMethodReturnTypeExtension
{

public static function getClass(): string
{
return \Doctrine\ORM\EntityRepository::class;
}

public function isMethodSupported(MethodReflection $methodReflection): bool
{
$methodName = $methodReflection->getName();
return strpos($methodName, 'findBy') === 0
|| strpos($methodName, 'findOneBy') === 0
|| $methodName === 'findAll'
|| $methodName === 'find';
}

public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
): Type
{
$calledOnType = $scope->getType($methodCall->var);
if (!$calledOnType instanceof EntityRepositoryType) {
return new MixedType();
}
$methodName = $methodReflection->getName();
$entityType = new ObjectType($calledOnType->getEntityClass());

if ($methodName === 'find' || strpos($methodName, 'findOneBy') === 0) {
return TypeCombinator::addNull($entityType);
}

return new ArrayType($entityType);
}

}
31 changes: 31 additions & 0 deletions src/Type/Doctrine/EntityRepositoryType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Doctrine;

use PHPStan\Type\ObjectType;

class EntityRepositoryType extends ObjectType
{

/**
* @var string
*/
private $entityClass;

public function __construct(string $entityClass)
{
parent::__construct(\Doctrine\ORM\EntityRepository::class);
$this->entityClass = $entityClass;
}

public function getEntityClass(): string
{
return $this->entityClass;
}

public function describe(): string
{
return sprintf('%s<%s>', parent::describe(), $this->entityClass);
}

}

0 comments on commit 5fa20b8

Please sign in to comment.