Skip to content

Commit 56efd04

Browse files
xificurkclaude
andcommitted
Remove Reflector from StubFilesExtensionLoader, add regression test
Replace the expensive `reflectClass('...ServiceEntityRepository')` call with a cheap `InstalledVersions::getVersion('doctrine/doctrine-bundle')` + `version_compare` check. LazyServiceEntityRepository became ServiceEntityRepository's parent in DoctrineBundle 2.8.1, so the version threshold is exact. Add StubFilesExtensionLoaderTest with ThrowingSourceLocator registered as `betterReflectionSourceLocator` in the test container — any future attempt to re-introduce reflectClass() in getFiles() will immediately throw. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 8af50e2 commit 56efd04

4 files changed

Lines changed: 93 additions & 25 deletions

File tree

src/Stubs/Doctrine/StubFilesExtensionLoader.php

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,15 @@
44

55
use Composer\InstalledVersions;
66
use OutOfBoundsException;
7-
use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound;
8-
use PHPStan\BetterReflection\Reflector\Reflector;
97
use PHPStan\PhpDoc\StubFilesExtension;
108
use function class_exists;
119
use function dirname;
1210
use function strpos;
11+
use function version_compare;
1312

1413
class StubFilesExtensionLoader implements StubFilesExtension
1514
{
1615

17-
private Reflector $reflector;
18-
19-
public function __construct(
20-
Reflector $reflector
21-
)
22-
{
23-
$this->reflector = $reflector;
24-
}
25-
2616
public function getFiles(): array
2717
{
2818
$stubsDir = dirname(dirname(dirname(__DIR__))) . '/stubs';
@@ -36,20 +26,7 @@ public function getFiles(): array
3626
$files[] = $stubsDir . '/DBAL/Connection.stub';
3727
}
3828

39-
$hasLazyServiceEntityRepositoryAsParent = false;
40-
41-
try {
42-
$serviceEntityRepository = $this->reflector->reflectClass('Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository');
43-
if ($serviceEntityRepository->getParentClass() !== null) {
44-
/** @var class-string $lazyServiceEntityRepositoryName */
45-
$lazyServiceEntityRepositoryName = 'Doctrine\Bundle\DoctrineBundle\Repository\LazyServiceEntityRepository';
46-
$hasLazyServiceEntityRepositoryAsParent = $serviceEntityRepository->getParentClass()->getName() === $lazyServiceEntityRepositoryName;
47-
}
48-
} catch (IdentifierNotFound $e) {
49-
// pass
50-
}
51-
52-
if ($hasLazyServiceEntityRepositoryAsParent) {
29+
if ($this->hasLazyServiceEntityRepository()) {
5330
$files[] = $stubsDir . '/LazyServiceEntityRepository.stub';
5431
} else {
5532
$files[] = $stubsDir . '/ServiceEntityRepository.stub';
@@ -73,6 +50,25 @@ public function getFiles(): array
7350
return $files;
7451
}
7552

53+
private function hasLazyServiceEntityRepository(): bool
54+
{
55+
if (!class_exists(InstalledVersions::class)) {
56+
return false;
57+
}
58+
59+
try {
60+
$bundleVersion = InstalledVersions::getVersion('doctrine/doctrine-bundle');
61+
} catch (OutOfBoundsException $e) {
62+
return false;
63+
}
64+
65+
if ($bundleVersion === null) {
66+
return false;
67+
}
68+
69+
return version_compare($bundleVersion, '2.8.1', '>=') && version_compare($bundleVersion, '3.0.0', '<');
70+
}
71+
7672
private function isInstalledVersion(string $package, int $majorVersion): bool
7773
{
7874
if (!class_exists(InstalledVersions::class)) {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Stubs;
4+
5+
use PHPStan\Stubs\Doctrine\StubFilesExtensionLoader;
6+
use PHPStan\Testing\PHPStanTestCase;
7+
use function dirname;
8+
use function in_array;
9+
10+
class StubFilesExtensionLoaderTest extends PHPStanTestCase
11+
{
12+
13+
public function testGetFilesDoesNotTriggerSourceLocator(): void
14+
{
15+
$loader = self::getContainer()->getByType(StubFilesExtensionLoader::class);
16+
$files = $loader->getFiles();
17+
18+
$this->assertNotEmpty($files);
19+
20+
$stubsDir = dirname(dirname(__DIR__)) . '/stubs';
21+
$hasService = in_array($stubsDir . '/ServiceEntityRepository.stub', $files, true);
22+
$hasLazy = in_array($stubsDir . '/LazyServiceEntityRepository.stub', $files, true);
23+
$this->assertTrue(
24+
($hasService || $hasLazy) && !($hasService && $hasLazy),
25+
'Exactly one ServiceEntityRepository stub variant must be selected',
26+
);
27+
foreach ($files as $file) {
28+
$this->assertFileExists($file);
29+
}
30+
}
31+
32+
/** @return string[] */
33+
public static function getAdditionalConfigFiles(): array
34+
{
35+
return [__DIR__ . '/phpstan.neon'];
36+
}
37+
38+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Stubs;
4+
5+
use LogicException;
6+
use PHPStan\BetterReflection\Identifier\Identifier;
7+
use PHPStan\BetterReflection\Identifier\IdentifierType;
8+
use PHPStan\BetterReflection\Reflection\Reflection;
9+
use PHPStan\BetterReflection\Reflector\Reflector;
10+
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
11+
12+
class ThrowingSourceLocator implements SourceLocator
13+
{
14+
15+
public function locateIdentifier(Reflector $reflector, Identifier $identifier): ?Reflection
16+
{
17+
throw new LogicException('StubFilesExtensionLoader::getFiles() must not trigger SourceLocator::locateIdentifier()');
18+
}
19+
20+
/** @return list<Reflection> */
21+
public function locateIdentifiersByType(Reflector $reflector, IdentifierType $identifierType): array
22+
{
23+
throw new LogicException('StubFilesExtensionLoader::getFiles() must not trigger SourceLocator::locateIdentifiersByType()');
24+
}
25+
26+
}

tests/Stubs/phpstan.neon

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
services:
2+
betterReflectionSourceLocator:
3+
class: PHPStan\Stubs\Doctrine\ThrowingSourceLocator
4+
5+
-
6+
class: PHPStan\Stubs\Doctrine\StubFilesExtensionLoader
7+
tags:
8+
- phpstan.stubFilesExtension

0 commit comments

Comments
 (0)