Skip to content

Commit 8c22f24

Browse files
committed
Bleeding edge - BetterReflectionProvider
Use PHPStan's static reflection instead of runtime reflection.
1 parent 3dbb999 commit 8c22f24

17 files changed

+145
-13
lines changed

extension.neon

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ services:
9696
class: PHPStan\Type\Doctrine\ObjectMetadataResolver
9797
arguments:
9898
objectManagerLoader: %doctrine.objectManagerLoader%
99+
bleedingEdge: %featureToggles.bleedingEdge%
99100
-
100101
class: PHPStan\Type\Doctrine\QueryBuilder\QueryBuilderGetDqlDynamicReturnTypeExtension
101102
arguments:

phpcs.xml

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
</rule>
3434
<rule ref="SlevomatCodingStandard.TypeHints.ParameterTypeHint">
3535
<exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingTraversableTypeHintSpecification"/>
36+
<exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingAnyTypeHint"/>
3637
<exclude name="SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint"/>
3738
</rule>
3839
<rule ref="SlevomatCodingStandard.TypeHints.ParameterTypeHint.UselessAnnotation">

phpstan-baseline.neon

+10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
parameters:
22
ignoreErrors:
3+
-
4+
message: "#^Method PHPStan\\\\Doctrine\\\\Mapping\\\\BetterReflectionService\\:\\:getParentClasses\\(\\) should return array\\<class\\-string\\> but returns array\\<string\\>\\.$#"
5+
count: 1
6+
path: src/Doctrine/Mapping/BetterReflectionService.php
7+
8+
-
9+
message: "#^PHPDoc tag @var with type ReflectionClass\\<T of object\\> is not subtype of type PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionClass\\|PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#"
10+
count: 1
11+
path: src/Doctrine/Mapping/BetterReflectionService.php
12+
313
-
414
message: "#^Calling PHPStan\\\\Type\\\\ParserNodeTypeToPHPStanType\\:\\:resolve\\(\\) is not covered by backward compatibility promise\\. The method might change in a minor PHPStan version\\.$#"
515
count: 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Doctrine\Mapping;
4+
5+
use Doctrine\Persistence\Mapping\MappingException;
6+
use Doctrine\Persistence\Mapping\ReflectionService;
7+
use PHPStan\Reflection\ReflectionProvider;
8+
use ReflectionClass;
9+
10+
class BetterReflectionService implements ReflectionService
11+
{
12+
13+
/** @var ReflectionProvider */
14+
private $reflectionProvider;
15+
16+
public function __construct(ReflectionProvider $reflectionProvider)
17+
{
18+
$this->reflectionProvider = $reflectionProvider;
19+
}
20+
21+
public function getParentClasses($class)
22+
{
23+
if (!$this->reflectionProvider->hasClass($class)) {
24+
throw MappingException::nonExistingClass($class);
25+
}
26+
27+
$classReflection = $this->reflectionProvider->getClass($class);
28+
29+
return $classReflection->getParentClassesNames();
30+
}
31+
32+
public function getClassShortName($class)
33+
{
34+
return $this->getClass($class)->getShortName();
35+
}
36+
37+
public function getClassNamespace($class)
38+
{
39+
return $this->getClass($class)->getNamespaceName();
40+
}
41+
42+
/**
43+
* @param class-string<T> $class
44+
* @return ReflectionClass<T>
45+
*
46+
* @template T of object
47+
*/
48+
public function getClass($class)
49+
{
50+
if (!$this->reflectionProvider->hasClass($class)) {
51+
throw MappingException::nonExistingClass($class);
52+
}
53+
54+
$classReflection = $this->reflectionProvider->getClass($class);
55+
56+
/** @var ReflectionClass<T> */
57+
return $classReflection->getNativeReflection();
58+
}
59+
60+
public function getAccessibleProperty($class, $property)
61+
{
62+
$classReflection = $this->getClass($class);
63+
$property = $classReflection->getProperty($property);
64+
$property->setAccessible(true);
65+
66+
return $property;
67+
}
68+
69+
public function hasPublicMethod($class, $method)
70+
{
71+
$classReflection = $this->getClass($class);
72+
if (!$classReflection->hasMethod($method)) {
73+
return false;
74+
}
75+
76+
return $classReflection->getMethod($method)->isPublic();
77+
}
78+
79+
}

src/Doctrine/Mapping/ClassMetadataFactory.php

+30
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use Doctrine\ORM\Mapping\ClassMetadata;
1010
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
1111
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
12+
use Doctrine\Persistence\Mapping\ReflectionService;
13+
use PHPStan\Reflection\ReflectionProvider;
1214
use ReflectionClass;
1315
use function class_exists;
1416
use function count;
@@ -17,6 +19,21 @@
1719
class ClassMetadataFactory extends \Doctrine\ORM\Mapping\ClassMetadataFactory
1820
{
1921

22+
/** @var ReflectionProvider */
23+
private $reflectionProvider;
24+
25+
/** @var bool */
26+
private $bleedingEdge;
27+
28+
/** @var BetterReflectionService|null */
29+
private $reflectionService = null;
30+
31+
public function __construct(ReflectionProvider $reflectionProvider, bool $bleedingEdge)
32+
{
33+
$this->reflectionProvider = $reflectionProvider;
34+
$this->bleedingEdge = $bleedingEdge;
35+
}
36+
2037
protected function initialize(): void
2138
{
2239
$parentReflection = new ReflectionClass(parent::class);
@@ -52,6 +69,19 @@ protected function initialize(): void
5269
$targetPlatformProperty->setValue($this, $platform);
5370
}
5471

72+
public function getReflectionService(): ReflectionService
73+
{
74+
if (!$this->bleedingEdge) {
75+
return parent::getReflectionService();
76+
}
77+
78+
if ($this->reflectionService === null) {
79+
$this->reflectionService = new BetterReflectionService($this->reflectionProvider);
80+
}
81+
82+
return $this->reflectionService;
83+
}
84+
5585
/**
5686
* @template T of object
5787
* @param class-string<T> $className

src/Type/Doctrine/ObjectMetadataResolver.php

+13-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Doctrine\ORM\Mapping\MappingException;
88
use Doctrine\Persistence\ObjectManager;
99
use PHPStan\Doctrine\Mapping\ClassMetadataFactory;
10+
use PHPStan\Reflection\ReflectionProvider;
1011
use PHPStan\ShouldNotHappenException;
1112
use ReflectionException;
1213
use function class_exists;
@@ -17,20 +18,30 @@
1718
final class ObjectMetadataResolver
1819
{
1920

21+
/** @var ReflectionProvider */
22+
private $reflectionProvider;
23+
2024
/** @var string|null */
2125
private $objectManagerLoader;
2226

27+
/** @var bool */
28+
private $bleedingEdge;
29+
2330
/** @var ObjectManager|false|null */
2431
private $objectManager;
2532

2633
/** @var ClassMetadataFactory|null */
2734
private $metadataFactory;
2835

2936
public function __construct(
30-
?string $objectManagerLoader
37+
ReflectionProvider $reflectionProvider,
38+
?string $objectManagerLoader,
39+
bool $bleedingEdge
3140
)
3241
{
42+
$this->reflectionProvider = $reflectionProvider;
3343
$this->objectManagerLoader = $objectManagerLoader;
44+
$this->bleedingEdge = $bleedingEdge;
3445
}
3546

3647
public function hasObjectManagerLoader(): bool
@@ -97,7 +108,7 @@ private function getMetadataFactory(): ?ClassMetadataFactory
97108
return null;
98109
}
99110

100-
return $this->metadataFactory = new ClassMetadataFactory();
111+
return $this->metadataFactory = new ClassMetadataFactory($this->reflectionProvider, $this->bleedingEdge);
101112
}
102113

103114
/**

tests/Rules/Doctrine/ORM/DqlRuleTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class DqlRuleTest extends RuleTestCase
1414

1515
protected function getRule(): Rule
1616
{
17-
return new DqlRule(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php'));
17+
return new DqlRule(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true));
1818
}
1919

2020
public function testRule(): void

tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ protected function getRule(): Rule
5656
}
5757

5858
return new EntityColumnRule(
59-
new ObjectMetadataResolver($this->objectManagerLoader),
59+
new ObjectMetadataResolver($this->createReflectionProvider(), $this->objectManagerLoader, true),
6060
new DescriptorRegistry([
6161
new ArrayType(),
6262
new BigIntType(),

tests/Rules/Doctrine/ORM/EntityMappingExceptionRuleTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class EntityMappingExceptionRuleTest extends RuleTestCase
1616
protected function getRule(): Rule
1717
{
1818
return new EntityMappingExceptionRule(
19-
new ObjectMetadataResolver(__DIR__ . '/entity-manager.php')
19+
new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true)
2020
);
2121
}
2222

tests/Rules/Doctrine/ORM/EntityNotFinalRuleTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class EntityNotFinalRuleTest extends RuleTestCase
1919
protected function getRule(): Rule
2020
{
2121
return new EntityNotFinalRule(
22-
new ObjectMetadataResolver($this->objectManagerLoader)
22+
new ObjectMetadataResolver($this->createReflectionProvider(), $this->objectManagerLoader, true)
2323
);
2424
}
2525

tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class EntityRelationRuleTest extends RuleTestCase
2323
protected function getRule(): Rule
2424
{
2525
return new EntityRelationRule(
26-
new ObjectMetadataResolver($this->objectManagerLoader),
26+
new ObjectMetadataResolver($this->createReflectionProvider(), $this->objectManagerLoader, true),
2727
$this->allowNullablePropertyForRequiredField,
2828
true
2929
);

tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class QueryBuilderDqlRuleSlowTest extends RuleTestCase
1414

1515
protected function getRule(): Rule
1616
{
17-
return new QueryBuilderDqlRule(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php'), true);
17+
return new QueryBuilderDqlRule(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true), true);
1818
}
1919

2020
public function testRule(): void

tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class QueryBuilderDqlRuleTest extends RuleTestCase
1414

1515
protected function getRule(): Rule
1616
{
17-
return new QueryBuilderDqlRule(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php'), true);
17+
return new QueryBuilderDqlRule(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true), true);
1818
}
1919

2020
public function testRule(): void

tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class RepositoryMethodCallRuleTest extends RuleTestCase
1414

1515
protected function getRule(): Rule
1616
{
17-
return new RepositoryMethodCallRule(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php'));
17+
return new RepositoryMethodCallRule(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true));
1818
}
1919

2020
/**

tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleWithoutObjectManagerLoaderTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class RepositoryMethodCallRuleWithoutObjectManagerLoaderTest extends RuleTestCas
1414

1515
protected function getRule(): Rule
1616
{
17-
return new RepositoryMethodCallRule(new ObjectMetadataResolver(null));
17+
return new RepositoryMethodCallRule(new ObjectMetadataResolver($this->createReflectionProvider(), null, true));
1818
}
1919

2020
/**

tests/Rules/Properties/MissingGedmoByPhpDocPropertyAssignRuleTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ protected function getRule(): Rule
2323
protected function getReadWritePropertiesExtensions(): array
2424
{
2525
return [
26-
new PropertiesExtension(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php')),
26+
new PropertiesExtension(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true)),
2727
];
2828
}
2929

tests/Rules/Properties/MissingGedmoPropertyAssignRuleTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ protected function getRule(): Rule
2424
protected function getReadWritePropertiesExtensions(): array
2525
{
2626
return [
27-
new PropertiesExtension(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php')),
27+
new PropertiesExtension(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true)),
2828
];
2929
}
3030

0 commit comments

Comments
 (0)