Skip to content

Commit 50b5322

Browse files
committed
Detect #[Column(enumType)], adjust ReflectionBasedMemberUsageProvider
1 parent 17903c7 commit 50b5322

File tree

4 files changed

+104
-4
lines changed

4 files changed

+104
-4
lines changed

src/Provider/DoctrineUsageProvider.php

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@
1010
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionMethod;
1111
use PHPStan\Node\InClassNode;
1212
use PHPStan\Reflection\ExtendedMethodReflection;
13+
use PHPStan\Reflection\ExtendedPropertyReflection;
1314
use PHPStan\Reflection\MethodReflection;
15+
use ShipMonk\PHPStan\DeadCode\Graph\ClassMemberUsage;
1416
use ShipMonk\PHPStan\DeadCode\Graph\ClassMethodRef;
1517
use ShipMonk\PHPStan\DeadCode\Graph\ClassMethodUsage;
18+
use ShipMonk\PHPStan\DeadCode\Graph\EnumCaseRef;
19+
use ShipMonk\PHPStan\DeadCode\Graph\EnumCaseUsage;
1620
use ShipMonk\PHPStan\DeadCode\Graph\UsageOrigin;
1721

1822
class DoctrineUsageProvider implements MemberUsageProvider
@@ -39,7 +43,7 @@ public function getUsages(
3943
if ($node instanceof InClassNode) { // @phpstan-ignore phpstanApi.instanceofAssumption
4044
$usages = [
4145
...$usages,
42-
...$this->getUsagesFromReflection($node),
46+
...$this->getUsagesFromReflection($node, $scope),
4347
];
4448
}
4549

@@ -54,15 +58,25 @@ public function getUsages(
5458
}
5559

5660
/**
57-
* @return list<ClassMethodUsage>
61+
* @return list<ClassMemberUsage>
5862
*/
59-
private function getUsagesFromReflection(InClassNode $node): array
63+
private function getUsagesFromReflection(InClassNode $node, Scope $scope): array
6064
{
6165
$classReflection = $node->getClassReflection();
6266
$nativeReflection = $classReflection->getNativeReflection();
6367

6468
$usages = [];
6569

70+
foreach ($nativeReflection->getProperties() as $nativePropertyReflection) {
71+
$propertyName = $nativePropertyReflection->name;
72+
$propertyReflection = $classReflection->getProperty($propertyName, $scope);
73+
74+
$usages = [
75+
...$usages,
76+
...$this->getUsagesOfEnumColumn($classReflection->getName(), $propertyName, $propertyReflection),
77+
];
78+
}
79+
6680
foreach ($nativeReflection->getMethods() as $method) {
6781
if ($method->getDeclaringClass()->getName() !== $nativeReflection->getName()) {
6882
continue;
@@ -243,4 +257,36 @@ private function createMethodUsage(
243257
);
244258
}
245259

260+
/**
261+
* @return list<EnumCaseUsage>
262+
*/
263+
private function getUsagesOfEnumColumn(string $className, string $propertyName, ExtendedPropertyReflection $property): array
264+
{
265+
$usages = [];
266+
267+
foreach ($property->getAttributes() as $attribute) {
268+
if ($attribute->getName() !== 'Doctrine\ORM\Mapping\Column') {
269+
continue;
270+
}
271+
272+
foreach ($attribute->getArgumentTypes() as $name => $type) {
273+
if ($name !== 'enumType') {
274+
continue;
275+
}
276+
277+
foreach ($type->getConstantStrings() as $constantString) {
278+
$usages[] = new EnumCaseUsage(
279+
UsageOrigin::createVirtual($this, VirtualUsageData::withNote("Used in enumType of #[Column] of $className::$propertyName")),
280+
new EnumCaseRef(
281+
$constantString->getValue(),
282+
null,
283+
),
284+
);
285+
}
286+
}
287+
}
288+
289+
return $usages;
290+
}
291+
246292
}

src/Provider/ReflectionBasedMemberUsageProvider.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@
77
use PHPStan\Node\InClassNode;
88
use PHPStan\Reflection\ClassReflection;
99
use ReflectionClassConstant;
10+
use ReflectionEnum;
11+
use ReflectionEnumUnitCase;
1012
use ReflectionMethod;
1113
use ShipMonk\PHPStan\DeadCode\Graph\ClassConstantRef;
1214
use ShipMonk\PHPStan\DeadCode\Graph\ClassConstantUsage;
1315
use ShipMonk\PHPStan\DeadCode\Graph\ClassMemberUsage;
1416
use ShipMonk\PHPStan\DeadCode\Graph\ClassMethodRef;
1517
use ShipMonk\PHPStan\DeadCode\Graph\ClassMethodUsage;
18+
use ShipMonk\PHPStan\DeadCode\Graph\EnumCaseRef;
19+
use ShipMonk\PHPStan\DeadCode\Graph\EnumCaseUsage;
1620
use ShipMonk\PHPStan\DeadCode\Graph\UsageOrigin;
1721
use function array_merge;
1822

@@ -33,6 +37,7 @@ public function getUsages(
3337
return array_merge(
3438
$this->getMethodUsages($classReflection),
3539
$this->getConstantUsages($classReflection),
40+
$this->getEnumCaseUsages($classReflection),
3641
);
3742
}
3843

@@ -49,6 +54,11 @@ protected function shouldMarkConstantAsUsed(ReflectionClassConstant $constant):
4954
return null; // Expected to be overridden by subclasses.
5055
}
5156

57+
protected function shouldMarkEnumCaseAsUsed(ReflectionEnumUnitCase $enumCase): ?VirtualUsageData
58+
{
59+
return null; // Expected to be overridden by subclasses.
60+
}
61+
5262
/**
5363
* @return list<ClassMethodUsage>
5464
*/
@@ -97,6 +107,30 @@ private function getConstantUsages(ClassReflection $classReflection): array
97107
return $usages;
98108
}
99109

110+
/**
111+
* @return list<EnumCaseUsage>
112+
*/
113+
private function getEnumCaseUsages(ClassReflection $classReflection): array
114+
{
115+
$nativeClassReflection = $classReflection->getNativeReflection();
116+
117+
if (!$nativeClassReflection instanceof ReflectionEnum) {
118+
return [];
119+
}
120+
121+
$usages = [];
122+
123+
foreach ($nativeClassReflection->getCases() as $nativeEnumCaseReflection) {
124+
$usage = $this->shouldMarkEnumCaseAsUsed($nativeEnumCaseReflection);
125+
126+
if ($usage !== null) {
127+
$usages[] = $this->createEnumCaseUsage($nativeEnumCaseReflection, $usage);
128+
}
129+
}
130+
131+
return $usages;
132+
}
133+
100134
private function createConstantUsage(
101135
ReflectionClassConstant $constantReflection,
102136
VirtualUsageData $data
@@ -127,4 +161,15 @@ private function createMethodUsage(
127161
);
128162
}
129163

164+
private function createEnumCaseUsage(ReflectionEnumUnitCase $enumCaseReflection, VirtualUsageData $usage): EnumCaseUsage
165+
{
166+
return new EnumCaseUsage(
167+
UsageOrigin::createVirtual($this, $usage),
168+
new EnumCaseRef(
169+
$enumCaseReflection->getDeclaringClass()->getName(),
170+
$enumCaseReflection->getName(),
171+
),
172+
);
173+
}
174+
130175
}

tests/Rule/DeadCodeRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -760,7 +760,7 @@ public static function provideFiles(): Traversable
760760
yield 'provider-symfony-7.1' => [__DIR__ . '/data/providers/symfony-gte71.php', self::requiresPhp(8_00_00) && self::requiresPackage('symfony/dependency-injection', '>= 7.1')];
761761
yield 'provider-twig' => [__DIR__ . '/data/providers/twig.php', self::requiresPhp(8_00_00)];
762762
yield 'provider-phpunit' => [__DIR__ . '/data/providers/phpunit.php', self::requiresPhp(8_00_00)];
763-
yield 'provider-doctrine' => [__DIR__ . '/data/providers/doctrine.php', self::requiresPhp(8_00_00)];
763+
yield 'provider-doctrine' => [__DIR__ . '/data/providers/doctrine.php', self::requiresPhp(8_01_00)];
764764
yield 'provider-phpstan' => [__DIR__ . '/data/providers/phpstan.php'];
765765
yield 'provider-nette' => [__DIR__ . '/data/providers/nette.php'];
766766
yield 'provider-apiphpdoc' => [__DIR__ . '/data/providers/api-phpdoc.php'];

tests/Rule/data/providers/doctrine.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,22 @@
22

33
namespace Doctrine;
44

5+
use Doctrine\DBAL\Types\Types;
56
use Doctrine\ORM\EntityManagerInterface;
67
use Doctrine\ORM\EntityRepository;
78
use Doctrine\ORM\Mapping\ClassMetadata;
89

10+
enum InvoiceStatus: string {
11+
case Closed = 'closed';
12+
case Open = 'open';
13+
}
14+
915
class MyEntity
1016
{
1117

18+
#[\Doctrine\ORM\Mapping\Column(type: Types::STRING, enumType: InvoiceStatus::class)]
19+
private InvoiceStatus $status;
20+
1221
#[\Doctrine\ORM\Mapping\PreUpdate]
1322
public function onUpdate(PreUpdateEventArgs $args): void {}
1423

0 commit comments

Comments
 (0)