Skip to content

Commit 62a6134

Browse files
committed
Detect #[Column(enumType)], adjust ReflectionBasedMemberUsageProvider
1 parent 435c3d8 commit 62a6134

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
@@ -36,7 +40,7 @@ public function getUsages(Node $node, Scope $scope): array
3640
if ($node instanceof InClassNode) { // @phpstan-ignore phpstanApi.instanceofAssumption
3741
$usages = [
3842
...$usages,
39-
...$this->getUsagesFromReflection($node),
43+
...$this->getUsagesFromReflection($node, $scope),
4044
];
4145
}
4246

@@ -51,15 +55,25 @@ public function getUsages(Node $node, Scope $scope): array
5155
}
5256

5357
/**
54-
* @return list<ClassMethodUsage>
58+
* @return list<ClassMemberUsage>
5559
*/
56-
private function getUsagesFromReflection(InClassNode $node): array
60+
private function getUsagesFromReflection(InClassNode $node, Scope $scope): array
5761
{
5862
$classReflection = $node->getClassReflection();
5963
$nativeReflection = $classReflection->getNativeReflection();
6064

6165
$usages = [];
6266

67+
foreach ($nativeReflection->getProperties() as $nativePropertyReflection) {
68+
$propertyName = $nativePropertyReflection->name;
69+
$propertyReflection = $classReflection->getProperty($propertyName, $scope);
70+
71+
$usages = [
72+
...$usages,
73+
...$this->getUsagesOfEnumColumn($classReflection->getName(), $propertyName, $propertyReflection),
74+
];
75+
}
76+
6377
foreach ($nativeReflection->getMethods() as $method) {
6478
if ($method->getDeclaringClass()->getName() !== $nativeReflection->getName()) {
6579
continue;
@@ -225,4 +239,36 @@ private function createMethodUsage(ExtendedMethodReflection $methodReflection, s
225239
);
226240
}
227241

242+
/**
243+
* @return list<EnumCaseUsage>
244+
*/
245+
private function getUsagesOfEnumColumn(string $className, string $propertyName, ExtendedPropertyReflection $property): array
246+
{
247+
$usages = [];
248+
249+
foreach ($property->getAttributes() as $attribute) {
250+
if ($attribute->getName() !== 'Doctrine\ORM\Mapping\Column') {
251+
continue;
252+
}
253+
254+
foreach ($attribute->getArgumentTypes() as $name => $type) {
255+
if ($name !== 'enumType') {
256+
continue;
257+
}
258+
259+
foreach ($type->getConstantStrings() as $constantString) {
260+
$usages[] = new EnumCaseUsage(
261+
UsageOrigin::createVirtual($this, VirtualUsageData::withNote("Used in enumType of #[Column] of $className::$propertyName")),
262+
new EnumCaseRef(
263+
$constantString->getValue(),
264+
null,
265+
),
266+
);
267+
}
268+
}
269+
}
270+
271+
return $usages;
272+
}
273+
228274
}

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

@@ -30,6 +34,7 @@ public function getUsages(Node $node, Scope $scope): array
3034
return array_merge(
3135
$this->getMethodUsages($classReflection),
3236
$this->getConstantUsages($classReflection),
37+
$this->getEnumCaseUsages($classReflection),
3338
);
3439
}
3540

@@ -46,6 +51,11 @@ protected function shouldMarkConstantAsUsed(ReflectionClassConstant $constant):
4651
return null; // Expected to be overridden by subclasses.
4752
}
4853

54+
protected function shouldMarkEnumCaseAsUsed(ReflectionEnumUnitCase $enumCase): ?VirtualUsageData
55+
{
56+
return null; // Expected to be overridden by subclasses.
57+
}
58+
4959
/**
5060
* @return list<ClassMethodUsage>
5161
*/
@@ -94,6 +104,30 @@ private function getConstantUsages(ClassReflection $classReflection): array
94104
return $usages;
95105
}
96106

107+
/**
108+
* @return list<EnumCaseUsage>
109+
*/
110+
private function getEnumCaseUsages(ClassReflection $classReflection): array
111+
{
112+
$nativeClassReflection = $classReflection->getNativeReflection();
113+
114+
if (!$nativeClassReflection instanceof ReflectionEnum) {
115+
return [];
116+
}
117+
118+
$usages = [];
119+
120+
foreach ($nativeClassReflection->getCases() as $nativeEnumCaseReflection) {
121+
$usage = $this->shouldMarkEnumCaseAsUsed($nativeEnumCaseReflection);
122+
123+
if ($usage !== null) {
124+
$usages[] = $this->createEnumCaseUsage($nativeEnumCaseReflection, $usage);
125+
}
126+
}
127+
128+
return $usages;
129+
}
130+
97131
private function createConstantUsage(ReflectionClassConstant $constantReflection, VirtualUsageData $data): ClassConstantUsage
98132
{
99133
return new ClassConstantUsage(
@@ -118,4 +152,15 @@ private function createMethodUsage(ReflectionMethod $methodReflection, VirtualUs
118152
);
119153
}
120154

155+
private function createEnumCaseUsage(ReflectionEnumUnitCase $enumCaseReflection, VirtualUsageData $usage): EnumCaseUsage
156+
{
157+
return new EnumCaseUsage(
158+
UsageOrigin::createVirtual($this, $usage),
159+
new EnumCaseRef(
160+
$enumCaseReflection->getDeclaringClass()->getName(),
161+
$enumCaseReflection->getName(),
162+
),
163+
);
164+
}
165+
121166
}

tests/Rule/DeadCodeRuleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,7 @@ public static function provideFiles(): Traversable
687687
yield 'provider-symfony-7.1' => [__DIR__ . '/data/providers/symfony-gte71.php', self::requiresPhp(8_00_00) && self::requiresPackage('symfony/dependency-injection', '>= 7.1')];
688688
yield 'provider-twig' => [__DIR__ . '/data/providers/twig.php', self::requiresPhp(8_00_00)];
689689
yield 'provider-phpunit' => [__DIR__ . '/data/providers/phpunit.php', self::requiresPhp(8_00_00)];
690-
yield 'provider-doctrine' => [__DIR__ . '/data/providers/doctrine.php', self::requiresPhp(8_00_00)];
690+
yield 'provider-doctrine' => [__DIR__ . '/data/providers/doctrine.php', self::requiresPhp(8_01_00)];
691691
yield 'provider-phpstan' => [__DIR__ . '/data/providers/phpstan.php'];
692692
yield 'provider-nette' => [__DIR__ . '/data/providers/nette.php'];
693693

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)