From bb3ade5d41617038cf8a6518d936e83fadcd43f1 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Tue, 21 Jan 2020 16:11:20 +0100 Subject: [PATCH 1/5] decopule RelationPropertyFactory --- .../NodeFactory/RelationPropertyFactory.php | 78 +++++++++++++++++++ .../CakePHPModelToDoctrineEntityRector.php | 59 +++++--------- .../has_many_and_belongs_to_many.php.inc | 32 ++++++++ 3 files changed, 130 insertions(+), 39 deletions(-) create mode 100644 packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php create mode 100644 packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPModelToDoctrineEntityRector/Fixture/has_many_and_belongs_to_many.php.inc diff --git a/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php b/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php new file mode 100644 index 000000000000..a8eea88b5705 --- /dev/null +++ b/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php @@ -0,0 +1,78 @@ +valueResolver = $valueResolver; + $this->docBlockManipulator = $docBlockManipulator; + } + + /** + * @return Property[] + */ + public function createManyToOneProperties(Property $belongToProperty): array + { + $properties = []; + + $onlyPropertyDefault = $belongToProperty->props[0]->default; + if ($onlyPropertyDefault === null) { + throw new ShouldNotHappenException(); + } + + $belongsToValue = $this->valueResolver->getValue($onlyPropertyDefault); + + foreach ($belongsToValue as $propertyName => $manyToOneConfiguration) { + $propertyName = lcfirst($propertyName); + $propertyBuilder = new PropertyBuilder($propertyName); + $propertyBuilder->makePrivate(); + + $className = $manyToOneConfiguration['className']; + + // add @ORM\ManyToOne + $manyToOneTagValueNode = new ManyToOneTagValueNode($className, null, null, null, null, $className); + $propertyNode = $propertyBuilder->getNode(); + + $spacelessPhpDocTagNode = new SpacelessPhpDocTagNode( + ManyToOneTagValueNode::SHORT_NAME, + $manyToOneTagValueNode + ); + $this->docBlockManipulator->addTag($propertyNode, $spacelessPhpDocTagNode); + + $joinColumnTagValueNode = new JoinColumnTagValueNode($manyToOneConfiguration['foreignKey'], null); + $spacelessPhpDocTagNode = new SpacelessPhpDocTagNode( + JoinColumnTagValueNode::SHORT_NAME, + $joinColumnTagValueNode + ); + + $this->docBlockManipulator->addTag($propertyNode, $spacelessPhpDocTagNode); + + $properties[] = $propertyNode; + } + + return $properties; + } +} diff --git a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineEntityRector.php b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineEntityRector.php index 472ade4f0a0c..f86cca5b78d0 100644 --- a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineEntityRector.php +++ b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineEntityRector.php @@ -4,14 +4,10 @@ namespace Rector\CakePHPToSymfony\Rector\Class_; -use PhpParser\Builder\Property; use PhpParser\Node; use PhpParser\Node\Stmt\Class_; -use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\SpacelessPhpDocTagNode; -use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\JoinColumnTagValueNode; -use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\ManyToOneTagValueNode; +use Rector\CakePHPToSymfony\NodeFactory\RelationPropertyFactory; use Rector\Doctrine\PhpDocParser\Ast\PhpDoc\PhpDocTagNodeFactory; -use Rector\Exception\ShouldNotHappenException; use Rector\PhpParser\Node\Manipulator\ClassManipulator; use Rector\PHPStan\Type\AliasedObjectType; use Rector\Rector\AbstractRector; @@ -19,6 +15,12 @@ use Rector\RectorDefinition\RectorDefinition; /** + * @see https://book.cakephp.org/2/en/models/associations-linking-models-together.html#relationship-types + * hasOne => one to one + * hasMany => one to many + * belongsTo => many to one + * hasAndBelongsToMany => many to many + * * @see https://book.cakephp.org/2/en/models/associations-linking-models-together.html# * @see https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/association-mapping.html * @@ -36,10 +38,19 @@ final class CakePHPModelToDoctrineEntityRector extends AbstractRector */ private $phpDocTagNodeFactory; - public function __construct(ClassManipulator $classManipulator, PhpDocTagNodeFactory $phpDocTagNodeFactory) - { + /** + * @var RelationPropertyFactory + */ + private $relationPropertyFactory; + + public function __construct( + ClassManipulator $classManipulator, + PhpDocTagNodeFactory $phpDocTagNodeFactory, + RelationPropertyFactory $relationPropertyFactory + ) { $this->classManipulator = $classManipulator; $this->phpDocTagNodeFactory = $phpDocTagNodeFactory; + $this->relationPropertyFactory = $relationPropertyFactory; } public function getDefinition(): RectorDefinition @@ -101,38 +112,8 @@ public function refactor(Node $node): ?Node // extract relations $belongToProperty = $this->classManipulator->getProperty($node, 'belongsTo'); if ($belongToProperty !== null) { - $onlyPropertyDefault = $belongToProperty->props[0]->default; - if ($onlyPropertyDefault === null) { - throw new ShouldNotHappenException(); - } - - $belongsToValue = $this->getValue($onlyPropertyDefault); - - foreach ($belongsToValue as $propertyName => $manyToOneConfiguration) { - $propertyName = lcfirst($propertyName); - $propertyBuilder = new Property($propertyName); - $propertyBuilder->makePrivate(); - - $className = $manyToOneConfiguration['className']; - - // add @ORM\ManyToOne - $manyToOneTagValueNode = new ManyToOneTagValueNode($className, null, null, null, null, $className); - $propertyNode = $propertyBuilder->getNode(); - $spacelessPhpDocTagNode = new SpacelessPhpDocTagNode( - ManyToOneTagValueNode::SHORT_NAME, - $manyToOneTagValueNode - ); - $this->docBlockManipulator->addTag($propertyNode, $spacelessPhpDocTagNode); - - $joinColumnTagValueNode = new JoinColumnTagValueNode($manyToOneConfiguration['foreignKey'], null); - $spacelessPhpDocTagNode = new SpacelessPhpDocTagNode( - JoinColumnTagValueNode::SHORT_NAME, - $joinColumnTagValueNode - ); - $this->docBlockManipulator->addTag($propertyNode, $spacelessPhpDocTagNode); - - $node->stmts[] = $propertyNode; - } + $manyToOneProperties = $this->relationPropertyFactory->createManyToOneProperties($belongToProperty); + $node->stmts = array_merge($manyToOneProperties, (array) $node->stmts); $this->removeNode($belongToProperty); } diff --git a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPModelToDoctrineEntityRector/Fixture/has_many_and_belongs_to_many.php.inc b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPModelToDoctrineEntityRector/Fixture/has_many_and_belongs_to_many.php.inc new file mode 100644 index 000000000000..e5c4102966b6 --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPModelToDoctrineEntityRector/Fixture/has_many_and_belongs_to_many.php.inc @@ -0,0 +1,32 @@ + [ + 'className' => 'Group', + ] + ]; +} + +?> +----- + From 146ddafea440dbb243fe91a06be7b4a1d7349894 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Tue, 21 Jan 2020 16:29:36 +0100 Subject: [PATCH 2/5] add ManyToMany support --- .../Property_/ManyToManyTagValueNode.php | 48 ++++++----- .../NodeFactory/RelationPropertyFactory.php | 81 +++++++++++++------ .../CakePHPModelToDoctrineEntityRector.php | 32 ++++++-- .../NodeAnalyzer/DocBlockManipulator.php | 11 +++ 4 files changed, 122 insertions(+), 50 deletions(-) diff --git a/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/ManyToManyTagValueNode.php b/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/ManyToManyTagValueNode.php index 52e5be4a95a4..476760f360de 100644 --- a/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/ManyToManyTagValueNode.php +++ b/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/ManyToManyTagValueNode.php @@ -37,14 +37,14 @@ final class ManyToManyTagValueNode extends AbstractDoctrineTagValueNode implemen private $cascade; /** - * @var string + * @var string|null */ private $fetch; /** - * @var bool + * @var bool|null */ - private $orphanRemoval = false; + private $orphanRemoval; /** * @var string|null @@ -52,20 +52,20 @@ final class ManyToManyTagValueNode extends AbstractDoctrineTagValueNode implemen private $indexBy; /** - * @var string + * @var string|null */ private $fqnTargetEntity; public function __construct( string $targetEntity, - ?string $mappedBy, - ?string $inversedBy, - ?array $cascade, - string $fetch, - bool $orphanRemoval, - ?string $indexBy, - string $originalContent, - string $fqnTargetEntity + ?string $mappedBy = null, + ?string $inversedBy = null, + ?array $cascade = null, + ?string $fetch = null, + ?bool $orphanRemoval = null, + ?string $indexBy = null, + ?string $originalContent = null, + ?string $fqnTargetEntity = null ) { $this->targetEntity = $targetEntity; $this->mappedBy = $mappedBy; @@ -76,7 +76,9 @@ public function __construct( $this->indexBy = $indexBy; $this->fqnTargetEntity = $fqnTargetEntity; - $this->resolveOriginalContentSpacingAndOrder($originalContent); + if ($originalContent !== null) { + $this->resolveOriginalContentSpacingAndOrder($originalContent); + } } public function __toString(): string @@ -93,23 +95,31 @@ public function __toString(): string $contentItems['inversedBy'] = sprintf('inversedBy="%s"', $this->inversedBy); } - if ($this->cascade) { + if ($this->cascade !== null) { $contentItems['cascade'] = $this->printArrayItem($this->cascade, 'cascade'); } - $contentItems['fetch'] = sprintf('fetch="%s"', $this->fetch); - $contentItems['orphanRemoval'] = sprintf('orphanRemoval=%s', $this->orphanRemoval ? 'true' : 'false'); - $contentItems['indexBy'] = sprintf('indexBy="%s"', $this->indexBy); + if ($this->fetch !== null) { + $contentItems['fetch'] = sprintf('fetch="%s"', $this->fetch); + } + + if ($this->orphanRemoval !== null) { + $contentItems['orphanRemoval'] = sprintf('orphanRemoval=%s', $this->orphanRemoval ? 'true' : 'false'); + } + + if ($this->indexBy !== null) { + $contentItems['indexBy'] = sprintf('indexBy="%s"', $this->indexBy); + } return $this->printContentItems($contentItems); } - public function getTargetEntity(): ?string + public function getTargetEntity(): string { return $this->targetEntity; } - public function getFqnTargetEntity(): string + public function getFqnTargetEntity(): ?string { return $this->fqnTargetEntity; } diff --git a/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php b/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php index a8eea88b5705..ff9be5bb0642 100644 --- a/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php +++ b/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php @@ -6,8 +6,8 @@ use PhpParser\Builder\Property as PropertyBuilder; use PhpParser\Node\Stmt\Property; -use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\SpacelessPhpDocTagNode; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\JoinColumnTagValueNode; +use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\ManyToManyTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\ManyToOneTagValueNode; use Rector\Exception\ShouldNotHappenException; use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator; @@ -36,43 +36,76 @@ public function __construct(ValueResolver $valueResolver, DocBlockManipulator $d */ public function createManyToOneProperties(Property $belongToProperty): array { + $belongsToValue = $this->getPropertyDefaultValue($belongToProperty); + $properties = []; + foreach ($belongsToValue as $propertyName => $manyToOneConfiguration) { + $property = $this->createPrivateProperty($propertyName); - $onlyPropertyDefault = $belongToProperty->props[0]->default; - if ($onlyPropertyDefault === null) { - throw new ShouldNotHappenException(); + $className = $manyToOneConfiguration['className']; + + // add @ORM\ManyToOne + $manyToOneTagValueNode = new ManyToOneTagValueNode($className, null, null, null, null, $className); + $this->docBlockManipulator->addDoctrineTagValueNode($property, $manyToOneTagValueNode); + + // add @ORM\JoinColumn + $joinColumnTagValueNode = new JoinColumnTagValueNode($manyToOneConfiguration['foreignKey'], null); + $this->docBlockManipulator->addDoctrineTagValueNode($property, $joinColumnTagValueNode); + + $properties[] = $property; } - $belongsToValue = $this->valueResolver->getValue($onlyPropertyDefault); + return $properties; + } - foreach ($belongsToValue as $propertyName => $manyToOneConfiguration) { - $propertyName = lcfirst($propertyName); - $propertyBuilder = new PropertyBuilder($propertyName); - $propertyBuilder->makePrivate(); + /** + * @return Property[] + */ + public function createManyToManyProperties(Property $hasAndBelongsToManyProperty): array + { + $hasAndBelongsToValue = $this->getPropertyDefaultValue($hasAndBelongsToManyProperty); + + foreach ($hasAndBelongsToValue as $propertyName => $manyToOneConfiguration) { + $property = $this->createPrivateProperty($propertyName); $className = $manyToOneConfiguration['className']; // add @ORM\ManyToOne - $manyToOneTagValueNode = new ManyToOneTagValueNode($className, null, null, null, null, $className); - $propertyNode = $propertyBuilder->getNode(); + $manyToOneTagValueNode = new ManyToManyTagValueNode($className); + $this->docBlockManipulator->addDoctrineTagValueNode($property, $manyToOneTagValueNode); - $spacelessPhpDocTagNode = new SpacelessPhpDocTagNode( - ManyToOneTagValueNode::SHORT_NAME, - $manyToOneTagValueNode - ); - $this->docBlockManipulator->addTag($propertyNode, $spacelessPhpDocTagNode); + $properties[] = $property; + } - $joinColumnTagValueNode = new JoinColumnTagValueNode($manyToOneConfiguration['foreignKey'], null); - $spacelessPhpDocTagNode = new SpacelessPhpDocTagNode( - JoinColumnTagValueNode::SHORT_NAME, - $joinColumnTagValueNode - ); + return $properties; + } - $this->docBlockManipulator->addTag($propertyNode, $spacelessPhpDocTagNode); + private function createPrivateProperty(string $propertyName): Property + { + $propertyName = lcfirst($propertyName); + + $propertyBuilder = new PropertyBuilder($propertyName); + $propertyBuilder->makePrivate(); - $properties[] = $propertyNode; + return $propertyBuilder->getNode(); + } + + private function getPropertyDefaultValue(Property $property): array + { + if (count((array) $property->props) !== 1) { + throw new ShouldNotHappenException(); } - return $properties; + $onlyPropertyDefault = $property->props[0]->default; + if ($onlyPropertyDefault === null) { + throw new ShouldNotHappenException(); + } + + $value = $this->valueResolver->getValue($onlyPropertyDefault); + if (! is_array($value)) { + throw new ShouldNotHappenException(); + } + + return $value; } } diff --git a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineEntityRector.php b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineEntityRector.php index f86cca5b78d0..8a97fe37070f 100644 --- a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineEntityRector.php +++ b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineEntityRector.php @@ -108,22 +108,40 @@ public function refactor(Node $node): ?Node } $node->extends = null; + $this->addEntityTagToEntityClass($node); + + $relationProperties = []; // extract relations - $belongToProperty = $this->classManipulator->getProperty($node, 'belongsTo'); - if ($belongToProperty !== null) { - $manyToOneProperties = $this->relationPropertyFactory->createManyToOneProperties($belongToProperty); - $node->stmts = array_merge($manyToOneProperties, (array) $node->stmts); + $belongsToProperty = $this->classManipulator->getProperty($node, 'belongsTo'); + if ($belongsToProperty !== null) { + $manyToOneProperties = $this->relationPropertyFactory->createManyToOneProperties($belongsToProperty); + $relationProperties = array_merge($relationProperties, $manyToOneProperties); + + $this->removeNode($belongsToProperty); + } + + $hasAndBelongsToMany = $this->classManipulator->getProperty($node, 'hasAndBelongsToMany'); + if ($hasAndBelongsToMany !== null) { + $manyToManyProperties = $this->relationPropertyFactory->createManyToManyProperties($hasAndBelongsToMany); + $relationProperties = array_merge($relationProperties, $manyToManyProperties); + + $this->removeNode($hasAndBelongsToMany); + } - $this->removeNode($belongToProperty); + if ($relationProperties !== []) { + $node->stmts = array_merge($relationProperties, (array) $node->stmts); } + return $node; + } + + private function addEntityTagToEntityClass(Class_ $node): void + { $entityTag = $this->phpDocTagNodeFactory->createEntityTag(); $this->docBlockManipulator->addTag($node, $entityTag); $objectType = new AliasedObjectType('ORM', 'Doctrine\Mapping\Annotation'); $this->addUseType($objectType, $node); - - return $node; } } diff --git a/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php b/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php index 0a13cbca70df..fbd0e7a3b603 100644 --- a/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php +++ b/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php @@ -36,9 +36,11 @@ use Rector\BetterPhpDocParser\Annotation\AnnotationNaming; use Rector\BetterPhpDocParser\Ast\PhpDocNodeTraverser; use Rector\BetterPhpDocParser\Attributes\Ast\AttributeAwareNodeFactory; +use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\SpacelessPhpDocTagNode; use Rector\BetterPhpDocParser\Contract\Doctrine\DoctrineRelationTagValueNodeInterface; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; +use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\AbstractDoctrineTagValueNode; use Rector\BetterPhpDocParser\Printer\PhpDocInfoPrinter; use Rector\Exception\ShouldNotHappenException; use Rector\NodeTypeResolver\Exception\MissingTagException; @@ -148,6 +150,15 @@ public function addTag(Node $node, PhpDocChildNode $phpDocChildNode): void } } + public function addDoctrineTagValueNode(Node $node, AbstractDoctrineTagValueNode $doctrineTagValueNode): void + { + $spacelessPhpDocTagNode = new SpacelessPhpDocTagNode( + $doctrineTagValueNode::SHORT_NAME, + $doctrineTagValueNode + ); + $this->addTag($node, $spacelessPhpDocTagNode); + } + public function removeTagFromNode(Node $node, string $name, bool $shouldSkipEmptyLinesAbove = false): void { if ($node->getDocComment() === null) { From 2f1e2043641f8a772e62e086cf0f9e3744275bf3 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Tue, 21 Jan 2020 16:36:31 +0100 Subject: [PATCH 3/5] misc --- .../src/NodeFactory/RelationPropertyFactory.php | 7 ++++--- .../RouterListToControllerAnnotationsRector.php | 8 +------- .../src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php | 9 +++------ .../GetParamToClassMethodParameterAndRouteRector.php | 8 +------- phpstan.neon | 3 +++ 5 files changed, 12 insertions(+), 23 deletions(-) diff --git a/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php b/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php index ff9be5bb0642..9cae097a40d5 100644 --- a/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php +++ b/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php @@ -46,11 +46,11 @@ public function createManyToOneProperties(Property $belongToProperty): array // add @ORM\ManyToOne $manyToOneTagValueNode = new ManyToOneTagValueNode($className, null, null, null, null, $className); - $this->docBlockManipulator->addDoctrineTagValueNode($property, $manyToOneTagValueNode); + $this->docBlockManipulator->addTagValueNodeWithShortName($property, $manyToOneTagValueNode); // add @ORM\JoinColumn $joinColumnTagValueNode = new JoinColumnTagValueNode($manyToOneConfiguration['foreignKey'], null); - $this->docBlockManipulator->addDoctrineTagValueNode($property, $joinColumnTagValueNode); + $this->docBlockManipulator->addTagValueNodeWithShortName($property, $joinColumnTagValueNode); $properties[] = $property; } @@ -65,6 +65,7 @@ public function createManyToManyProperties(Property $hasAndBelongsToManyProperty { $hasAndBelongsToValue = $this->getPropertyDefaultValue($hasAndBelongsToManyProperty); + $properties = []; foreach ($hasAndBelongsToValue as $propertyName => $manyToOneConfiguration) { $property = $this->createPrivateProperty($propertyName); @@ -72,7 +73,7 @@ public function createManyToManyProperties(Property $hasAndBelongsToManyProperty // add @ORM\ManyToOne $manyToOneTagValueNode = new ManyToManyTagValueNode($className); - $this->docBlockManipulator->addDoctrineTagValueNode($property, $manyToOneTagValueNode); + $this->docBlockManipulator->addTagValueNodeWithShortName($property, $manyToOneTagValueNode); $properties[] = $property; } diff --git a/packages/NetteToSymfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php b/packages/NetteToSymfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php index 929b87e81340..7fea34f154a5 100644 --- a/packages/NetteToSymfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php +++ b/packages/NetteToSymfony/src/Rector/ClassMethod/RouterListToControllerAnnotationsRector.php @@ -14,7 +14,6 @@ use PhpParser\Node\Stmt\Class_; use PhpParser\Node\Stmt\ClassMethod; use PHPStan\Type\ObjectType; -use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\SpacelessPhpDocTagNode; use Rector\BetterPhpDocParser\PhpDocNode\Symfony\SymfonyRouteTagValueNode; use Rector\NetteToSymfony\Route\RouteInfoFactory; use Rector\NetteToSymfony\ValueObject\RouteInfo; @@ -244,12 +243,7 @@ private function addSymfonyRouteShortTagNodeWithUse( SymfonyRouteTagValueNode $symfonyRouteTagValueNode, ClassMethod $classMethod ): void { - $symfonyRoutePhpDocTagNode = new SpacelessPhpDocTagNode( - SymfonyRouteTagValueNode::SHORT_NAME, - $symfonyRouteTagValueNode - ); - - $this->docBlockManipulator->addTag($classMethod, $symfonyRoutePhpDocTagNode); + $this->docBlockManipulator->addTagValueNodeWithShortName($classMethod, $symfonyRouteTagValueNode); $symfonyRouteUseObjectType = new FullyQualifiedObjectType(SymfonyRouteTagValueNode::CLASS_NAME); $this->addUseType($symfonyRouteUseObjectType, $classMethod); diff --git a/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php b/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php index fbd0e7a3b603..fdc574b0eee4 100644 --- a/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php +++ b/packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/DocBlockManipulator.php @@ -40,7 +40,7 @@ use Rector\BetterPhpDocParser\Contract\Doctrine\DoctrineRelationTagValueNodeInterface; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo; use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory; -use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\AbstractDoctrineTagValueNode; +use Rector\BetterPhpDocParser\PhpDocNode\AbstractTagValueNode; use Rector\BetterPhpDocParser\Printer\PhpDocInfoPrinter; use Rector\Exception\ShouldNotHappenException; use Rector\NodeTypeResolver\Exception\MissingTagException; @@ -150,12 +150,9 @@ public function addTag(Node $node, PhpDocChildNode $phpDocChildNode): void } } - public function addDoctrineTagValueNode(Node $node, AbstractDoctrineTagValueNode $doctrineTagValueNode): void + public function addTagValueNodeWithShortName(Node $node, AbstractTagValueNode $tagValueNode): void { - $spacelessPhpDocTagNode = new SpacelessPhpDocTagNode( - $doctrineTagValueNode::SHORT_NAME, - $doctrineTagValueNode - ); + $spacelessPhpDocTagNode = new SpacelessPhpDocTagNode($tagValueNode::SHORT_NAME, $tagValueNode); $this->addTag($node, $spacelessPhpDocTagNode); } diff --git a/packages/ZendToSymfony/src/Rector/ClassMethod/GetParamToClassMethodParameterAndRouteRector.php b/packages/ZendToSymfony/src/Rector/ClassMethod/GetParamToClassMethodParameterAndRouteRector.php index d0ef85b08aff..6509bb5af926 100644 --- a/packages/ZendToSymfony/src/Rector/ClassMethod/GetParamToClassMethodParameterAndRouteRector.php +++ b/packages/ZendToSymfony/src/Rector/ClassMethod/GetParamToClassMethodParameterAndRouteRector.php @@ -12,7 +12,6 @@ use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Param; use PhpParser\Node\Stmt\ClassMethod; -use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\SpacelessPhpDocTagNode; use Rector\BetterPhpDocParser\PhpDocNode\Symfony\SymfonyRouteTagValueNode; use Rector\NodeTypeResolver\Node\AttributeKey; use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator; @@ -145,12 +144,7 @@ private function addClassMethodParameters(ClassMethod $classMethod, array $param private function addRouteAnnotation(ClassMethod $classMethod, RouteValueObject $routeValueObject): void { $symfonyRoutePhpDocTagNode = $routeValueObject->getSymfonyRoutePhpDocTagNode(); - $symfonyRoutePhpDocNode = new SpacelessPhpDocTagNode( - SymfonyRouteTagValueNode::SHORT_NAME, - $symfonyRoutePhpDocTagNode - ); - - $this->docBlockManipulator->addTag($classMethod, $symfonyRoutePhpDocNode); + $this->docBlockManipulator->addTagValueNodeWithShortName($classMethod, $symfonyRoutePhpDocTagNode); $this->addUseType(new FullyQualifiedObjectType(SymfonyRouteTagValueNode::CLASS_NAME), $classMethod); } diff --git a/phpstan.neon b/phpstan.neon index d37b2b204f71..ed8f76213aee 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -225,3 +225,6 @@ parameters: - '#Method Rector\\CakePHPToSymfony\\Rector\\Template\\TemplateMethodCallManipulator\:\:matchThisRenderMethodCallBareOrInReturn\(\) should return PhpParser\\Node\\Expr\\MethodCall\|null but returns PhpParser\\Node\\Expr#' # doc is not enough - '#Result of \|\| is always true#' + + # known value + - '#Access to undefined constant Rector\\BetterPhpDocParser\\PhpDocNode\\AbstractTagValueNode\:\:SHORT_NAME#' From 1da8409b6118e8826fe78a1cad86be0023a77b8e Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Tue, 21 Jan 2020 16:43:23 +0100 Subject: [PATCH 4/5] add OneToMany support --- .../Property_/OneToManyTagValueNode.php | 39 ++++++++++++------- .../NodeFactory/RelationPropertyFactory.php | 24 ++++++++++++ .../CakePHPModelToDoctrineEntityRector.php | 18 +++++++-- .../{fixture.php.inc => belongs_to.php.inc} | 0 .../Fixture/has_many.php.inc | 32 +++++++++++++++ 5 files changed, 95 insertions(+), 18 deletions(-) rename packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPModelToDoctrineEntityRector/Fixture/{fixture.php.inc => belongs_to.php.inc} (100%) create mode 100644 packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPModelToDoctrineEntityRector/Fixture/has_many.php.inc diff --git a/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/OneToManyTagValueNode.php b/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/OneToManyTagValueNode.php index 142ab49a7c79..09946f501283 100644 --- a/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/OneToManyTagValueNode.php +++ b/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/OneToManyTagValueNode.php @@ -31,12 +31,12 @@ final class OneToManyTagValueNode extends AbstractDoctrineTagValueNode implement private $cascade; /** - * @var string + * @var string|null */ private $fetch; /** - * @var bool + * @var bool|null */ private $orphanRemoval = false; @@ -46,19 +46,19 @@ final class OneToManyTagValueNode extends AbstractDoctrineTagValueNode implement private $indexBy; /** - * @var string + * @var string|null */ private $fqnTargetEntity; public function __construct( - ?string $mappedBy, + ?string $mappedBy = null, string $targetEntity, - ?array $cascade, - string $fetch, - bool $orphanRemoval, - ?string $indexBy, - ?string $originalContent, - string $fqnTargetEntity + ?array $cascade = null, + ?string $fetch = null, + ?bool $orphanRemoval = null, + ?string $indexBy = null, + ?string $originalContent = null, + ?string $fqnTargetEntity = null ) { $this->mappedBy = $mappedBy; $this->targetEntity = $targetEntity; @@ -75,15 +75,26 @@ public function __toString(): string { $contentItems = []; - $contentItems['mappedBy'] = sprintf('mappedBy="%s"', $this->mappedBy); + if ($this->mappedBy !== null) { + $contentItems['mappedBy'] = sprintf('mappedBy="%s"', $this->mappedBy); + } $contentItems['targetEntity'] = sprintf('targetEntity="%s"', $this->targetEntity); if ($this->cascade) { $contentItems['cascade'] = $this->printArrayItem($this->cascade, 'cascade'); } - $contentItems['fetch'] = sprintf('fetch="%s"', $this->fetch); - $contentItems['orphanRemoval'] = sprintf('orphanRemoval=%s', $this->orphanRemoval ? 'true' : 'false'); - $contentItems['indexBy'] = sprintf('indexBy="%s"', $this->indexBy); + + if ($this->fetch !== null) { + $contentItems['fetch'] = sprintf('fetch="%s"', $this->fetch); + } + + if ($this->orphanRemoval !== null) { + $contentItems['orphanRemoval'] = sprintf('orphanRemoval=%s', $this->orphanRemoval ? 'true' : 'false'); + } + + if ($this->indexBy !== null) { + $contentItems['indexBy'] = sprintf('indexBy="%s"', $this->indexBy); + } return $this->printContentItems($contentItems); } diff --git a/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php b/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php index 9cae097a40d5..c8ed77e05225 100644 --- a/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php +++ b/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php @@ -9,6 +9,7 @@ use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\JoinColumnTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\ManyToManyTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\ManyToOneTagValueNode; +use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\OneToManyTagValueNode; use Rector\Exception\ShouldNotHappenException; use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator; use Rector\PhpParser\Node\Value\ValueResolver; @@ -81,6 +82,29 @@ public function createManyToManyProperties(Property $hasAndBelongsToManyProperty return $properties; } + /** + * @return Property[] + */ + public function createOneToManyProperties(Property $hasManyProperty): array + { + $propertyDefaultValue = $this->getPropertyDefaultValue($hasManyProperty); + + $properties = []; + foreach ($propertyDefaultValue as $propertyName => $relationConfiguration) { + $property = $this->createPrivateProperty($propertyName); + + $className = $relationConfiguration['className']; + + // add @ORM\OneToMany + $manyToOneTagValueNode = new OneToManyTagValueNode(null, $className); + $this->docBlockManipulator->addTagValueNodeWithShortName($property, $manyToOneTagValueNode); + + $properties[] = $property; + } + + return $properties; + } + private function createPrivateProperty(string $propertyName): Property { $propertyName = lcfirst($propertyName); diff --git a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineEntityRector.php b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineEntityRector.php index 8a97fe37070f..0d505f1e579f 100644 --- a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineEntityRector.php +++ b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineEntityRector.php @@ -121,12 +121,22 @@ public function refactor(Node $node): ?Node $this->removeNode($belongsToProperty); } - $hasAndBelongsToMany = $this->classManipulator->getProperty($node, 'hasAndBelongsToMany'); - if ($hasAndBelongsToMany !== null) { - $manyToManyProperties = $this->relationPropertyFactory->createManyToManyProperties($hasAndBelongsToMany); + $hasAndBelongsToManyProperty = $this->classManipulator->getProperty($node, 'hasAndBelongsToMany'); + if ($hasAndBelongsToManyProperty !== null) { + $manyToManyProperties = $this->relationPropertyFactory->createManyToManyProperties( + $hasAndBelongsToManyProperty + ); $relationProperties = array_merge($relationProperties, $manyToManyProperties); - $this->removeNode($hasAndBelongsToMany); + $this->removeNode($hasAndBelongsToManyProperty); + } + + $hasManyProperty = $this->classManipulator->getProperty($node, 'hasMany'); + if ($hasManyProperty !== null) { + $manyToManyProperties = $this->relationPropertyFactory->createOneToManyProperties($hasManyProperty); + $relationProperties = array_merge($relationProperties, $manyToManyProperties); + + $this->removeNode($hasManyProperty); } if ($relationProperties !== []) { diff --git a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPModelToDoctrineEntityRector/Fixture/fixture.php.inc b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPModelToDoctrineEntityRector/Fixture/belongs_to.php.inc similarity index 100% rename from packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPModelToDoctrineEntityRector/Fixture/fixture.php.inc rename to packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPModelToDoctrineEntityRector/Fixture/belongs_to.php.inc diff --git a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPModelToDoctrineEntityRector/Fixture/has_many.php.inc b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPModelToDoctrineEntityRector/Fixture/has_many.php.inc new file mode 100644 index 000000000000..2d17cda19c65 --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPModelToDoctrineEntityRector/Fixture/has_many.php.inc @@ -0,0 +1,32 @@ + [ + 'className' => 'HasManyType', + ], + ]; +} + +?> +----- + From 812942e52cf925ec98952c1dcd6a551e4262db99 Mon Sep 17 00:00:00 2001 From: TomasVotruba Date: Tue, 21 Jan 2020 16:46:09 +0100 Subject: [PATCH 5/5] add oneToOne --- .../Property_/OneToManyTagValueNode.php | 2 +- .../Property_/OneToOneTagValueNode.php | 29 ++++++++++------- .../NodeFactory/RelationPropertyFactory.php | 24 ++++++++++++++ .../CakePHPModelToDoctrineEntityRector.php | 16 +++++++--- .../Fixture/has_one.php.inc | 32 +++++++++++++++++++ 5 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPModelToDoctrineEntityRector/Fixture/has_one.php.inc diff --git a/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/OneToManyTagValueNode.php b/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/OneToManyTagValueNode.php index 09946f501283..47b6ab043fd4 100644 --- a/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/OneToManyTagValueNode.php +++ b/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/OneToManyTagValueNode.php @@ -104,7 +104,7 @@ public function getTargetEntity(): string return $this->targetEntity; } - public function getFqnTargetEntity(): string + public function getFqnTargetEntity(): ?string { return $this->fqnTargetEntity; } diff --git a/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/OneToOneTagValueNode.php b/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/OneToOneTagValueNode.php index a05a998c93e4..e172af9462a2 100644 --- a/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/OneToOneTagValueNode.php +++ b/packages/BetterPhpDocParser/src/PhpDocNode/Doctrine/Property_/OneToOneTagValueNode.php @@ -42,12 +42,12 @@ final class OneToOneTagValueNode extends AbstractDoctrineTagValueNode implements private $fetch; /** - * @var bool + * @var bool|null */ private $orphanRemoval = false; /** - * @var string + * @var string|null */ private $fqnTargetEntity; @@ -56,13 +56,13 @@ final class OneToOneTagValueNode extends AbstractDoctrineTagValueNode implements */ public function __construct( string $targetEntity, - ?string $mappedBy, - ?string $inversedBy, - ?array $cascade, - ?string $fetch, - bool $orphanRemoval, - ?string $originalContent, - string $fqnTargetEntity + ?string $mappedBy = null, + ?string $inversedBy = null, + ?array $cascade = null, + ?string $fetch = null, + ?bool $orphanRemoval = null, + ?string $originalContent = null, + ?string $fqnTargetEntity = null ) { $this->targetEntity = $targetEntity; $this->mappedBy = $mappedBy; @@ -93,8 +93,13 @@ public function __toString(): string $contentItems['cascade'] = $this->printArrayItem($this->cascade, 'cascade'); } - $contentItems['fetch'] = sprintf('fetch="%s"', $this->fetch); - $contentItems['orphanRemoval'] = sprintf('orphanRemoval=%s', $this->orphanRemoval ? 'true' : 'false'); + if ($this->fetch !== null) { + $contentItems['fetch'] = sprintf('fetch="%s"', $this->fetch); + } + + if ($this->orphanRemoval !== null) { + $contentItems['orphanRemoval'] = sprintf('orphanRemoval=%s', $this->orphanRemoval ? 'true' : 'false'); + } return $this->printContentItems($contentItems); } @@ -104,7 +109,7 @@ public function getTargetEntity(): ?string return $this->targetEntity; } - public function getFqnTargetEntity(): string + public function getFqnTargetEntity(): ?string { return $this->fqnTargetEntity; } diff --git a/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php b/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php index c8ed77e05225..1a88fc12e923 100644 --- a/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php +++ b/packages/CakePHPToSymfony/src/NodeFactory/RelationPropertyFactory.php @@ -10,6 +10,7 @@ use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\ManyToManyTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\ManyToOneTagValueNode; use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\OneToManyTagValueNode; +use Rector\BetterPhpDocParser\PhpDocNode\Doctrine\Property_\OneToOneTagValueNode; use Rector\Exception\ShouldNotHappenException; use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator; use Rector\PhpParser\Node\Value\ValueResolver; @@ -59,6 +60,29 @@ public function createManyToOneProperties(Property $belongToProperty): array return $properties; } + /** + * @return Property[] + */ + public function createOneToOneProperties(Property $hasOneProperty): array + { + $propertyDefaultValue = $this->getPropertyDefaultValue($hasOneProperty); + + $properties = []; + foreach ($propertyDefaultValue as $propertyName => $relationConfiguration) { + $property = $this->createPrivateProperty($propertyName); + + $className = $relationConfiguration['className']; + + // add @ORM\ManyToOne + $manyToOneTagValueNode = new OneToOneTagValueNode($className); + $this->docBlockManipulator->addTagValueNodeWithShortName($property, $manyToOneTagValueNode); + + $properties[] = $property; + } + + return $properties; + } + /** * @return Property[] */ diff --git a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineEntityRector.php b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineEntityRector.php index 0d505f1e579f..4578ea768c87 100644 --- a/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineEntityRector.php +++ b/packages/CakePHPToSymfony/src/Rector/Class_/CakePHPModelToDoctrineEntityRector.php @@ -123,22 +123,30 @@ public function refactor(Node $node): ?Node $hasAndBelongsToManyProperty = $this->classManipulator->getProperty($node, 'hasAndBelongsToMany'); if ($hasAndBelongsToManyProperty !== null) { - $manyToManyProperties = $this->relationPropertyFactory->createManyToManyProperties( + $newRelationProperties = $this->relationPropertyFactory->createManyToManyProperties( $hasAndBelongsToManyProperty ); - $relationProperties = array_merge($relationProperties, $manyToManyProperties); + $relationProperties = array_merge($relationProperties, $newRelationProperties); $this->removeNode($hasAndBelongsToManyProperty); } $hasManyProperty = $this->classManipulator->getProperty($node, 'hasMany'); if ($hasManyProperty !== null) { - $manyToManyProperties = $this->relationPropertyFactory->createOneToManyProperties($hasManyProperty); - $relationProperties = array_merge($relationProperties, $manyToManyProperties); + $newRelationProperties = $this->relationPropertyFactory->createOneToManyProperties($hasManyProperty); + $relationProperties = array_merge($relationProperties, $newRelationProperties); $this->removeNode($hasManyProperty); } + $hasOneProperty = $this->classManipulator->getProperty($node, 'hasOne'); + if ($hasOneProperty !== null) { + $newRelationProperties = $this->relationPropertyFactory->createOneToOneProperties($hasOneProperty); + $relationProperties = array_merge($relationProperties, $newRelationProperties); + + $this->removeNode($hasOneProperty); + } + if ($relationProperties !== []) { $node->stmts = array_merge($relationProperties, (array) $node->stmts); } diff --git a/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPModelToDoctrineEntityRector/Fixture/has_one.php.inc b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPModelToDoctrineEntityRector/Fixture/has_one.php.inc new file mode 100644 index 000000000000..9c14297c3200 --- /dev/null +++ b/packages/CakePHPToSymfony/tests/Rector/Class_/CakePHPModelToDoctrineEntityRector/Fixture/has_one.php.inc @@ -0,0 +1,32 @@ + [ + 'className' => 'HasOneType', + ], + ]; +} + +?> +----- +