Skip to content

Commit 7cc794d

Browse files
committed
Check that doctrine entities are not final
1 parent 78376cb commit 7cc794d

File tree

4 files changed

+146
-0
lines changed

4 files changed

+146
-0
lines changed

rules.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,7 @@ services:
3333
reportUnknownTypes: %doctrine.reportUnknownTypes%
3434
tags:
3535
- phpstan.rules.rule
36+
37+
conditionalTags:
38+
PHPStan\Rules\Doctrine\ORM\EntityRule:
39+
phpstan.rules.rule: %featureToggles.bleedingEdge%
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Doctrine\ORM;
4+
5+
use PhpParser\Node;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Rules\Rule;
8+
use PHPStan\Type\Doctrine\ObjectMetadataResolver;
9+
use function sprintf;
10+
11+
/**
12+
* @implements Rule<Node\Stmt\Class_>
13+
*/
14+
class EntityRule implements Rule
15+
{
16+
17+
/** @var \PHPStan\Type\Doctrine\ObjectMetadataResolver */
18+
private $objectMetadataResolver;
19+
20+
public function __construct(ObjectMetadataResolver $objectMetadataResolver)
21+
{
22+
$this->objectMetadataResolver = $objectMetadataResolver;
23+
}
24+
25+
public function getNodeType(): string
26+
{
27+
return Node\Stmt\Class_::class;
28+
}
29+
30+
public function processNode(Node $node, Scope $scope): array
31+
{
32+
if (! $node->isFinal()) {
33+
return [];
34+
}
35+
36+
$objectManager = $this->objectMetadataResolver->getObjectManager();
37+
if ($objectManager === null) {
38+
return [];
39+
}
40+
41+
$className = (string) $node->namespacedName;
42+
try {
43+
if ($objectManager->getMetadataFactory()->isTransient($className)) {
44+
return [];
45+
}
46+
} catch (\ReflectionException $e) {
47+
return [];
48+
}
49+
50+
try {
51+
$metadata = $objectManager->getClassMetadata($className);
52+
} catch (\Doctrine\ORM\Mapping\MappingException $e) {
53+
return [];
54+
}
55+
56+
$classMetadataInfo = 'Doctrine\ORM\Mapping\ClassMetadataInfo';
57+
if (!$metadata instanceof $classMetadataInfo) {
58+
return [];
59+
}
60+
61+
return [sprintf(
62+
'Entity class %s is final which can cause problems with proxies.',
63+
$className
64+
)];
65+
}
66+
67+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Doctrine\ORM;
4+
5+
use Iterator;
6+
use PHPStan\Rules\Rule;
7+
use PHPStan\Testing\RuleTestCase;
8+
use PHPStan\Type\Doctrine\ObjectMetadataResolver;
9+
10+
/**
11+
* @extends RuleTestCase<EntityRule>
12+
*/
13+
class EntityRuleTest extends RuleTestCase
14+
{
15+
16+
protected function getRule(): Rule
17+
{
18+
return new EntityRule(
19+
new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', null)
20+
);
21+
}
22+
23+
/**
24+
* @dataProvider ruleProvider
25+
* @param string $file
26+
* @param mixed[] $expectedErrors
27+
*/
28+
public function testRule(string $file, array $expectedErrors): void
29+
{
30+
$this->analyse([$file], $expectedErrors);
31+
}
32+
33+
/**
34+
* @return \Iterator<mixed[]>
35+
*/
36+
public function ruleProvider(): Iterator
37+
{
38+
yield 'final entity' => [
39+
__DIR__ . '/data/FinalEntity.php',
40+
[
41+
[
42+
'Entity class PHPStan\Rules\Doctrine\ORM\FinalEntity is final which can cause problems with proxies.',
43+
10,
44+
],
45+
],
46+
];
47+
48+
yield 'correct entity' => [
49+
__DIR__ . '/data/MyEntity.php',
50+
[],
51+
];
52+
}
53+
54+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Doctrine\ORM;
4+
5+
use Doctrine\ORM\Mapping as ORM;
6+
7+
/**
8+
* @ORM\Entity()
9+
*/
10+
final class FinalEntity
11+
{
12+
/**
13+
* @ORM\Id()
14+
* @ORM\GeneratedValue()
15+
* @ORM\Column(type="integer")
16+
*
17+
* @var int
18+
*/
19+
private $id;
20+
21+
}

0 commit comments

Comments
 (0)