diff --git a/packages/better-php-doc-parser/src/PhpDocNode/Doctrine/Class_/EmbeddableTagValueNode.php b/packages/better-php-doc-parser/src/PhpDocNode/Doctrine/Class_/EmbeddableTagValueNode.php new file mode 100644 index 000000000000..da18b9769d58 --- /dev/null +++ b/packages/better-php-doc-parser/src/PhpDocNode/Doctrine/Class_/EmbeddableTagValueNode.php @@ -0,0 +1,25 @@ +resolveOriginalContentSpacingAndOrder($originalContent); + } + + public function __toString(): string + { + return $this->originalContent; + } + + public function getShortName(): string + { + return '@ORM\Embeddable'; + } +} diff --git a/packages/better-php-doc-parser/src/PhpDocNodeFactory/Doctrine/Class_/EmbeddablePhpDocNodeFactory.php b/packages/better-php-doc-parser/src/PhpDocNodeFactory/Doctrine/Class_/EmbeddablePhpDocNodeFactory.php new file mode 100644 index 000000000000..693bc13254ed --- /dev/null +++ b/packages/better-php-doc-parser/src/PhpDocNodeFactory/Doctrine/Class_/EmbeddablePhpDocNodeFactory.php @@ -0,0 +1,39 @@ +nodeAnnotationReader->readClassAnnotation($node, $this->getClass()); + if ($entity === null) { + return null; + } + + $annotationContent = $this->resolveContentFromTokenIterator($tokenIterator); + + return new EmbeddableTagValueNode($annotationContent); + } +} diff --git a/phpstan.neon b/phpstan.neon index 08e78594872a..0a7448837346 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -246,8 +246,7 @@ parameters: - '#Right side of && is always true#' - '#Parameter \#(.*?) (.*?) of class PhpParser\\Node\\Expr\\BinaryOp\\(.*?) constructor expects PhpParser\\Node\\Expr, PhpParser\\Node given#' - - '#Method Rector\\BetterPhpDocParser\\PhpDocNode\\JMS\\JMSInjectParamsTagValueNode\:\:__toString\(\) should return string but returns string\|null#' - - '#Method Rector\\BetterPhpDocParser\\PhpDocNode\\JMS\\JMSServiceValueNode\:\:__toString\(\) should return string but returns string\|null#' + - '#Method Rector\\(.*?)\:\:__toString\(\) should return string but returns string\|null#' - '#Strict comparison using \=\=\= between mixed and (.*?) will always evaluate to false#' - '#Parameter \#1 \$c of method Rector\\Php70\\EregToPcreTransformer\:\:_ere2pcre_escape\(\) expects string, mixed given#' diff --git a/rules/doctrine/src/PhpDocParser/DoctrineDocBlockResolver.php b/rules/doctrine/src/PhpDocParser/DoctrineDocBlockResolver.php index 46dc9c3ff464..8933a0ada2da 100644 --- a/rules/doctrine/src/PhpDocParser/DoctrineDocBlockResolver.php +++ b/rules/doctrine/src/PhpDocParser/DoctrineDocBlockResolver.php @@ -10,6 +10,7 @@ use PhpParser\Node\Stmt\ClassLike; use PhpParser\Node\Stmt\Property; use Rector\BetterPhpDocParser\Contract\Doctrine\DoctrineRelationTagValueNodeInterface; +use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Class_\EmbeddableTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Class_\EntityTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\ColumnTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\IdTagValueNode; @@ -37,28 +38,11 @@ public function __construct(ParsedNodeCollector $parsedNodeCollector) public function isDoctrineEntityClass($class): bool { if ($class instanceof Class_) { - $phpDocInfo = $class->getAttribute(AttributeKey::PHP_DOC_INFO); - if ($phpDocInfo === null) { - return false; - } - - return $phpDocInfo->hasByType(EntityTagValueNode::class); + return $this->isDoctrineEntityClassNode($class); } if (is_string($class)) { - if (ClassExistenceStaticHelper::doesClassLikeExist($class)) { - $classNode = $this->parsedNodeCollector->findClass($class); - if ($classNode !== null) { - return $this->isDoctrineEntityClass($classNode); - } - - $reflectionClass = new ReflectionClass($class); - - // dummy check of 3rd party code without running it - return Strings::contains((string) $reflectionClass->getDocComment(), '@ORM\Entity'); - } - - return false; + return $this->isStringClassEntity($class); } throw new ShouldNotHappenException(); @@ -137,4 +121,41 @@ public function isInDoctrineEntityClass(Node $node): bool return $this->isDoctrineEntityClass($classNode); } + + private function isDoctrineEntityClassNode(Class_ $class): bool + { + $phpDocInfo = $class->getAttribute(AttributeKey::PHP_DOC_INFO); + if ($phpDocInfo === null) { + return false; + } + + if ($phpDocInfo->hasByType(EntityTagValueNode::class)) { + return true; + } + + return $phpDocInfo->hasByType(EmbeddableTagValueNode::class); + } + + private function isStringClassEntity(string $class): bool + { + if (! ClassExistenceStaticHelper::doesClassLikeExist($class)) { + return false; + } + + $classNode = $this->parsedNodeCollector->findClass($class); + if ($classNode !== null) { + return $this->isDoctrineEntityClass($classNode); + } + + $reflectionClass = new ReflectionClass($class); + + // dummy check of 3rd party code without running it + $docCommentContent = (string) $reflectionClass->getDocComment(); + + if (Strings::contains($docCommentContent, '@ORM\Entity')) { + return true; + } + + return Strings::contains($docCommentContent, '@ORM\Embeddable'); + } } diff --git a/rules/solid/tests/Rector/Class_/FinalizeClassesWithoutChildrenRector/Fixture/skip_embedable.php.inc b/rules/solid/tests/Rector/Class_/FinalizeClassesWithoutChildrenRector/Fixture/skip_embedable.php.inc new file mode 100644 index 000000000000..49718d589b20 --- /dev/null +++ b/rules/solid/tests/Rector/Class_/FinalizeClassesWithoutChildrenRector/Fixture/skip_embedable.php.inc @@ -0,0 +1,12 @@ +