diff --git a/composer.json b/composer.json index f35def090..c9d860ee0 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "phpunit/phpunit": "^13.0", "symfony/framework-bundle": "^6.2", "illuminate/container": "^11.0", - "phpecs/phpecs": "^2.2", + "symplify/easy-coding-standard": "^13.1", "tomasvotruba/class-leak": "^2.1", "rector/rector": "^2.4", "phpstan/extension-installer": "^1.4", diff --git a/rector.php b/rector.php index 13e4ac7a7..e2d96e3c1 100644 --- a/rector.php +++ b/rector.php @@ -17,6 +17,8 @@ StringClassNameToClassConstantRector::class => [ __DIR__ . '/src/Enum', __DIR__ . '/src/Testing/PHPUnitTestAnalyser.php', + __DIR__ . '/src/Rules/NoEntityOutsideEntityNamespaceRule.php', __DIR__ . '/tests/Naming/ClassToSuffixResolverTest.php', + __DIR__ . '/src/Doctrine/DoctrineEntityDocumentAnalyser.php', ], ]); diff --git a/src/Doctrine/DoctrineEntityDocumentAnalyser.php b/src/Doctrine/DoctrineEntityDocumentAnalyser.php index 8c1332bba..dfd8298ad 100644 --- a/src/Doctrine/DoctrineEntityDocumentAnalyser.php +++ b/src/Doctrine/DoctrineEntityDocumentAnalyser.php @@ -14,8 +14,20 @@ */ private const array ENTITY_DOCBLOCK_MARKERS = ['@Document', '@ORM\\Document', '@Entity', '@ORM\\Entity']; + /** + * @var string[] + */ + private const array ENTITY_ATTRIBUTES = [ + 'Doctrine\\ORM\\Mapping\\Entity', + 'Doctrine\\ODM\\MongoDB\\Mapping\\Annotations\\Document', + ]; + public static function isEntityClass(ClassReflection $classReflection): bool { + if (self::hasEntityAttribute($classReflection)) { + return true; + } + $resolvedPhpDocBlock = $classReflection->getResolvedPhpDoc(); if (! $resolvedPhpDocBlock instanceof ResolvedPhpDocBlock) { return false; @@ -23,4 +35,19 @@ public static function isEntityClass(ClassReflection $classReflection): bool return array_any(self::ENTITY_DOCBLOCK_MARKERS, fn (string $entityDocBlockMarker): bool => str_contains($resolvedPhpDocBlock->getPhpDocString(), $entityDocBlockMarker)); } + + private static function hasEntityAttribute(ClassReflection $classReflection): bool + { + $attributeReflections = $classReflection->getNativeReflection() + ->getAttributes(); + + return array_any( + $attributeReflections, + static fn ($reflectionAttribute): bool => in_array( + $reflectionAttribute->getName(), + self::ENTITY_ATTRIBUTES, + true + ) + ); + } } diff --git a/stubs/Doctrine/ORM/Mapping/Entity.php b/stubs/Doctrine/ORM/Mapping/Entity.php new file mode 100644 index 000000000..95ffeb6f3 --- /dev/null +++ b/stubs/Doctrine/ORM/Mapping/Entity.php @@ -0,0 +1,12 @@ +createMock(SomeAttributeEntity::class); + } +} diff --git a/tests/Rules/PHPUnit/NoEntityMockingRule/NoEntityMockingRuleTest.php b/tests/Rules/PHPUnit/NoEntityMockingRule/NoEntityMockingRuleTest.php index 20645f722..12d42f6ec 100644 --- a/tests/Rules/PHPUnit/NoEntityMockingRule/NoEntityMockingRuleTest.php +++ b/tests/Rules/PHPUnit/NoEntityMockingRule/NoEntityMockingRuleTest.php @@ -29,6 +29,8 @@ public static function provideData(): Iterator { yield [__DIR__ . '/Fixture/MockingEntity.php', [[NoEntityMockingRule::ERROR_MESSAGE, 12]]]; + yield [__DIR__ . '/Fixture/MockingAttributeEntity.php', [[NoEntityMockingRule::ERROR_MESSAGE, 12]]]; + yield [__DIR__ . '/Fixture/MockingDocument.php', [[NoEntityMockingRule::ERROR_MESSAGE, 12]]]; yield [__DIR__ . '/Fixture/SkipMockingOtherObject.php', []]; diff --git a/tests/Rules/PHPUnit/NoEntityMockingRule/Source/SomeAttributeEntity.php b/tests/Rules/PHPUnit/NoEntityMockingRule/Source/SomeAttributeEntity.php new file mode 100644 index 000000000..04bbe7c3d --- /dev/null +++ b/tests/Rules/PHPUnit/NoEntityMockingRule/Source/SomeAttributeEntity.php @@ -0,0 +1,10 @@ +