From 42cbfd8905bcb9cc287279c5c11a3e08711a3dcb Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 26 Aug 2019 11:37:02 +0200 Subject: [PATCH 1/3] [BetterPhpDocParser] Add support for parsing Doctrine annotations --- composer.json | 4 + ecs.yaml | 2 + .../Ast/AttributeAwareNodeFactory.php | 7 +- .../Exception/ShouldNotHappenException.php | 9 -- .../src/PhpDocInfo/PhpDocInfoFactory.php | 14 +- .../src/PhpDocParser/BetterPhpDocParser.php | 31 ++++- .../src/Printer/OriginalSpacingRestorer.php | 15 ++- .../src/Printer/PhpDocInfoPrinter.php | 10 +- .../Doctrine/DoctrineEntityManipulator.php | 21 ++- ...trineEntityMethodAndPropertyRectorTest.php | 18 +-- .../DoctrinePhpDocParser/config/config.yaml | 8 ++ .../AnnotationReaderFactory.php | 16 +++ .../AnnotationReader/NodeAnnotationReader.php | 77 +++++++++++ .../src/Array_/ArrayItemStaticHelper.php | 48 +++++++ .../src/Ast/PhpDoc/ColumnTagValueNode.php | 125 ++++++++++++++++++ .../src/Ast/PhpDoc/EntityTagValueNode.php | 56 ++++++++ .../Ast/PhpDoc/DoctrineTagNodeInterface.php | 7 + .../src/PhpDocParser/OrmTagParser.php | 119 +++++++++++++++++ .../OrmTagParser/AbstractOrmTagParserTest.php | 60 +++++++++ .../Class_/Fixture/SomeEntity.php | 15 +++ .../Class_/Fixture/expected_some_entity.txt | 5 + .../Class_/OrmTagParserClassTest.php | 28 ++++ .../Class_/Source/ExistingRepositoryClass.php | 8 ++ .../Property_/Fixture/FromOfficialDocs.php | 13 ++ .../Property_/Fixture/PropertyWithName.php | 14 ++ .../Property_/Fixture/SomeProperty.php | 13 ++ .../Fixture/expected_from_official_docs.txt | 3 + .../Fixture/expected_property_with_name.txt | 4 + .../Fixture/expected_some_property.txt | 3 + .../Property_/OrmTagParserPropertyTest.php | 30 +++++ .../src/FileSystemFileProcessor.php | 8 -- .../src/Parser/FileInfoParser.php | 37 ++++++ .../src/Rector/AbstractFileSystemRector.php | 1 + ...ultAnalyzedSymfonyApplicationContainer.php | 5 +- .../DoctrineColumnPropertyTypeInferer.php | 3 +- .../DoctrineRelationPropertyTypeInferer.php | 11 +- phpstan.neon | 2 + src/Configuration/CurrentNodeProvider.php | 23 ++++ .../Exception/NotImplementedYetException.php | 2 +- ...epositoryFromParentToConstructorRector.php | 10 +- ...ositoryCallsByRepositoryPropertyRector.php | 3 +- 41 files changed, 830 insertions(+), 58 deletions(-) delete mode 100644 packages/BetterPhpDocParser/src/Exception/ShouldNotHappenException.php create mode 100644 packages/DoctrinePhpDocParser/config/config.yaml create mode 100644 packages/DoctrinePhpDocParser/src/AnnotationReader/AnnotationReaderFactory.php create mode 100644 packages/DoctrinePhpDocParser/src/AnnotationReader/NodeAnnotationReader.php create mode 100644 packages/DoctrinePhpDocParser/src/Array_/ArrayItemStaticHelper.php create mode 100644 packages/DoctrinePhpDocParser/src/Ast/PhpDoc/ColumnTagValueNode.php create mode 100644 packages/DoctrinePhpDocParser/src/Ast/PhpDoc/EntityTagValueNode.php create mode 100644 packages/DoctrinePhpDocParser/src/Contract/Ast/PhpDoc/DoctrineTagNodeInterface.php create mode 100644 packages/DoctrinePhpDocParser/src/PhpDocParser/OrmTagParser.php create mode 100644 packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/AbstractOrmTagParserTest.php create mode 100644 packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/Class_/Fixture/SomeEntity.php create mode 100644 packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/Class_/Fixture/expected_some_entity.txt create mode 100644 packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/Class_/OrmTagParserClassTest.php create mode 100644 packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/Class_/Source/ExistingRepositoryClass.php create mode 100644 packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/Property_/Fixture/FromOfficialDocs.php create mode 100644 packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/Property_/Fixture/PropertyWithName.php create mode 100644 packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/Property_/Fixture/SomeProperty.php create mode 100644 packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/Property_/Fixture/expected_from_official_docs.txt create mode 100644 packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/Property_/Fixture/expected_property_with_name.txt create mode 100644 packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/Property_/Fixture/expected_some_property.txt create mode 100644 packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/Property_/OrmTagParserPropertyTest.php create mode 100644 packages/FileSystemRector/src/Parser/FileInfoParser.php create mode 100644 src/Configuration/CurrentNodeProvider.php rename {packages/BetterPhpDocParser/src => src}/Exception/NotImplementedYetException.php (70%) diff --git a/composer.json b/composer.json index 865465cf183e..e6251074a4dc 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,9 @@ "require": { "php": "^7.1", "composer/xdebug-handler": "^1.3", + "doctrine/annotations": "^1.7", "doctrine/inflector": "^1.3", + "doctrine/orm": "^2.6", "jean85/pretty-package-versions": "^1.2", "nette/robot-loader": "^3.1", "nette/utils": "^2.5|^3.0", @@ -52,6 +54,7 @@ "Rector\\ConsoleDiffer\\": "packages/ConsoleDiffer/src", "Rector\\DeadCode\\": "packages/DeadCode/src", "Rector\\Doctrine\\": "packages/Doctrine/src", + "Rector\\DoctrinePhpDocParser\\": "packages/DoctrinePhpDocParser/src", "Rector\\DomainDrivenDesign\\": "packages/DomainDrivenDesign/src", "Rector\\ElasticSearchDSL\\": "packages/ElasticSearchDSL/src", "Rector\\FileSystemRector\\": "packages/FileSystemRector/src", @@ -97,6 +100,7 @@ "Rector\\CodingStyle\\Tests\\": "packages/CodingStyle/tests", "Rector\\DeadCode\\Tests\\": "packages/DeadCode/tests", "Rector\\Doctrine\\Tests\\": "packages/Doctrine/tests", + "Rector\\DoctrinePhpDocParser\\Tests\\": "packages/DoctrinePhpDocParser/tests", "Rector\\DomainDrivenDesign\\Tests\\": "packages/DomainDrivenDesign/tests", "Rector\\ElasticSearchDSL\\Tests\\": "packages/ElasticSearchDSL/tests", "Rector\\Guzzle\\Tests\\": "packages/Guzzle/tests", diff --git a/ecs.yaml b/ecs.yaml index ecf746c6c09c..379fcc76f30e 100644 --- a/ecs.yaml +++ b/ecs.yaml @@ -96,6 +96,8 @@ parameters: - 'src/Util/*.php' - 'packages/BetterPhpDocParser/src/Annotation/AnnotationNaming.php' - 'src/Testing/PHPUnit/PHPUnitEnvironment.php' + # honesty first + - 'src/*StaticHelper.php' Symplify\CodingStandard\Fixer\Naming\PropertyNameMatchingTypeFixer: - 'packages/NodeTypeResolver/src/PHPStan/Scope/NodeScopeResolver.php' diff --git a/packages/BetterPhpDocParser/src/Attributes/Ast/AttributeAwareNodeFactory.php b/packages/BetterPhpDocParser/src/Attributes/Ast/AttributeAwareNodeFactory.php index d7ccdd23b9cc..195f36b7f635 100644 --- a/packages/BetterPhpDocParser/src/Attributes/Ast/AttributeAwareNodeFactory.php +++ b/packages/BetterPhpDocParser/src/Attributes/Ast/AttributeAwareNodeFactory.php @@ -48,8 +48,8 @@ use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\Type\AttributeAwareThisTypeNode; use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\Type\AttributeAwareUnionTypeNode; use Rector\BetterPhpDocParser\Attributes\Contract\Ast\AttributeAwareNodeInterface; -use Rector\BetterPhpDocParser\Exception\NotImplementedYetException; -use Rector\BetterPhpDocParser\Exception\ShouldNotHappenException; +use Rector\Exception\NotImplementedYetException; +use Rector\Exception\ShouldNotHappenException; final class AttributeAwareNodeFactory { @@ -125,7 +125,8 @@ private function createFromPhpDocValueNode(PhpDocTagValueNode $phpDocTagValueNod $typeNode, $phpDocTagValueNode->isVariadic, $phpDocTagValueNode->parameterName, - $phpDocTagValueNode->description + $phpDocTagValueNode->description, + false // @todo maybe solve better ); } diff --git a/packages/BetterPhpDocParser/src/Exception/ShouldNotHappenException.php b/packages/BetterPhpDocParser/src/Exception/ShouldNotHappenException.php deleted file mode 100644 index 34b7066609c9..000000000000 --- a/packages/BetterPhpDocParser/src/Exception/ShouldNotHappenException.php +++ /dev/null @@ -1,9 +0,0 @@ -phpDocParser = $phpDocParser; $this->lexer = $lexer; $this->phpDocNodeDecoratorInterfaces = $phpDocNodeDecoratorInterfacenodeDecorators; + $this->currentNodeProvider = $currentNodeProvider; } public function createFromNode(Node $node): PhpDocInfo { + /** needed for @see OrmTagParser */ + $this->currentNodeProvider->setNode($node); + $content = $node->getDocComment()->getText(); $tokens = $this->lexer->tokenize($content); diff --git a/packages/BetterPhpDocParser/src/PhpDocParser/BetterPhpDocParser.php b/packages/BetterPhpDocParser/src/PhpDocParser/BetterPhpDocParser.php index eb412f1a13db..87953c63ba44 100644 --- a/packages/BetterPhpDocParser/src/PhpDocParser/BetterPhpDocParser.php +++ b/packages/BetterPhpDocParser/src/PhpDocParser/BetterPhpDocParser.php @@ -7,6 +7,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode; use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\Parser\ConstExprParser; @@ -20,6 +21,7 @@ use Rector\BetterPhpDocParser\Attributes\Attribute\Attribute; use Rector\BetterPhpDocParser\Data\StartEndInfo; use Rector\BetterPhpDocParser\Printer\MultilineSpaceFormatPreserver; +use Rector\DoctrinePhpDocParser\PhpDocParser\OrmTagParser; use Symplify\PackageBuilder\Reflection\PrivatesAccessor; use Symplify\PackageBuilder\Reflection\PrivatesCaller; @@ -50,11 +52,17 @@ final class BetterPhpDocParser extends PhpDocParser */ private $multilineSpaceFormatPreserver; + /** + * @var OrmTagParser + */ + private $ormTagParser; + public function __construct( TypeParser $typeParser, ConstExprParser $constExprParser, AttributeAwareNodeFactory $attributeAwareNodeFactory, - MultilineSpaceFormatPreserver $multilineSpaceFormatPreserver + MultilineSpaceFormatPreserver $multilineSpaceFormatPreserver, + OrmTagParser $ormTagParser ) { parent::__construct($typeParser, $constExprParser); @@ -62,6 +70,7 @@ public function __construct( $this->privatesAccessor = new PrivatesAccessor(); $this->attributeAwareNodeFactory = $attributeAwareNodeFactory; $this->multilineSpaceFormatPreserver = $multilineSpaceFormatPreserver; + $this->ormTagParser = $ormTagParser; } /** @@ -101,8 +110,28 @@ public function parse(TokenIterator $tokenIterator): PhpDocNode return $this->attributeAwareNodeFactory->createFromNode($phpDocNode); } + public function parseTag(TokenIterator $tokenIterator): PhpDocTagNode + { + $tag = $tokenIterator->currentTokenValue(); + $tokenIterator->next(); + + if ($tag === '@ORM') { + $tag .= $tokenIterator->currentTokenValue(); + $tokenIterator->next(); + } + + $value = $this->parseTagValue($tokenIterator, $tag); + + return new PhpDocTagNode($tag, $value); + } + public function parseTagValue(TokenIterator $tokenIterator, string $tag): PhpDocTagValueNode { + // @todo do via extension :) + if (in_array($tag, ['@ORM\Entity', '@ORM\Column'], true)) { + return $this->ormTagParser->parse($tokenIterator, $tag); + } + // needed for reference support in params, see https://github.com/rectorphp/rector/issues/1734 if ($tag === '@param') { try { diff --git a/packages/BetterPhpDocParser/src/Printer/OriginalSpacingRestorer.php b/packages/BetterPhpDocParser/src/Printer/OriginalSpacingRestorer.php index 0cd28a99d78e..be061a8ab025 100644 --- a/packages/BetterPhpDocParser/src/Printer/OriginalSpacingRestorer.php +++ b/packages/BetterPhpDocParser/src/Printer/OriginalSpacingRestorer.php @@ -4,8 +4,10 @@ use Nette\Utils\Arrays; use Nette\Utils\Strings; +use PHPStan\PhpDocParser\Ast\Node; use PHPStan\PhpDocParser\Lexer\Lexer; use Rector\BetterPhpDocParser\Data\StartEndInfo; +use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\DoctrineTagNodeInterface; final class OriginalSpacingRestorer { @@ -13,11 +15,12 @@ final class OriginalSpacingRestorer * @param mixed[] $tokens */ public function restoreInOutputWithTokensStartAndEndPosition( + Node $node, string $nodeOutput, array $tokens, StartEndInfo $startEndInfo ): string { - $oldWhitespaces = $this->detectOldWhitespaces($tokens, $startEndInfo); + $oldWhitespaces = $this->detectOldWhitespaces($node, $tokens, $startEndInfo); // no original whitespaces, return if ($oldWhitespaces === []) { @@ -26,7 +29,6 @@ public function restoreInOutputWithTokensStartAndEndPosition( $newNodeOutput = ''; $i = 0; - // replace system whitespace by old ones foreach (Strings::split($nodeOutput, '#\s+#') as $nodeOutputPart) { $newNodeOutput .= ($oldWhitespaces[$i] ?? '') . $nodeOutputPart; @@ -41,11 +43,16 @@ public function restoreInOutputWithTokensStartAndEndPosition( * @param mixed[] $tokens * @return string[] */ - private function detectOldWhitespaces(array $tokens, StartEndInfo $startEndInfo): array + private function detectOldWhitespaces(Node $node, array $tokens, StartEndInfo $startEndInfo): array { $oldWhitespaces = []; - for ($i = $startEndInfo->getStart(); $i < $startEndInfo->getEnd(); ++$i) { + $start = $startEndInfo->getStart(); + if ($node instanceof DoctrineTagNodeInterface) { + --$start; + } + + for ($i = $start; $i < $startEndInfo->getEnd(); ++$i) { if ($tokens[$i][1] === Lexer::TOKEN_HORIZONTAL_WS) { $oldWhitespaces[] = $tokens[$i][0]; } diff --git a/packages/BetterPhpDocParser/src/Printer/PhpDocInfoPrinter.php b/packages/BetterPhpDocParser/src/Printer/PhpDocInfoPrinter.php index b88bcaeb2692..3202cad329ad 100644 --- a/packages/BetterPhpDocParser/src/Printer/PhpDocInfoPrinter.php +++ b/packages/BetterPhpDocParser/src/Printer/PhpDocInfoPrinter.php @@ -78,6 +78,7 @@ public function printFormatPreserving(PhpDocInfo $phpDocInfo, bool $shouldSkipEm { $this->attributeAwarePhpDocNode = $phpDocInfo->getPhpDocNode(); $this->tokens = $phpDocInfo->getTokens(); + $this->tokenCount = count($phpDocInfo->getTokens()); $this->phpDocInfo = $phpDocInfo; @@ -154,16 +155,17 @@ private function printNode( $this->currentTokenPosition = $startEndInfo->getEnd(); } - if ($attributeAwareNode instanceof PhpDocTagNode && $startEndInfo) { - return $this->printPhpDocTagNode($attributeAwareNode, $startEndInfo, $output); - } - if ($attributeAwareNode instanceof PhpDocTagNode) { + if ($startEndInfo) { + return $this->printPhpDocTagNode($attributeAwareNode, $startEndInfo, $output); + } + return $output . PHP_EOL . ' * ' . $attributeAwareNode; } if (! $attributeAwareNode instanceof PhpDocTextNode && ! $attributeAwareNode instanceof GenericTagValueNode && $startEndInfo) { return $this->originalSpacingRestorer->restoreInOutputWithTokensStartAndEndPosition( + $attributeAwareNode, (string) $attributeAwareNode, $this->tokens, $startEndInfo diff --git a/packages/DeadCode/src/Doctrine/DoctrineEntityManipulator.php b/packages/DeadCode/src/Doctrine/DoctrineEntityManipulator.php index ad4cff60f101..494410a39b52 100644 --- a/packages/DeadCode/src/Doctrine/DoctrineEntityManipulator.php +++ b/packages/DeadCode/src/Doctrine/DoctrineEntityManipulator.php @@ -2,6 +2,13 @@ namespace Rector\DeadCode\Doctrine; +use Doctrine\ORM\Mapping\Entity; +use Doctrine\ORM\Mapping\InheritanceType; +use Doctrine\ORM\Mapping\JoinColumn; +use Doctrine\ORM\Mapping\ManyToMany; +use Doctrine\ORM\Mapping\ManyToOne; +use Doctrine\ORM\Mapping\OneToMany; +use Doctrine\ORM\Mapping\OneToOne; use Nette\Utils\Strings; use PhpParser\Comment\Doc; use PhpParser\Node; @@ -29,16 +36,16 @@ final class DoctrineEntityManipulator * @var string[] */ private const RELATION_ANNOTATIONS = [ - 'Doctrine\ORM\Mapping\OneToMany', + OneToMany::class, self::MANY_TO_ONE_ANNOTATION, - 'Doctrine\ORM\Mapping\OneToOne', - 'Doctrine\ORM\Mapping\ManyToMany', + OneToOne::class, + ManyToMany::class, ]; /** * @var string */ - private const MANY_TO_ONE_ANNOTATION = 'Doctrine\ORM\Mapping\ManyToOne'; + private const MANY_TO_ONE_ANNOTATION = ManyToOne::class; /** * @var string @@ -48,7 +55,7 @@ final class DoctrineEntityManipulator /** * @var string */ - private const JOIN_COLUMN_ANNOTATION = 'Doctrine\ORM\Mapping\JoinColumn'; + private const JOIN_COLUMN_ANNOTATION = JoinColumn::class; /** * @var DocBlockManipulator @@ -140,11 +147,11 @@ public function isStandaloneDoctrineEntityClass(Class_ $class): bool } // is parent entity - if ($this->docBlockManipulator->hasTag($class, 'Doctrine\ORM\Mapping\InheritanceType')) { + if ($this->docBlockManipulator->hasTag($class, InheritanceType::class)) { return false; } - return $this->docBlockManipulator->hasTag($class, 'Doctrine\ORM\Mapping\Entity'); + return $this->docBlockManipulator->hasTag($class, Entity::class); } public function removeMappedByOrInversedByFromProperty(Property $property): void diff --git a/packages/DeadCode/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest.php b/packages/DeadCode/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest.php index 7404399718b1..1d00166deea4 100644 --- a/packages/DeadCode/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest.php +++ b/packages/DeadCode/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest.php @@ -10,15 +10,15 @@ final class RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest extends Abstra public function test(): void { $this->doTestFiles([ - __DIR__ . '/Fixture/fixture.php.inc', - __DIR__ . '/Fixture/remove_inversed_by.php.inc', - __DIR__ . '/Fixture/remove_inversed_by_non_fqn.php.inc', - // skip - __DIR__ . '/Fixture/skip_double_entity_call.php.inc', - __DIR__ . '/Fixture/skip_id_and_system.php.inc', - __DIR__ . '/Fixture/skip_trait_called_method.php.inc', - __DIR__ . '/Fixture/skip_trait_doc_typed.php.inc', - __DIR__ . '/Fixture/skip_trait_complex.php.inc', + // __DIR__ . '/Fixture/fixture.php.inc', + // __DIR__ . '/Fixture/remove_inversed_by.php.inc', + // __DIR__ . '/Fixture/remove_inversed_by_non_fqn.php.inc', + // skip + // __DIR__ . '/Fixture/skip_double_entity_call.php.inc', + // __DIR__ . '/Fixture/skip_id_and_system.php.inc', + // __DIR__ . '/Fixture/skip_trait_called_method.php.inc', + // __DIR__ . '/Fixture/skip_trait_doc_typed.php.inc', + // __DIR__ . '/Fixture/skip_trait_complex.php.inc', __DIR__ . '/Fixture/skip_abstract_parent.php.inc', ]); } diff --git a/packages/DoctrinePhpDocParser/config/config.yaml b/packages/DoctrinePhpDocParser/config/config.yaml new file mode 100644 index 000000000000..bc88adb56d2f --- /dev/null +++ b/packages/DoctrinePhpDocParser/config/config.yaml @@ -0,0 +1,8 @@ +services: + _defaults: + autowire: true + public: true + + Rector\DoctrinePhpDocParser\: + resource: '../src' + exclude: '../src/{Ast/PhpDoc/*,*StaticHelper.php}' diff --git a/packages/DoctrinePhpDocParser/src/AnnotationReader/AnnotationReaderFactory.php b/packages/DoctrinePhpDocParser/src/AnnotationReader/AnnotationReaderFactory.php new file mode 100644 index 000000000000..be428e70caea --- /dev/null +++ b/packages/DoctrinePhpDocParser/src/AnnotationReader/AnnotationReaderFactory.php @@ -0,0 +1,16 @@ +annotationReader = $annotationReader; + $this->nameResolver = $nameResolver; + } + + public function readDoctrineClassAnnotation(Class_ $class, string $annotationClassName): Annotation + { + $classReflection = $this->createClassReflectionFromNode($class); + + /** @var Annotation|null $classAnnotation */ + $classAnnotation = $this->annotationReader->getClassAnnotation($classReflection, $annotationClassName); + if ($classAnnotation === null) { + throw new ShouldNotHappenException(__METHOD__ . '() on line ' . __LINE__); + } + + return $classAnnotation; + } + + public function readDoctrinePropertyAnnotation(Property $property, string $annotationClassName): Annotation + { + $propertyReflection = $this->createPropertyReflectionFromPropertyNode($property); + + /** @var Annotation|null $propertyAnnotation */ + $propertyAnnotation = $this->annotationReader->getPropertyAnnotation($propertyReflection, $annotationClassName); + if ($propertyAnnotation === null) { + throw new ShouldNotHappenException(__METHOD__ . '() on line ' . __LINE__); + } + + return $propertyAnnotation; + } + + private function createPropertyReflectionFromPropertyNode(Property $property): ReflectionProperty + { + /** @var string $propertyName */ + $propertyName = $this->nameResolver->getName($property); + + /** @var string $className */ + $className = $property->getAttribute(AttributeKey::CLASS_NAME); + + return new ReflectionProperty($className, $propertyName); + } + + private function createClassReflectionFromNode(Class_ $class): ReflectionClass + { + /** @var string $className */ + $className = $this->nameResolver->getName($class); + + return new ReflectionClass($className); + } +} diff --git a/packages/DoctrinePhpDocParser/src/Array_/ArrayItemStaticHelper.php b/packages/DoctrinePhpDocParser/src/Array_/ArrayItemStaticHelper.php new file mode 100644 index 000000000000..e3696ea98114 --- /dev/null +++ b/packages/DoctrinePhpDocParser/src/Array_/ArrayItemStaticHelper.php @@ -0,0 +1,48 @@ + $secondItemPosition; + }); + + return $contentItems; + } +} diff --git a/packages/DoctrinePhpDocParser/src/Ast/PhpDoc/ColumnTagValueNode.php b/packages/DoctrinePhpDocParser/src/Ast/PhpDoc/ColumnTagValueNode.php new file mode 100644 index 000000000000..793ca305df92 --- /dev/null +++ b/packages/DoctrinePhpDocParser/src/Ast/PhpDoc/ColumnTagValueNode.php @@ -0,0 +1,125 @@ +name = $name; + $this->type = $type; + $this->length = $length; + $this->precision = $precision; + $this->scale = $scale; + $this->unique = $unique; + $this->nullable = $nullable; + $this->options = $options; + $this->columnDefinition = $columnDefinition; + $this->orderedVisibleItems = $orderedVisibleItems; + } + + public function __toString(): string + { + $contentItems = []; + + // required + $contentItems['type'] = sprintf('type="%s"', $this->type); + $contentItems['name'] = sprintf('name="%s"', $this->name); + $contentItems['length'] = sprintf('length=%s', $this->length); + $contentItems['precision'] = sprintf('precision=%s', $this->precision); + $contentItems['scale'] = sprintf('scale=%s', $this->scale); + $contentItems['unique'] = sprintf('unique=%s', $this->unique ? 'true' : 'false'); + $contentItems['nullable'] = sprintf('nullable=%s', $this->nullable ? 'true' : 'false'); + + if ($this->options !== []) { + $optionsContent = Json::encode($this->options); + $optionsContent = Strings::replace($optionsContent, '#,#', ', '); + $contentItems['options'] = 'options=' . $optionsContent; + } + + $contentItems['columnDefinition'] = sprintf('columnDefinition="%s"', $this->columnDefinition); + + $contentItems = ArrayItemStaticHelper::filterAndSortVisibleItems($contentItems, $this->orderedVisibleItems); + if ($contentItems === []) { + return ''; + } + + return '(' . implode(', ', $contentItems) . ')'; + } +} diff --git a/packages/DoctrinePhpDocParser/src/Ast/PhpDoc/EntityTagValueNode.php b/packages/DoctrinePhpDocParser/src/Ast/PhpDoc/EntityTagValueNode.php new file mode 100644 index 000000000000..fe35c137fd73 --- /dev/null +++ b/packages/DoctrinePhpDocParser/src/Ast/PhpDoc/EntityTagValueNode.php @@ -0,0 +1,56 @@ +repositoryClass = $repositoryClass; + $this->readOnly = $readOnly; + $this->orderedVisibleItems = $orderedVisibleItems; + } + + public function __toString(): string + { + $contentItems = []; + + $contentItems['repositoryClass'] = sprintf('repositoryClass="%s"', $this->repositoryClass); + + // default value + $contentItems['readOnly'] = sprintf('readOnly=%s', $this->readOnly ? 'true' : 'false'); + + $contentItems = ArrayItemStaticHelper::filterAndSortVisibleItems($contentItems, $this->orderedVisibleItems); + if ($contentItems === []) { + return ''; + } + + return '(' . implode(', ', $contentItems) . ')'; + } +} diff --git a/packages/DoctrinePhpDocParser/src/Contract/Ast/PhpDoc/DoctrineTagNodeInterface.php b/packages/DoctrinePhpDocParser/src/Contract/Ast/PhpDoc/DoctrineTagNodeInterface.php new file mode 100644 index 000000000000..6abc32906d48 --- /dev/null +++ b/packages/DoctrinePhpDocParser/src/Contract/Ast/PhpDoc/DoctrineTagNodeInterface.php @@ -0,0 +1,7 @@ +currentNodeProvider = $currentNodeProvider; + $this->nodeAnnotationReader = $nodeAnnotationReader; + } + + public function parse(TokenIterator $tokenIterator, string $tag): PhpDocTagValueNode + { + /** @var Class_|Property $node */ + $node = $this->currentNodeProvider->getNode(); + + // skip all tokens for this annotation, so next annotation can work with tokens after this one + $annotationContent = $tokenIterator->joinUntil( + Lexer::TOKEN_END, + Lexer::TOKEN_PHPDOC_EOL, + Lexer::TOKEN_CLOSE_PHPDOC + ); + + // Entity tags + if ($node instanceof Class_) { + if ($tag === '@ORM\Entity') { + return $this->createEntityTagValueNode($node, $annotationContent); + } + } + + // Property tags + if ($node instanceof Property) { + if ($tag === '@ORM\Column') { + return $this->createColumnTagValueNode($node, $annotationContent); + } + } + + // @todo + throw new NotImplementedException(__METHOD__); + } + + private function createEntityTagValueNode(Class_ $node, string $content): EntityTagValueNode + { + /** @var Entity $entity */ + $entity = $this->nodeAnnotationReader->readDoctrineClassAnnotation($node, Entity::class); + + $itemsOrder = $this->resolveAnnotationItemsOrder($content); + + return new EntityTagValueNode($entity->repositoryClass, $entity->readOnly, $itemsOrder); + } + + private function createColumnTagValueNode(Property $property, string $content): ColumnTagValueNode + { + /** @var Column $column */ + $column = $this->nodeAnnotationReader->readDoctrinePropertyAnnotation($property, Column::class); + + $itemsOrder = $this->resolveAnnotationItemsOrder($content); + + return new ColumnTagValueNode( + $column->name, + $column->type, + $column->length, + $column->precision, + $column->scale, + $column->unique, + $column->nullable, + $column->options, + $column->columnDefinition, + $itemsOrder + ); + } + + /** + * @return string[] + */ + private function resolveAnnotationItemsOrder(string $content): array + { + $itemsOrder = []; + $matches = Strings::matchAll($content, '#(?\w+)=#m'); + foreach ($matches as $match) { + $itemsOrder[] = $match['item']; + } + + return $itemsOrder; + } +} diff --git a/packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/AbstractOrmTagParserTest.php b/packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/AbstractOrmTagParserTest.php new file mode 100644 index 000000000000..a1c4a7031204 --- /dev/null +++ b/packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/AbstractOrmTagParserTest.php @@ -0,0 +1,60 @@ +bootKernel(RectorKernel::class); + + $this->phpDocInfoFactory = self::$container->get(PhpDocInfoFactory::class); + $this->fileInfoParser = self::$container->get(FileInfoParser::class); + + $this->betterNodeFinder = self::$container->get(BetterNodeFinder::class); + $this->phpDocInfoPrinter = self::$container->get(PhpDocInfoPrinter::class); + } + + protected function parseFileAndGetFirstNodeOfType(string $filePath, string $type): Node + { + $nodes = $this->fileInfoParser->parseFileInfoToNodesAndDecorate(new SmartFileInfo($filePath)); + + return $this->betterNodeFinder->findFirstInstanceOf($nodes, $type); + } + + protected function createPhpDocInfoFromNodeAndPrintBackToString(Node $node): string + { + $phpDocInfo = $this->phpDocInfoFactory->createFromNode($node); + + return $this->phpDocInfoPrinter->printFormatPreserving($phpDocInfo); + } +} diff --git a/packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/Class_/Fixture/SomeEntity.php b/packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/Class_/Fixture/SomeEntity.php new file mode 100644 index 000000000000..a625bc689d48 --- /dev/null +++ b/packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/Class_/Fixture/SomeEntity.php @@ -0,0 +1,15 @@ +parseFileAndGetFirstNodeOfType($filePath, Class_::class); + $printedPhpDocInfo = $this->createPhpDocInfoFromNodeAndPrintBackToString($class); + + $this->assertStringEqualsFile($expectedPrintedPhpDoc, $printedPhpDocInfo); + } + + public function provideData(): iterable + { + yield [__DIR__ . '/Fixture/SomeEntity.php', __DIR__ . '/Fixture/expected_some_entity.txt']; + } +} diff --git a/packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/Class_/Source/ExistingRepositoryClass.php b/packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/Class_/Source/ExistingRepositoryClass.php new file mode 100644 index 000000000000..0c0e6e4cb5b2 --- /dev/null +++ b/packages/DoctrinePhpDocParser/tests/PhpDocParser/OrmTagParser/Class_/Source/ExistingRepositoryClass.php @@ -0,0 +1,8 @@ +parseFileAndGetFirstNodeOfType($filePath, Property::class); + $printedPhpDocInfo = $this->createPhpDocInfoFromNodeAndPrintBackToString($property); + + $this->assertStringEqualsFile($expectedPrintedPhpDoc, $printedPhpDocInfo); + } + + public function provideData(): iterable + { + yield [__DIR__ . '/Fixture/SomeProperty.php', __DIR__ . '/Fixture/expected_some_property.txt']; + yield [__DIR__ . '/Fixture/PropertyWithName.php', __DIR__ . '/Fixture/expected_property_with_name.txt']; + yield [__DIR__ . '/Fixture/FromOfficialDocs.php', __DIR__ . '/Fixture/expected_from_official_docs.txt']; + } +} diff --git a/packages/FileSystemRector/src/FileSystemFileProcessor.php b/packages/FileSystemRector/src/FileSystemFileProcessor.php index 84f115ef66c0..3eb30ce21dd9 100644 --- a/packages/FileSystemRector/src/FileSystemFileProcessor.php +++ b/packages/FileSystemRector/src/FileSystemFileProcessor.php @@ -20,14 +20,6 @@ public function __construct(array $fileSystemRectors = []) $this->fileSystemRectors = $fileSystemRectors; } - /** - * @return FileSystemRectorInterface[] - */ - public function getFileSystemRectors(): array - { - return $this->fileSystemRectors; - } - public function processFileInfo(SmartFileInfo $smartFileInfo): void { foreach ($this->fileSystemRectors as $fileSystemRector) { diff --git a/packages/FileSystemRector/src/Parser/FileInfoParser.php b/packages/FileSystemRector/src/Parser/FileInfoParser.php new file mode 100644 index 000000000000..be9f3fccf10d --- /dev/null +++ b/packages/FileSystemRector/src/Parser/FileInfoParser.php @@ -0,0 +1,37 @@ +parser = $parser; + $this->nodeScopeAndMetadataDecorator = $nodeScopeAndMetadataDecorator; + } + + /** + * @return Node[] + */ + public function parseFileInfoToNodesAndDecorate(SmartFileInfo $fileInfo): array + { + $oldStmts = $this->parser->parseFile($fileInfo->getRealPath()); + + return $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile($oldStmts, $fileInfo->getRealPath()); + } +} diff --git a/packages/FileSystemRector/src/Rector/AbstractFileSystemRector.php b/packages/FileSystemRector/src/Rector/AbstractFileSystemRector.php index 8d16907db92c..acadd189c16b 100644 --- a/packages/FileSystemRector/src/Rector/AbstractFileSystemRector.php +++ b/packages/FileSystemRector/src/Rector/AbstractFileSystemRector.php @@ -92,6 +92,7 @@ protected function parseFileInfoToNodes(SmartFileInfo $smartFileInfo): array $oldStmts = $this->parser->parseFile($smartFileInfo->getRealPath()); $this->oldStmts = $oldStmts; // needed for format preserving + $this->oldStmts = $oldStmts; return $this->nodeScopeAndMetadataDecorator->decorateNodesFromFile( $oldStmts, $smartFileInfo->getRealPath() diff --git a/packages/Symfony/src/Bridge/DefaultAnalyzedSymfonyApplicationContainer.php b/packages/Symfony/src/Bridge/DefaultAnalyzedSymfonyApplicationContainer.php index 504f3bf7406e..2f4348647553 100644 --- a/packages/Symfony/src/Bridge/DefaultAnalyzedSymfonyApplicationContainer.php +++ b/packages/Symfony/src/Bridge/DefaultAnalyzedSymfonyApplicationContainer.php @@ -2,6 +2,7 @@ namespace Rector\Symfony\Bridge; +use Doctrine\ORM\EntityManagerInterface; use Rector\Bridge\Contract\AnalyzedApplicationContainerInterface; use Rector\Configuration\Option; use Rector\Exception\ShouldNotHappenException; @@ -39,8 +40,8 @@ final class DefaultAnalyzedSymfonyApplicationContainer implements AnalyzedApplic */ private $commonNamesToTypes = [ 'doctrine' => 'Symfony\Bridge\Doctrine\RegistryInterface', - 'doctrine.orm.entity_manager' => 'Doctrine\ORM\EntityManagerInterface', - 'doctrine.orm.default_entity_manager' => 'Doctrine\ORM\EntityManagerInterface', + 'doctrine.orm.entity_manager' => EntityManagerInterface::class, + 'doctrine.orm.default_entity_manager' => EntityManagerInterface::class, ]; public function __construct( diff --git a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineColumnPropertyTypeInferer.php b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineColumnPropertyTypeInferer.php index e26fff5774e9..19ec149d559d 100644 --- a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineColumnPropertyTypeInferer.php +++ b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineColumnPropertyTypeInferer.php @@ -3,6 +3,7 @@ namespace Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer; use DateTimeInterface; +use Doctrine\ORM\Mapping\Column; use Nette\Utils\Strings; use PhpParser\Node\Stmt\Property; use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; @@ -14,7 +15,7 @@ final class DoctrineColumnPropertyTypeInferer implements PropertyTypeInfererInte /** * @var string */ - private const COLUMN_ANNOTATION = 'Doctrine\ORM\Mapping\Column'; + private const COLUMN_ANNOTATION = Column::class; /** * @var string[] diff --git a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineRelationPropertyTypeInferer.php b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineRelationPropertyTypeInferer.php index 0034f8865d0d..e321d689f5c4 100644 --- a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineRelationPropertyTypeInferer.php +++ b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineRelationPropertyTypeInferer.php @@ -2,6 +2,11 @@ namespace Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer; +use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping\ManyToMany; +use Doctrine\ORM\Mapping\ManyToOne; +use Doctrine\ORM\Mapping\OneToMany; +use Doctrine\ORM\Mapping\OneToOne; use PhpParser\Node\Stmt\Property; use Rector\DeadCode\Doctrine\DoctrineEntityManipulator; use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator; @@ -12,18 +17,18 @@ final class DoctrineRelationPropertyTypeInferer implements PropertyTypeInfererIn /** * @var string[] */ - private const TO_MANY_ANNOTATIONS = ['Doctrine\ORM\Mapping\OneToMany', 'Doctrine\ORM\Mapping\ManyToMany']; + private const TO_MANY_ANNOTATIONS = [OneToMany::class, ManyToMany::class]; /** * Nullable by default, @see https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/annotations-reference.html#joincolumn - "JoinColumn" and nullable=true * @var string[] */ - private const TO_ONE_ANNOTATIONS = ['Doctrine\ORM\Mapping\ManyToOne', 'Doctrine\ORM\Mapping\OneToOne']; + private const TO_ONE_ANNOTATIONS = [ManyToOne::class, OneToOne::class]; /** * @var string */ - private const COLLECTION_TYPE = 'Doctrine\Common\Collections\Collection'; + private const COLLECTION_TYPE = Collection::class; /** * @var DocBlockManipulator diff --git a/phpstan.neon b/phpstan.neon index c656a9b4c6b9..998d8b78e752 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -184,6 +184,8 @@ parameters: - '#Parameter \#1 \$node of method Rector\\NodeTypeResolver\\NodeTypeResolver\:\:resolveSingleTypeToStrings\(\) expects PhpParser\\Node, PhpParser\\Node\\Expr\|null given#' - '#Parameter \#1 \$name of class ReflectionFunction constructor expects Closure\|string, callable\(\)\: mixed given#' + - '#Method Rector\\DoctrinePhpDocParser\\PhpDocParser\\OrmTagParser\:\:readDoctrineAnnotation\(\) should return Doctrine\\ORM\\Mapping\\Annotation\|null but returns object\|null#' + - '#Method Rector\\DoctrinePhpDocParser\\Tests\\PhpDocParser\\OrmTagParser\\AbstractOrmTagParserTest\:\:parseFileAndGetFirstNodeOfType\(\) should return PhpParser\\Node but returns PhpParser\\Node\|null#' # PHP 7.4 1_000 support - '#Property PhpParser\\Node\\Scalar\\DNumber\:\:\$value \(float\) does not accept string#' diff --git a/src/Configuration/CurrentNodeProvider.php b/src/Configuration/CurrentNodeProvider.php new file mode 100644 index 000000000000..99ac58b71e77 --- /dev/null +++ b/src/Configuration/CurrentNodeProvider.php @@ -0,0 +1,23 @@ +node = $node; + } + + public function getNode(): Node + { + return $this->node; + } +} diff --git a/packages/BetterPhpDocParser/src/Exception/NotImplementedYetException.php b/src/Exception/NotImplementedYetException.php similarity index 70% rename from packages/BetterPhpDocParser/src/Exception/NotImplementedYetException.php rename to src/Exception/NotImplementedYetException.php index 7a52c4517084..16b486d820a9 100644 --- a/packages/BetterPhpDocParser/src/Exception/NotImplementedYetException.php +++ b/src/Exception/NotImplementedYetException.php @@ -1,6 +1,6 @@ doctrineEntityAndRepositoryMapper = $doctrineEntityAndRepositoryMapper; $this->entityRepositoryClass = $entityRepositoryClass; @@ -84,8 +86,8 @@ public function __construct(\Doctrine\ORM\EntityManager $entityManager) CODE_SAMPLE , [ - '$entityRepositoryClass' => 'Doctrine\ORM\EntityRepository', - '$entityManagerClass' => 'Doctrine\ORM\EntityManager', + '$entityRepositoryClass' => EntityRepository::class, + '$entityManagerClass' => EntityManager::class, ] ), ]); diff --git a/src/Rector/Architecture/RepositoryAsService/ReplaceParentRepositoryCallsByRepositoryPropertyRector.php b/src/Rector/Architecture/RepositoryAsService/ReplaceParentRepositoryCallsByRepositoryPropertyRector.php index 747735b1db39..c2b4c679487a 100644 --- a/src/Rector/Architecture/RepositoryAsService/ReplaceParentRepositoryCallsByRepositoryPropertyRector.php +++ b/src/Rector/Architecture/RepositoryAsService/ReplaceParentRepositoryCallsByRepositoryPropertyRector.php @@ -2,6 +2,7 @@ namespace Rector\Rector\Architecture\RepositoryAsService; +use Doctrine\ORM\EntityRepository; use PhpParser\Node; use PhpParser\Node\Expr\MethodCall; use PhpParser\Node\Identifier; @@ -32,7 +33,7 @@ final class ReplaceParentRepositoryCallsByRepositoryPropertyRector extends Abstr 'matching', ]; - public function __construct(string $entityRepositoryClass = 'Doctrine\ORM\EntityRepository') + public function __construct(string $entityRepositoryClass = EntityRepository::class) { $this->entityRepositoryClass = $entityRepositoryClass; } From d4c3b002bbd1e2885ecb37d0e0d35caacf5e47a9 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 26 Aug 2019 21:31:34 +0200 Subject: [PATCH 2/3] simplify RemoveRepositoryFromEntityAnnotationRector --- ...veRepositoryFromEntityAnnotationRector.php | 21 ++---------- .../src/PhpDocInfo/PhpDocInfo.php | 14 ++++++++ ...trineEntityMethodAndPropertyRectorTest.php | 18 +++++----- .../src/Ast/PhpDoc/EntityTagValueNode.php | 10 ++++++ .../PropertyTypeDeclarationRectorTest.php | 34 +++++++++---------- 5 files changed, 53 insertions(+), 44 deletions(-) diff --git a/packages/Architecture/src/Rector/Class_/RemoveRepositoryFromEntityAnnotationRector.php b/packages/Architecture/src/Rector/Class_/RemoveRepositoryFromEntityAnnotationRector.php index 67903a0ab1dc..cedd54e5138c 100644 --- a/packages/Architecture/src/Rector/Class_/RemoveRepositoryFromEntityAnnotationRector.php +++ b/packages/Architecture/src/Rector/Class_/RemoveRepositoryFromEntityAnnotationRector.php @@ -2,10 +2,8 @@ namespace Rector\Architecture\Rector\Class_; -use Nette\Utils\Strings; use PhpParser\Node; use PhpParser\Node\Stmt\Class_; -use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use Rector\Architecture\Tests\Rector\Class_\RemoveRepositoryFromEntityAnnotationRector\RemoveRepositoryFromEntityAnnotationRectorTest; use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator; use Rector\Rector\AbstractRector; @@ -17,11 +15,6 @@ */ final class RemoveRepositoryFromEntityAnnotationRector extends AbstractRector { - /** - * @var string - */ - private const DOCTRINE_ORM_MAPPING_ENTITY = 'Doctrine\ORM\Mapping\Entity'; - /** * @var DocBlockManipulator */ @@ -79,21 +72,13 @@ public function refactor(Node $node): ?Node } $phpDocInfo = $this->docBlockManipulator->createPhpDocInfoFromNode($node); - if (! $phpDocInfo->hasTag(self::DOCTRINE_ORM_MAPPING_ENTITY)) { - return null; - } - - $entityTags = $phpDocInfo->getTagsByName(self::DOCTRINE_ORM_MAPPING_ENTITY); - if ($entityTags === []) { - return null; - } - $entityTag = $entityTags[0]; - if (! $entityTag->value instanceof GenericTagValueNode) { + $doctrineEntityTag = $phpDocInfo->getDoctrineEntityTag(); + if ($doctrineEntityTag === null) { return null; } - $entityTag->value->value = Strings::replace($entityTag->value->value, '#\(repositoryClass="(.*?)"\)#'); + $doctrineEntityTag->removeRepositoryClass(); // save the entity tag $this->docBlockManipulator->updateNodeWithPhpDocInfo($node, $phpDocInfo); diff --git a/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfo.php b/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfo.php index 974912d769f9..8cb32485362e 100644 --- a/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfo.php +++ b/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfo.php @@ -13,6 +13,7 @@ use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareVarTagValueNode; use Rector\BetterPhpDocParser\Attributes\Attribute\Attribute; use Rector\BetterPhpDocParser\Attributes\Contract\Ast\AttributeAwareNodeInterface; +use Rector\DoctrinePhpDocParser\Ast\PhpDoc\EntityTagValueNode; final class PhpDocInfo { @@ -167,6 +168,19 @@ public function getVarTypes(): array return $this->getResolvedTypesAttribute($varTagValue); } + public function getDoctrineEntityTag(): ?EntityTagValueNode + { + foreach ($this->getPhpDocNode()->children as $phpDocChildNode) { + if ($phpDocChildNode instanceof PhpDocTagNode) { + if ($phpDocChildNode->value instanceof EntityTagValueNode) { + return $phpDocChildNode->value; + } + } + } + + return null; + } + /** * @return string[] */ diff --git a/packages/DeadCode/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest.php b/packages/DeadCode/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest.php index 1d00166deea4..2599fe0e044f 100644 --- a/packages/DeadCode/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest.php +++ b/packages/DeadCode/tests/Rector/Class_/RemoveUnusedDoctrineEntityMethodAndPropertyRector/RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest.php @@ -10,15 +10,15 @@ final class RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest extends Abstra public function test(): void { $this->doTestFiles([ - // __DIR__ . '/Fixture/fixture.php.inc', - // __DIR__ . '/Fixture/remove_inversed_by.php.inc', - // __DIR__ . '/Fixture/remove_inversed_by_non_fqn.php.inc', - // skip - // __DIR__ . '/Fixture/skip_double_entity_call.php.inc', - // __DIR__ . '/Fixture/skip_id_and_system.php.inc', - // __DIR__ . '/Fixture/skip_trait_called_method.php.inc', - // __DIR__ . '/Fixture/skip_trait_doc_typed.php.inc', - // __DIR__ . '/Fixture/skip_trait_complex.php.inc', + __DIR__ . '/Fixture/fixture.php.inc', + __DIR__ . '/Fixture/remove_inversed_by.php.inc', + __DIR__ . '/Fixture/remove_inversed_by_non_fqn.php.inc', + // skip + __DIR__ . '/Fixture/skip_double_entity_call.php.inc', + __DIR__ . '/Fixture/skip_id_and_system.php.inc', + __DIR__ . '/Fixture/skip_trait_called_method.php.inc', + __DIR__ . '/Fixture/skip_trait_doc_typed.php.inc', + __DIR__ . '/Fixture/skip_trait_complex.php.inc', __DIR__ . '/Fixture/skip_abstract_parent.php.inc', ]); } diff --git a/packages/DoctrinePhpDocParser/src/Ast/PhpDoc/EntityTagValueNode.php b/packages/DoctrinePhpDocParser/src/Ast/PhpDoc/EntityTagValueNode.php index fe35c137fd73..a95f0f38064c 100644 --- a/packages/DoctrinePhpDocParser/src/Ast/PhpDoc/EntityTagValueNode.php +++ b/packages/DoctrinePhpDocParser/src/Ast/PhpDoc/EntityTagValueNode.php @@ -53,4 +53,14 @@ public function __toString(): string return '(' . implode(', ', $contentItems) . ')'; } + + public function removeRepositoryClass(): void + { + $itemPosition = array_search('repositoryClass', $this->orderedVisibleItems, true); + if ($itemPosition !== null) { + unset($this->orderedVisibleItems[$itemPosition]); + } + + $this->repositoryClass = null; + } } diff --git a/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/PropertyTypeDeclarationRectorTest.php b/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/PropertyTypeDeclarationRectorTest.php index 0106efc25640..c26f74f1b821 100644 --- a/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/PropertyTypeDeclarationRectorTest.php +++ b/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/PropertyTypeDeclarationRectorTest.php @@ -10,24 +10,24 @@ final class PropertyTypeDeclarationRectorTest extends AbstractRectorTestCase public function test(): void { $this->doTestFiles([ - __DIR__ . '/Fixture/constructor_param.php.inc', - __DIR__ . '/Fixture/constructor_param_with_nullable.php.inc', - __DIR__ . '/Fixture/constructor_param_with_aliased_param.php.inc', - __DIR__ . '/Fixture/constructor_array_param_with_nullable.php.inc', - __DIR__ . '/Fixture/constructor_assign.php.inc', - __DIR__ . '/Fixture/phpunit_setup.php.inc', - __DIR__ . '/Fixture/default_value.php.inc', +// __DIR__ . '/Fixture/constructor_param.php.inc', +// __DIR__ . '/Fixture/constructor_param_with_nullable.php.inc', +// __DIR__ . '/Fixture/constructor_param_with_aliased_param.php.inc', +// __DIR__ . '/Fixture/constructor_array_param_with_nullable.php.inc', +// __DIR__ . '/Fixture/constructor_assign.php.inc', +// __DIR__ . '/Fixture/phpunit_setup.php.inc', +// __DIR__ . '/Fixture/default_value.php.inc', __DIR__ . '/Fixture/doctrine_column.php.inc', - __DIR__ . '/Fixture/doctrine_relation_to_many.php.inc', - __DIR__ . '/Fixture/doctrine_relation_to_one.php.inc', - __DIR__ . '/Fixture/doctrine_relation_target_entity_same_namespace.php.inc', - // get and set - __DIR__ . '/Fixture/complex.php.inc', - __DIR__ . '/Fixture/single_nullable_return.php.inc', - __DIR__ . '/Fixture/getter_type.php.inc', - __DIR__ . '/Fixture/setter_type.php.inc', - // skip - __DIR__ . '/Fixture/skip_multi_vars.php.inc', +// __DIR__ . '/Fixture/doctrine_relation_to_many.php.inc', +// __DIR__ . '/Fixture/doctrine_relation_to_one.php.inc', +// __DIR__ . '/Fixture/doctrine_relation_target_entity_same_namespace.php.inc', +// // get and set +// __DIR__ . '/Fixture/complex.php.inc', +// __DIR__ . '/Fixture/single_nullable_return.php.inc', +// __DIR__ . '/Fixture/getter_type.php.inc', +// __DIR__ . '/Fixture/setter_type.php.inc', +// // skip +// __DIR__ . '/Fixture/skip_multi_vars.php.inc', ]); } From faaf605e199aeb1ebbd73b6aaedc5f87a3ddae95 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Mon, 26 Aug 2019 21:52:06 +0200 Subject: [PATCH 3/3] update DoctrineColumnPropertyTypeInferer to work with doctrine annotation nodes --- .../src/PhpDocInfo/PhpDocInfo.php | 16 ++++++++- .../src/Ast/PhpDoc/ColumnTagValueNode.php | 13 +++++++ ...ultAnalyzedSymfonyApplicationContainer.php | 4 +++ .../DoctrineColumnPropertyTypeInferer.php | 31 ++++------------- .../PropertyTypeDeclarationRectorTest.php | 34 +++++++++---------- phpstan.neon | 1 + src/Console/Command/AbstractCommand.php | 4 +++ 7 files changed, 60 insertions(+), 43 deletions(-) diff --git a/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfo.php b/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfo.php index 8cb32485362e..5a85ede5b49a 100644 --- a/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfo.php +++ b/packages/BetterPhpDocParser/src/PhpDocInfo/PhpDocInfo.php @@ -13,6 +13,7 @@ use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwareVarTagValueNode; use Rector\BetterPhpDocParser\Attributes\Attribute\Attribute; use Rector\BetterPhpDocParser\Attributes\Contract\Ast\AttributeAwareNodeInterface; +use Rector\DoctrinePhpDocParser\Ast\PhpDoc\ColumnTagValueNode; use Rector\DoctrinePhpDocParser\Ast\PhpDoc\EntityTagValueNode; final class PhpDocInfo @@ -170,7 +171,7 @@ public function getVarTypes(): array public function getDoctrineEntityTag(): ?EntityTagValueNode { - foreach ($this->getPhpDocNode()->children as $phpDocChildNode) { + foreach ($this->phpDocNode->children as $phpDocChildNode) { if ($phpDocChildNode instanceof PhpDocTagNode) { if ($phpDocChildNode->value instanceof EntityTagValueNode) { return $phpDocChildNode->value; @@ -181,6 +182,19 @@ public function getDoctrineEntityTag(): ?EntityTagValueNode return null; } + public function getDoctrineColumnTagValueNode(): ?ColumnTagValueNode + { + foreach ($this->phpDocNode->children as $phpDocChildNode) { + if ($phpDocChildNode instanceof PhpDocTagNode) { + if ($phpDocChildNode->value instanceof ColumnTagValueNode) { + return $phpDocChildNode->value; + } + } + } + + return null; + } + /** * @return string[] */ diff --git a/packages/DoctrinePhpDocParser/src/Ast/PhpDoc/ColumnTagValueNode.php b/packages/DoctrinePhpDocParser/src/Ast/PhpDoc/ColumnTagValueNode.php index 793ca305df92..4330ae2fbe34 100644 --- a/packages/DoctrinePhpDocParser/src/Ast/PhpDoc/ColumnTagValueNode.php +++ b/packages/DoctrinePhpDocParser/src/Ast/PhpDoc/ColumnTagValueNode.php @@ -122,4 +122,17 @@ public function __toString(): string return '(' . implode(', ', $contentItems) . ')'; } + + /** + * @return mixed + */ + public function getType() + { + return $this->type; + } + + public function isNullable(): bool + { + return $this->nullable; + } } diff --git a/packages/Symfony/src/Bridge/DefaultAnalyzedSymfonyApplicationContainer.php b/packages/Symfony/src/Bridge/DefaultAnalyzedSymfonyApplicationContainer.php index 2f4348647553..68b2fd64eef7 100644 --- a/packages/Symfony/src/Bridge/DefaultAnalyzedSymfonyApplicationContainer.php +++ b/packages/Symfony/src/Bridge/DefaultAnalyzedSymfonyApplicationContainer.php @@ -76,6 +76,10 @@ public function getTypeForName(string $name): ?string ), $throwable->getCode(), $throwable); } + if ($service === null) { + return null; + } + $serviceClass = get_class($service); if ($container->has($serviceClass)) { return $serviceClass; diff --git a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineColumnPropertyTypeInferer.php b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineColumnPropertyTypeInferer.php index 19ec149d559d..b410bb8a79c1 100644 --- a/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineColumnPropertyTypeInferer.php +++ b/packages/TypeDeclaration/src/TypeInferer/PropertyTypeInferer/DoctrineColumnPropertyTypeInferer.php @@ -3,20 +3,12 @@ namespace Rector\TypeDeclaration\TypeInferer\PropertyTypeInferer; use DateTimeInterface; -use Doctrine\ORM\Mapping\Column; -use Nette\Utils\Strings; use PhpParser\Node\Stmt\Property; -use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode; use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator; use Rector\TypeDeclaration\Contract\TypeInferer\PropertyTypeInfererInterface; final class DoctrineColumnPropertyTypeInferer implements PropertyTypeInfererInterface { - /** - * @var string - */ - private const COLUMN_ANNOTATION = Column::class; - /** * @var string[] * @see \Doctrine\DBAL\Platforms\MySqlPlatform::initializeDoctrineTypeMappings() @@ -71,23 +63,17 @@ public function __construct(DocBlockManipulator $docBlockManipulator) */ public function inferProperty(Property $property): array { - if (! $this->docBlockManipulator->hasTag($property, self::COLUMN_ANNOTATION)) { - return []; - } - - $columnTag = $this->docBlockManipulator->getTagByName($property, self::COLUMN_ANNOTATION); - if (! $columnTag->value instanceof GenericTagValueNode) { + if ($property->getDocComment() === null) { return []; } - $match = Strings::match($columnTag->value->value, '#type=\"(?.*?)\"#'); - if (! isset($match['type'])) { + $phpDocInfo = $this->docBlockManipulator->createPhpDocInfoFromNode($property); + $doctrineColumnTagValueNode = $phpDocInfo->getDoctrineColumnTagValueNode(); + if ($doctrineColumnTagValueNode === null) { return []; } - $doctrineType = $match['type']; - $scalarType = $this->doctrineTypeToScalarType[$doctrineType] ?? null; - + $scalarType = $this->doctrineTypeToScalarType[$doctrineColumnTagValueNode->getType()] ?? null; if ($scalarType === null) { return []; } @@ -95,7 +81,7 @@ public function inferProperty(Property $property): array $types = [$scalarType]; // is nullable? - if ($this->isNullable($columnTag->value->value)) { + if ($doctrineColumnTagValueNode->isNullable()) { $types[] = 'null'; } @@ -106,9 +92,4 @@ public function getPriority(): int { return 1000; } - - private function isNullable(string $value): bool - { - return (bool) Strings::match($value, '#nullable=true#'); - } } diff --git a/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/PropertyTypeDeclarationRectorTest.php b/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/PropertyTypeDeclarationRectorTest.php index c26f74f1b821..0106efc25640 100644 --- a/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/PropertyTypeDeclarationRectorTest.php +++ b/packages/TypeDeclaration/tests/Rector/Property/PropertyTypeDeclarationRector/PropertyTypeDeclarationRectorTest.php @@ -10,24 +10,24 @@ final class PropertyTypeDeclarationRectorTest extends AbstractRectorTestCase public function test(): void { $this->doTestFiles([ -// __DIR__ . '/Fixture/constructor_param.php.inc', -// __DIR__ . '/Fixture/constructor_param_with_nullable.php.inc', -// __DIR__ . '/Fixture/constructor_param_with_aliased_param.php.inc', -// __DIR__ . '/Fixture/constructor_array_param_with_nullable.php.inc', -// __DIR__ . '/Fixture/constructor_assign.php.inc', -// __DIR__ . '/Fixture/phpunit_setup.php.inc', -// __DIR__ . '/Fixture/default_value.php.inc', + __DIR__ . '/Fixture/constructor_param.php.inc', + __DIR__ . '/Fixture/constructor_param_with_nullable.php.inc', + __DIR__ . '/Fixture/constructor_param_with_aliased_param.php.inc', + __DIR__ . '/Fixture/constructor_array_param_with_nullable.php.inc', + __DIR__ . '/Fixture/constructor_assign.php.inc', + __DIR__ . '/Fixture/phpunit_setup.php.inc', + __DIR__ . '/Fixture/default_value.php.inc', __DIR__ . '/Fixture/doctrine_column.php.inc', -// __DIR__ . '/Fixture/doctrine_relation_to_many.php.inc', -// __DIR__ . '/Fixture/doctrine_relation_to_one.php.inc', -// __DIR__ . '/Fixture/doctrine_relation_target_entity_same_namespace.php.inc', -// // get and set -// __DIR__ . '/Fixture/complex.php.inc', -// __DIR__ . '/Fixture/single_nullable_return.php.inc', -// __DIR__ . '/Fixture/getter_type.php.inc', -// __DIR__ . '/Fixture/setter_type.php.inc', -// // skip -// __DIR__ . '/Fixture/skip_multi_vars.php.inc', + __DIR__ . '/Fixture/doctrine_relation_to_many.php.inc', + __DIR__ . '/Fixture/doctrine_relation_to_one.php.inc', + __DIR__ . '/Fixture/doctrine_relation_target_entity_same_namespace.php.inc', + // get and set + __DIR__ . '/Fixture/complex.php.inc', + __DIR__ . '/Fixture/single_nullable_return.php.inc', + __DIR__ . '/Fixture/getter_type.php.inc', + __DIR__ . '/Fixture/setter_type.php.inc', + // skip + __DIR__ . '/Fixture/skip_multi_vars.php.inc', ]); } diff --git a/phpstan.neon b/phpstan.neon index 998d8b78e752..b8052902956b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -186,6 +186,7 @@ parameters: - '#Method Rector\\DoctrinePhpDocParser\\PhpDocParser\\OrmTagParser\:\:readDoctrineAnnotation\(\) should return Doctrine\\ORM\\Mapping\\Annotation\|null but returns object\|null#' - '#Method Rector\\DoctrinePhpDocParser\\Tests\\PhpDocParser\\OrmTagParser\\AbstractOrmTagParserTest\:\:parseFileAndGetFirstNodeOfType\(\) should return PhpParser\\Node but returns PhpParser\\Node\|null#' + - '#Method Rector\\Symfony\\Bridge\\DefaultAnalyzedSymfonyApplicationContainer\:\:getService\(\) should return object but returns object\|null#' # PHP 7.4 1_000 support - '#Property PhpParser\\Node\\Scalar\\DNumber\:\:\$value \(float\) does not accept string#' diff --git a/src/Console/Command/AbstractCommand.php b/src/Console/Command/AbstractCommand.php index 185d80a499f0..999bce7a2c62 100644 --- a/src/Console/Command/AbstractCommand.php +++ b/src/Console/Command/AbstractCommand.php @@ -52,6 +52,10 @@ public function run(InputInterface $input, OutputInterface $output): int protected function initialize(InputInterface $input, OutputInterface $output): void { if ($input->getOption('debug')) { + if ($this->getApplication() === null) { + return; + } + $this->getApplication()->setCatchExceptions(false); } }