From cd8bcc9db0126bb15343f860d8451941ed298a48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1chym=20Tou=C5=A1ek?= Date: Thu, 24 Sep 2020 08:45:55 +0200 Subject: [PATCH] Check that doctrine entities are not final --- rules.neon | 6 ++ src/Rules/Doctrine/ORM/EntityNotFinalRule.php | 56 ++++++++++++++++++ .../Doctrine/ORM/EntityNotFinalRuleTest.php | 59 +++++++++++++++++++ tests/Rules/Doctrine/ORM/data/FinalEntity.php | 21 +++++++ .../Doctrine/ORM/data/FinalNonEntity.php | 7 +++ 5 files changed, 149 insertions(+) create mode 100644 src/Rules/Doctrine/ORM/EntityNotFinalRule.php create mode 100644 tests/Rules/Doctrine/ORM/EntityNotFinalRuleTest.php create mode 100644 tests/Rules/Doctrine/ORM/data/FinalEntity.php create mode 100644 tests/Rules/Doctrine/ORM/data/FinalNonEntity.php diff --git a/rules.neon b/rules.neon index 66d47315..dadc9583 100644 --- a/rules.neon +++ b/rules.neon @@ -33,3 +33,9 @@ services: reportUnknownTypes: %doctrine.reportUnknownTypes% tags: - phpstan.rules.rule + - + class: PHPStan\Rules\Doctrine\ORM\EntityNotFinalRule + +conditionalTags: + PHPStan\Rules\Doctrine\ORM\EntityNotFinalRule: + phpstan.rules.rule: %featureToggles.bleedingEdge% diff --git a/src/Rules/Doctrine/ORM/EntityNotFinalRule.php b/src/Rules/Doctrine/ORM/EntityNotFinalRule.php new file mode 100644 index 00000000..2dfec03b --- /dev/null +++ b/src/Rules/Doctrine/ORM/EntityNotFinalRule.php @@ -0,0 +1,56 @@ + + */ +class EntityNotFinalRule implements Rule +{ + + /** @var \PHPStan\Type\Doctrine\ObjectMetadataResolver */ + private $objectMetadataResolver; + + public function __construct(ObjectMetadataResolver $objectMetadataResolver) + { + $this->objectMetadataResolver = $objectMetadataResolver; + } + + public function getNodeType(): string + { + return Node\Stmt\Class_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (! $node->isFinal()) { + return []; + } + + $objectManager = $this->objectMetadataResolver->getObjectManager(); + if ($objectManager === null) { + return []; + } + + $className = (string) $node->namespacedName; + try { + if ($objectManager->getMetadataFactory()->isTransient($className)) { + return []; + } + } catch (\ReflectionException $e) { + return []; + } + + return [sprintf( + 'Entity class %s is final which can cause problems with proxies.', + $className + )]; + } + +} diff --git a/tests/Rules/Doctrine/ORM/EntityNotFinalRuleTest.php b/tests/Rules/Doctrine/ORM/EntityNotFinalRuleTest.php new file mode 100644 index 00000000..4829f69f --- /dev/null +++ b/tests/Rules/Doctrine/ORM/EntityNotFinalRuleTest.php @@ -0,0 +1,59 @@ + + */ +class EntityNotFinalRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new EntityNotFinalRule( + new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', null) + ); + } + + /** + * @dataProvider ruleProvider + * @param string $file + * @param mixed[] $expectedErrors + */ + public function testRule(string $file, array $expectedErrors): void + { + $this->analyse([$file], $expectedErrors); + } + + /** + * @return \Iterator + */ + public function ruleProvider(): Iterator + { + yield 'final entity' => [ + __DIR__ . '/data/FinalEntity.php', + [ + [ + 'Entity class PHPStan\Rules\Doctrine\ORM\FinalEntity is final which can cause problems with proxies.', + 10, + ], + ], + ]; + + yield 'final non-entity' => [ + __DIR__ . '/data/FinalNonEntity.php', + [], + ]; + + yield 'correct entity' => [ + __DIR__ . '/data/MyEntity.php', + [], + ]; + } + +} diff --git a/tests/Rules/Doctrine/ORM/data/FinalEntity.php b/tests/Rules/Doctrine/ORM/data/FinalEntity.php new file mode 100644 index 00000000..298f79bd --- /dev/null +++ b/tests/Rules/Doctrine/ORM/data/FinalEntity.php @@ -0,0 +1,21 @@ +