Skip to content

Commit 496aee1

Browse files
malarzmmcfedr
authored andcommitted
Resolve custom repository classes
1 parent 77a6f9e commit 496aee1

File tree

3 files changed

+122
-4
lines changed

3 files changed

+122
-4
lines changed

extension.neon

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
parameters:
22
doctrine:
33
repositoryClass: Doctrine\ORM\EntityRepository
4+
metadata: []
45
allCollectionsSelectable: true
56

67
conditionalTags:
@@ -21,7 +22,7 @@ services:
2122
-
2223
class: PHPStan\Type\Doctrine\ObjectManagerGetRepositoryDynamicReturnTypeExtension
2324
arguments:
24-
repositoryClass: %doctrine.repositoryClass%
25+
metadataProvider: @PHPStan\DoctrineClassMetadataProvider
2526
tags:
2627
- phpstan.broker.dynamicMethodReturnTypeExtension
2728
-
@@ -32,3 +33,9 @@ services:
3233
class: PHPStan\Type\Doctrine\ObjectRepositoryDynamicReturnTypeExtension
3334
tags:
3435
- phpstan.broker.dynamicMethodReturnTypeExtension
36+
-
37+
class: PHPStan\DoctrineClassMetadataProvider
38+
arguments:
39+
repositoryClass: %doctrine.repositoryClass%
40+
mapping: %doctrine.metadata%
41+
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan;
4+
5+
use Doctrine\Common\Annotations\AnnotationReader;
6+
use Doctrine\Common\EventManager;
7+
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriver;
8+
use Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain;
9+
use Doctrine\ORM\Configuration;
10+
use Doctrine\ORM\EntityManager;
11+
use Doctrine\ORM\Mapping;
12+
use Doctrine\ORM\Proxy\ProxyFactory;
13+
14+
class DoctrineClassMetadataProvider
15+
{
16+
17+
/**
18+
* @var EntityManager
19+
*/
20+
private $em;
21+
22+
/**
23+
* @var string
24+
*/
25+
private $repositoryClass;
26+
27+
public function __construct(string $repositoryClass, array $mapping)
28+
{
29+
$configuration = new Configuration();
30+
$configuration->setDefaultRepositoryClassName($repositoryClass);
31+
$configuration->setMetadataDriverImpl($this->setupMappingDriver($mapping));
32+
$configuration->setProxyDir('/dev/null');
33+
$configuration->setProxyNamespace('__DP__');
34+
$configuration->setAutoGenerateProxyClasses(ProxyFactory::AUTOGENERATE_EVAL);
35+
$evm = new EventManager();
36+
$this->em = EntityManager::create(
37+
\Doctrine\DBAL\DriverManager::getConnection(['host' => '/:memory:', 'driver' => 'pdo_sqlite'], $configuration, $evm),
38+
$configuration,
39+
$evm
40+
);
41+
$this->repositoryClass = $repositoryClass;
42+
}
43+
44+
private function setupMappingDriver(array $mapping): MappingDriver
45+
{
46+
$driver = new MappingDriverChain();
47+
foreach ($mapping as $namespace => $config) {
48+
switch ($config['type']) {
49+
case 'annotation':
50+
$nested = new Mapping\Driver\AnnotationDriver(new AnnotationReader(), $config['paths']);
51+
break;
52+
case 'yml':
53+
case 'yaml':
54+
$nested = new Mapping\Driver\YamlDriver($config['paths']);
55+
break;
56+
case 'xml':
57+
$nested = new Mapping\Driver\XmlDriver($config['paths']);
58+
break;
59+
default:
60+
throw new \InvalidArgumentException('Unknown mapping type: ' . $config['type']);
61+
}
62+
$driver->addDriver($nested, $namespace);
63+
}
64+
return $driver;
65+
}
66+
67+
public function getBaseRepositoryClass(): string
68+
{
69+
return $this->repositoryClass;
70+
}
71+
72+
/**
73+
* @throws \Doctrine\Common\Persistence\Mapping\MappingException
74+
* @throws \ReflectionException
75+
*/
76+
public function getMetadataFor(string $className): Mapping\ClassMetadataInfo
77+
{
78+
return $this->em->getClassMetadata($className);
79+
}
80+
81+
}

src/Type/Doctrine/ObjectManagerGetRepositoryDynamicReturnTypeExtension.php

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
namespace PHPStan\Type\Doctrine;
44

5+
use Doctrine\Common\Annotations\AnnotationRegistry;
6+
use Doctrine\Common\Persistence\Mapping\MappingException;
57
use PhpParser\Node\Expr\MethodCall;
68
use PHPStan\Analyser\Scope;
9+
use PHPStan\DoctrineClassMetadataProvider;
710
use PHPStan\Reflection\MethodReflection;
811
use PHPStan\Reflection\ParametersAcceptorSelector;
912
use PHPStan\Type\Constant\ConstantStringType;
@@ -16,10 +19,16 @@ class ObjectManagerGetRepositoryDynamicReturnTypeExtension implements \PHPStan\T
1619
/** @var string */
1720
private $repositoryClass;
1821

19-
public function __construct(string $repositoryClass)
22+
/**
23+
* @var DoctrineClassMetadataProvider
24+
*/
25+
private $metadataProvider;
26+
27+
public function __construct(DoctrineClassMetadataProvider $metadataProvider)
2028
{
21-
$this->repositoryClass = $repositoryClass;
22-
}
29+
$this->metadataProvider = $metadataProvider;
30+
AnnotationRegistry::registerLoader('class_exists');
31+
}
2332

2433
public function getClass(): string
2534
{
@@ -48,6 +57,27 @@ public function getTypeFromMethodCall(
4857
}
4958

5059
return new ObjectRepositoryType($argType->getValue(), $this->repositoryClass);
60+
61+
$class = $arg->class;
62+
if (!($class instanceof \PhpParser\Node\Name)) {
63+
return $methodReflection->getReturnType();
64+
}
65+
66+
$class = (string) $class;
67+
if ($class === 'static') {
68+
return $methodReflection->getReturnType();
69+
}
70+
71+
if ($class === 'self') {
72+
$class = $scope->getClassReflection()->getName();
73+
}
74+
75+
try {
76+
$metadata = $this->metadataProvider->getMetadataFor($class);
77+
return new EntityRepositoryType($class, $metadata->customRepositoryClassName ?: $this->metadataProvider->getBaseRepositoryClass());
78+
} catch (MappingException $e) {
79+
return new EntityRepositoryType($class, $this->metadataProvider->getBaseRepositoryClass());
80+
}
5181
}
5282

5383
}

0 commit comments

Comments
 (0)