init uuid migration

This commit is contained in:
Tomas Votruba 2019-08-27 18:23:50 +02:00
parent 697655d090
commit c1303c6ba7
45 changed files with 1368 additions and 145 deletions

View File

@ -1 +1,3 @@
services:
Rector\Doctrine\Rector\Class_\AddUuidToEntityWhereMissingRector: ~
Rector\Doctrine\Rector\Class_\AddUuidMirrorForRelationPropertyRector: ~

View File

@ -109,6 +109,7 @@ parameters:
Symplify\CodingStandard\Sniffs\CleanCode\CognitiveComplexitySniff:
# tough logic
- 'packages/DoctrinePhpDocParser/src/Ast/PhpDoc/*/*TagValueNode.php'
- 'packages/NodeTypeResolver/src/PhpDoc/NodeAnalyzer/FqnNamePhpDocNodeDecorator.php'
- 'packages/NodeTypeResolver/src/PHPStan/Type/StaticTypeAnalyzer.php'
- 'src/NodeContainer/ParsedNodesByType.php'

View File

@ -15,12 +15,13 @@ use Rector\BetterPhpDocParser\Attributes\Attribute\Attribute;
use Rector\BetterPhpDocParser\Attributes\Contract\Ast\AttributeAwareNodeInterface;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Class_\EntityTagValueNode;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\ColumnTagValueNode;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\IdTagValueNode;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\JoinColumnTagValueNode;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\ManyToManyTagValueNode;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\ManyToOneTagValueNode;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\OneToManyTagValueNode;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\OneToOneTagValueNode;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\RelationTagValueNodeInterface;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\DoctrineRelationTagValueNodeInterface;
final class PhpDocInfo
{
@ -175,6 +176,11 @@ final class PhpDocInfo
return $this->getResolvedTypesAttribute($varTagValue);
}
public function getDoctrineIdTagValueNode(): ?IdTagValueNode
{
return $this->matchChildValueNodeOfType(IdTagValueNode::class);
}
public function getDoctrineManyToManyTagValueNode(): ?ManyToManyTagValueNode
{
return $this->matchChildValueNodeOfType(ManyToManyTagValueNode::class);
@ -249,7 +255,7 @@ final class PhpDocInfo
return $this->getResolvedTypesAttribute($returnTypeValueNode);
}
public function getRelationTagValueNode(): ?RelationTagValueNodeInterface
public function getDoctrineRelationTagValueNode(): ?DoctrineRelationTagValueNodeInterface
{
return $this->getDoctrineManyToManyTagValueNode() ??
$this->getDoctrineOneToManyTagValueNode() ??
@ -257,6 +263,19 @@ final class PhpDocInfo
$this->getDoctrineManyToOneTagValueNode() ?? null;
}
public function removeTagValueNodeFromNode(PhpDocTagValueNode $phpDocTagValueNode): void
{
foreach ($this->phpDocNode->children as $key => $phpDocChildNode) {
if ($phpDocChildNode instanceof PhpDocTagNode) {
if ($phpDocChildNode->value !== $phpDocTagValueNode) {
continue;
}
unset($this->phpDocNode->children[$key]);
}
}
}
private function getParamTagValueByName(string $name): ?AttributeAwareParamTagValueNode
{
$phpDocNode = $this->getPhpDocNode();

View File

@ -31,6 +31,12 @@ final class OriginalSpacingRestorer
// replace system whitespace by old ones, include \n*
$nodeOutputParts = Strings::split($nodeOutput, '#\s+#');
// new nodes were probably added, skip them
if (count($oldWhitespaces) < count($nodeOutputParts)) {
return $nodeOutput;
}
$hasAsterixMultiline = false;
foreach ($nodeOutputParts as $key => $nodeOutputPart) {
if (isset($oldWhitespaces[$key])) {

View File

@ -37,7 +37,7 @@ final class DoctrineEntityManipulator
$phpDocInfo = $this->docBlockManipulator->createPhpDocInfoFromNode($property);
$relationTagValueNode = $phpDocInfo->getRelationTagValueNode();
$relationTagValueNode = $phpDocInfo->getDoctrineRelationTagValueNode();
if ($relationTagValueNode === null) {
return null;
}
@ -84,7 +84,7 @@ final class DoctrineEntityManipulator
}
$phpDocInfo = $this->docBlockManipulator->createPhpDocInfoFromNode($property);
$relationTagValueNode = $phpDocInfo->getRelationTagValueNode();
$relationTagValueNode = $phpDocInfo->getDoctrineRelationTagValueNode();
$shouldUpdate = false;
if ($relationTagValueNode instanceof MappedByNodeInterface) {
@ -125,7 +125,7 @@ final class DoctrineEntityManipulator
}
$phpDocInfo = $this->docBlockManipulator->createPhpDocInfoFromNode($stmt);
if ($phpDocInfo->getRelationTagValueNode() === null) {
if ($phpDocInfo->getDoctrineRelationTagValueNode() === null) {
continue;
}

View File

@ -13,7 +13,7 @@ final class RemoveUnusedDoctrineEntityMethodAndPropertyRectorTest extends Abstra
__DIR__ . '/Fixture/fixture.php.inc',
__DIR__ . '/Fixture/remove_inversed_by.php.inc',
__DIR__ . '/Fixture/remove_inversed_by_non_fqn.php.inc',
// skip
// 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',

View File

@ -1,3 +1,6 @@
parameters:
doctrine_uuid_generator_class: 'Ramsey\Uuid\Doctrine\UuidGenerator'
services:
_defaults:
autowire: true

View File

@ -0,0 +1,33 @@
<?php declare(strict_types=1);
namespace Rector\Doctrine\AbstarctRector;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\DoctrineRelationTagValueNodeInterface;
use Rector\Rector\AbstractRector\DocBlockManipulatorTrait;
trait DoctrineTrait
{
use DocBlockManipulatorTrait;
protected function isDoctrineEntityClass(Class_ $class): bool
{
$classPhpDocInfo = $this->getPhpDocInfo($class);
if ($classPhpDocInfo === null) {
return false;
}
return (bool) $classPhpDocInfo->getDoctrineEntityTag();
}
protected function getDoctrineRelationTagValueNode(Property $property): ?DoctrineRelationTagValueNodeInterface
{
$propertyPhpDocInfo = $this->getPhpDocInfo($property);
if ($propertyPhpDocInfo === null) {
return null;
}
return $propertyPhpDocInfo->getDoctrineRelationTagValueNode();
}
}

View File

@ -0,0 +1,24 @@
<?php declare(strict_types=1);
namespace Rector\Doctrine\Collector;
final class EntitiesWithAddedUuidPropertyCollector
{
/**
* @var string[]
*/
private $classes = [];
public function addClass(string $class): void
{
$this->classes[] = $class;
}
/**
* @return string[]
*/
public function getClasses(): array
{
return $this->classes;
}
}

View File

@ -0,0 +1,38 @@
<?php declare(strict_types=1);
namespace Rector\Doctrine\Extension;
use Rector\Contract\Extension\FinishingExtensionInterface;
use Rector\Doctrine\Collector\EntitiesWithAddedUuidPropertyCollector;
use Symfony\Component\Console\Style\SymfonyStyle;
final class ReportEntitiesWithAddedUuidFinishExtension implements FinishingExtensionInterface
{
/**
* @var EntitiesWithAddedUuidPropertyCollector
*/
private $entitiesWithAddedUuidPropertyCollector;
/**
* @var SymfonyStyle
*/
private $symfonyStyle;
public function __construct(
EntitiesWithAddedUuidPropertyCollector $entitiesWithAddedUuidPropertyCollector,
SymfonyStyle $symfonyStyle
) {
$this->entitiesWithAddedUuidPropertyCollector = $entitiesWithAddedUuidPropertyCollector;
$this->symfonyStyle = $symfonyStyle;
}
public function run(): void
{
$classes = $this->entitiesWithAddedUuidPropertyCollector->getClasses();
if (count($classes)) {
$this->symfonyStyle->section('Entities with new nullable $uuid property');
$this->symfonyStyle->listing($classes);
}
}
}

View File

@ -0,0 +1,184 @@
<?php
declare(strict_types=1);
namespace Rector\Doctrine\NodeFactory;
use Nette\Utils\Strings;
use PhpParser\BuilderFactory;
use PhpParser\Comment\Doc;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use Rector\Doctrine\ValueObject\DoctrineClass;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\ColumnTagValueNode;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\CustomIdGeneratorTagValueNode;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\GeneratedValueTagValueNode;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\IdTagValueNode;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
final class EntityUuidNodeFactory
{
/**
* @var DocBlockManipulator
*/
private $docBlockManipulator;
/**
* @var BuilderFactory
*/
private $builderFactory;
/**
* @var string
*/
private $doctrineUuidGeneratorClass;
public function __construct(
string $doctrineUuidGeneratorClass,
DocBlockManipulator $docBlockManipulator,
BuilderFactory $builderFactory
) {
$this->docBlockManipulator = $docBlockManipulator;
$this->builderFactory = $builderFactory;
$this->doctrineUuidGeneratorClass = $doctrineUuidGeneratorClass;
}
public function createTemporaryUuidProperty(): Property
{
$uuidProperty = $this->builderFactory->property('uuid')
->makePrivate()
->getNode();
$this->decoratePropertyWithUuidAnnotations($uuidProperty, true, false);
return $uuidProperty;
}
/**
* Creates: "$this->uid = \Ramsey\Uuid\Uuid::uuid4();"
*/
public function createUuidPropertyDefaultValueAssign(): Expression
{
$thisUuidPropertyFetch = new PropertyFetch(new Variable('this'), 'uuid');
$uuid4StaticCall = new StaticCall(new FullyQualified('Ramsey\Uuid\Uuid'), 'uuid4');
$assign = new Assign($thisUuidPropertyFetch, $uuid4StaticCall);
return new Expression($assign);
}
public function createConstructorWithUuidPropertyDefaultValueAssign(): ClassMethod
{
$classMethodBuilder = $this->builderFactory->method('__construct');
$classMethodBuilder->makePublic();
$assign = $this->createUuidPropertyDefaultValueAssign();
$classMethodBuilder->addStmt($assign);
return $classMethodBuilder->getNode();
}
private function clearVarAndOrmAnnotations(Node $node): void
{
$docComment = $node->getDocComment();
if ($docComment === null) {
return;
}
$clearedDocCommentText = Strings::replace($docComment->getText(), '#^(\s+)\*(\s+)\@(var|ORM)(.*?)$#ms');
$node->setDocComment(new Doc($clearedDocCommentText));
}
private function decoratePropertyWithUuidAnnotations(Property $property, bool $isNullable, bool $isId): void
{
$this->clearVarAndOrmAnnotations($property);
$this->replaceIntSerializerTypeWithString($property);
// add @var
$this->docBlockManipulator->addTag($property, $this->createVarTagUuidInterface());
if ($isId) {
// add @ORM\Id
$this->docBlockManipulator->addTag($property, $this->createIdTag());
}
$this->docBlockManipulator->addTag($property, $this->createUuidColumnTag($isNullable));
if ($isId) {
$this->docBlockManipulator->addTag($property, $this->createGeneratedValueTag());
$this->docBlockManipulator->addTag($property, $this->createCustomIdGeneratorTag());
}
}
/**
* See https://github.com/ramsey/uuid-doctrine/issues/50#issuecomment-348123520.
*/
private function replaceIntSerializerTypeWithString(Node $node): void
{
$docComment = $node->getDocComment();
if ($docComment === null) {
return;
}
$stringTypeText = Strings::replace(
$docComment->getText(),
'#(\@Serializer\\\\Type\(")(int)("\))#',
'$1string$3'
);
$node->setDocComment(new Doc($stringTypeText));
}
private function createVarTagUuidInterface(): PhpDocTagNode
{
$varTagValueNode = new VarTagValueNode(new IdentifierTypeNode(
'\\' . DoctrineClass::RAMSEY_UUID_INTERFACE
), '', '');
return new PhpDocTagNode('@var', $varTagValueNode);
}
private function createIdTag(): PhpDocTagNode
{
return new PhpDocTagNode(IdTagValueNode::SHORT_NAME, new IdTagValueNode());
}
private function createUuidColumnTag(bool $isNullable): PhpDocTagNode
{
$columnTagValueNode = new ColumnTagValueNode(
null,
'uuid_binary',
null,
null,
null,
true,
$isNullable ? true : null
);
return new PhpDocTagNode($columnTagValueNode::SHORT_NAME, $columnTagValueNode);
}
private function createGeneratedValueTag(): PhpDocTagNode
{
return new PhpDocTagNode(GeneratedValueTagValueNode::SHORT_NAME, new GeneratedValueTagValueNode('CUSTOM'));
}
private function createCustomIdGeneratorTag(): PhpDocTagNode
{
return new PhpDocTagNode(
CustomIdGeneratorTagValueNode::SHORT_NAME,
new CustomIdGeneratorTagValueNode($this->doctrineUuidGeneratorClass)
);
}
}

View File

@ -0,0 +1,227 @@
<?php
declare(strict_types=1);
namespace Rector\Doctrine\Rector\Class_;
use Nette\Utils\Strings;
use PhpParser\Comment\Doc;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\PropertyProperty;
use PhpParser\Node\VarLikeIdentifier;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use Rector\BetterPhpDocParser\Attributes\Ast\PhpDoc\AttributeAwarePhpDocTagNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\JoinColumnTagValueNode;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\JoinTableTagValueNode;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\DoctrineRelationTagValueNodeInterface;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\ToManyTagNodeInterface;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\ToOneTagNodeInterface;
use Rector\Exception\ShouldNotHappenException;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Rector\NodeTypeResolver\PhpDoc\NodeAnalyzer\DocBlockManipulator;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\RectorDefinition;
/**
* @sponsor Thanks https://spaceflow.io/ for sponsoring this rule - visit them on https://github.com/SpaceFlow-app
*/
final class AddUuidMirrorForRelationPropertyRector extends AbstractRector
{
/**
* @var DocBlockManipulator
*/
private $docBlockManipulator;
public function __construct(DocBlockManipulator $docBlockManipulator)
{
$this->docBlockManipulator = $docBlockManipulator;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
'Adds $uuid property to entities, that already have $id with integer type.' .
'Require for step-by-step migration from int to uuid.'
);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->isDoctrineEntityClass($node)) {
return null;
}
// traverse relations and see which of them have freshly added uuid on the other side
foreach ($node->stmts as $classStmt) {
if (! $classStmt instanceof Property) {
continue;
}
// this relation already is or has uuid property
if ($this->isName($classStmt, '*Uuid')) {
continue;
}
$uuidPropertyName = $this->getName($classStmt) . 'Uuid';
if ($this->hasClassPropertyName($node, $uuidPropertyName)) {
continue;
}
$doctrineRelationTagValueNode = $this->getDoctrineRelationTagValueNode($classStmt);
if ($doctrineRelationTagValueNode === null) {
continue;
}
if ($doctrineRelationTagValueNode->getTargetEntity() === null) {
throw new ShouldNotHappenException();
}
$node->stmts[] = $this->createMirrorNullable($classStmt);
}
return $node;
}
/**
* Creates duplicated property, that has "*uuidSuffix"
* and nullable join column, so we cna complete them manually
*/
private function createMirrorNullable(Property $property): Property
{
$propertyWithUuid = clone $property;
// this is needed to keep old property name
$this->updateDocComment($propertyWithUuid);
// name must be changed after the doc comment update, because the reflection annotation needed for update of doc comment
// would miss non existing *Uuid property
$uuidPropertyName = $this->getName($propertyWithUuid) . 'Uuid';
$newPropertyProperty = new PropertyProperty(new VarLikeIdentifier($uuidPropertyName));
$propertyWithUuid->props = [$newPropertyProperty];
return $propertyWithUuid;
}
/**
* Creates unique many-to-many table name like: first_table_uuid_second_table_uuid
*/
private function createManyToManyUuidTableName(Property $property): string
{
/** @var string $currentClass */
$currentClass = $property->getAttribute(AttributeKey::CLASS_NAME);
$shortCurrentClass = Strings::after($currentClass, '\\', -1);
$targetClass = $this->resolveTargetClass($property);
if ($targetClass === null) {
throw new ShouldNotHappenException(__METHOD__);
}
$shortTargetClass = Strings::after($targetClass, '\\', -1);
return strtolower($shortCurrentClass . '_uuid_' . $shortTargetClass . '_uuid');
}
private function resolveTargetClass(Property $property): ?string
{
/** @var PhpDocInfo $phpDocInfo */
$phpDocInfo = $this->getPhpDocInfo($property);
/** @var DoctrineRelationTagValueNodeInterface $relationTagValueNode */
$relationTagValueNode = $phpDocInfo->getDoctrineRelationTagValueNode();
return $relationTagValueNode->getFqnTargetEntity();
}
private function updateDocComment(Property $property): void
{
$propertyPhpDocInfo = $this->getPhpDocInfo($property);
if ($propertyPhpDocInfo === null) {
return;
}
/** @var DoctrineRelationTagValueNodeInterface $doctrineRelationTagValueNode */
$doctrineRelationTagValueNode = $propertyPhpDocInfo->getDoctrineRelationTagValueNode();
if ($doctrineRelationTagValueNode instanceof ToManyTagNodeInterface) {
$this->refactorToManyPropertyPhpDocInfo($propertyPhpDocInfo, $property);
} elseif ($doctrineRelationTagValueNode instanceof ToOneTagNodeInterface) {
$this->refactorToOnePropertyPhpDocInfo($propertyPhpDocInfo);
}
$this->docBlockManipulator->updateNodeWithPhpDocInfo($property, $propertyPhpDocInfo);
}
private function refactorToManyPropertyPhpDocInfo(PhpDocInfo $propertyPhpDocInfo, Property $property): void
{
$doctrineJoinColumnTagValueNode = $propertyPhpDocInfo->getDoctrineJoinColumnTagValueNode();
if ($doctrineJoinColumnTagValueNode) {
// replace @ORM\JoinColumn with @ORM\JoinTable
$propertyPhpDocInfo->removeTagValueNodeFromNode($doctrineJoinColumnTagValueNode);
}
$propertyPhpDocInfo->getPhpDocNode()->children[] = $this->createJoinTableTagNode($property);
}
private function refactorToOnePropertyPhpDocInfo(PhpDocInfo $propertyPhpDocInfo): void
{
$joinColumnTagValueNode = $propertyPhpDocInfo->getDoctrineJoinColumnTagValueNode();
if ($joinColumnTagValueNode) {
$joinColumnTagValueNode->changeNullable(true);
$joinColumnTagValueNode->changeReferencedColumnName('uuid');
} else {
$propertyPhpDocInfo->getPhpDocNode()->children[] = $this->createJoinColumnTagNode();
}
}
private function hasClassPropertyName(Class_ $node, string $uuidPropertyName): bool
{
foreach ($node->stmts as $stmt) {
if (! $stmt instanceof Property) {
continue;
}
if (! $this->isName($stmt, $uuidPropertyName)) {
continue;
}
return true;
}
return false;
}
private function createJoinTableTagNode(Property $property): PhpDocTagNode
{
$joinTableName = $this->createManyToManyUuidTableName($property);
$joinTableTagValueNode = new JoinTableTagValueNode($joinTableName, null, [
new JoinColumnTagValueNode(null, 'uuid'),
], [new JoinColumnTagValueNode(null, 'uuid')]);
return new AttributeAwarePhpDocTagNode(JoinTableTagValueNode::SHORT_NAME, $joinTableTagValueNode);
}
private function createJoinColumnTagNode(): PhpDocTagNode
{
$joinColumnTagValueNode = new JoinColumnTagValueNode(null, 'uuid', null, false);
return new AttributeAwarePhpDocTagNode(JoinColumnTagValueNode::SHORT_NAME, $joinColumnTagValueNode);
}
}

View File

@ -0,0 +1,135 @@
<?php
declare(strict_types=1);
namespace Rector\Doctrine\Rector\Class_;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Property;
use Rector\Doctrine\Collector\EntitiesWithAddedUuidPropertyCollector;
use Rector\Doctrine\NodeFactory\EntityUuidNodeFactory;
use Rector\PhpParser\Node\Manipulator\ClassManipulator;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\RectorDefinition;
/**
* @sponsor Thanks https://spaceflow.io/ for sponsoring this rule - visit them on https://github.com/SpaceFlow-app
*/
final class AddUuidToEntityWhereMissingRector extends AbstractRector
{
/**
* @var EntityUuidNodeFactory
*/
private $entityUuidNodeFactory;
/**
* @var ClassManipulator
*/
private $classManipulator;
/**
* @var EntitiesWithAddedUuidPropertyCollector
*/
private $entitiesWithAddedUuidPropertyCollector;
public function __construct(
EntityUuidNodeFactory $entityUuidNodeFactory,
ClassManipulator $classManipulator,
EntitiesWithAddedUuidPropertyCollector $entitiesWithAddedUuidPropertyCollector
) {
$this->entityUuidNodeFactory = $entityUuidNodeFactory;
$this->classManipulator = $classManipulator;
$this->entitiesWithAddedUuidPropertyCollector = $entitiesWithAddedUuidPropertyCollector;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
'Adds $uuid property to entities, that already have $id with integer type.' .
'Require for step-by-step migration from int to uuid. ' .
'In following step it should be renamed to $id and replace it'
);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->isDoctrineEntityClass($node)) {
return null;
}
// already has $uuid property
if ($this->classManipulator->getProperty($node, 'uuid')) {
return null;
}
if ($this->hasClassIdPropertyWithUuidType($node)) {
return null;
}
// 1. add to start of the class, so it can be easily seen
$uuidProperty = $this->entityUuidNodeFactory->createTemporaryUuidProperty();
$node->stmts = array_merge([$uuidProperty], $node->stmts);
// 2. add default value to uuid property
$constructClassMethod = $node->getMethod('__construct');
if ($constructClassMethod) {
$assignExpression = $this->entityUuidNodeFactory->createUuidPropertyDefaultValueAssign();
$constructClassMethod->stmts = array_merge([$assignExpression], (array) $constructClassMethod->stmts);
} else {
$constructClassMethod = $this->entityUuidNodeFactory->createConstructorWithUuidPropertyDefaultValueAssign();
$node->stmts = array_merge([$constructClassMethod], $node->stmts);
}
/** @var string $class */
$class = $this->getName($node);
$this->entitiesWithAddedUuidPropertyCollector->addClass($class);
return $node;
}
private function hasClassIdPropertyWithUuidType(Class_ $class): bool
{
foreach ($class->stmts as $classStmt) {
if (! $classStmt instanceof Property) {
continue;
}
if (! $this->isName($classStmt, 'id')) {
continue;
}
$propertyPhpDocInfo = $this->getPhpDocInfo($classStmt);
if ($propertyPhpDocInfo === null) {
return false;
}
$idTagValueNode = $propertyPhpDocInfo->getDoctrineIdTagValueNode();
if ($idTagValueNode === null) {
return false;
}
// get column!
$columnTagValueNode = $propertyPhpDocInfo->getDoctrineColumnTagValueNode();
if ($columnTagValueNode === null) {
return false;
}
return (bool) Strings::match((string) $columnTagValueNode->getType(), '#^uuid(_binary)?$#');
}
return false;
}
}

View File

@ -4,6 +4,11 @@ namespace Rector\Doctrine\ValueObject;
final class DoctrineClass
{
/**
* @var string
*/
public const RAMSEY_UUID_INTERFACE = 'Ramsey\Uuid\UuidInterface';
/**
* @var string
*/

View File

@ -0,0 +1,23 @@
<?php declare(strict_types=1);
namespace Rector\Doctrine\Tests\Rector\Class_\AddUuidMirrorForRelationPropertyRector;
use Rector\Doctrine\Rector\Class_\AddUuidMirrorForRelationPropertyRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class AddUuidToEntityWhereMissingRectorTest extends AbstractRectorTestCase
{
public function test(): void
{
$this->doTestFiles([
__DIR__ . '/Fixture/fixture.php.inc',
__DIR__ . '/Fixture/skip_already_added.php.inc',
__DIR__ . '/Fixture/to_many.php.inc',
]);
}
protected function getRectorClass(): string
{
return AddUuidMirrorForRelationPropertyRector::class;
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\AddUuidMirrorForRelationPropertyRector\Fixture;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class SomeEntity
{
/**
* @ORM\ManyToOne(targetEntity="Rector\Doctrine\Tests\Rector\Class_\AddUuidMirrorForRelationPropertyRector\Source\AnotherEntity", cascade={"persist", "merge"})
* @ORM\JoinColumn(nullable=false)
*/
private $amenity;
}
?>
-----
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\AddUuidMirrorForRelationPropertyRector\Fixture;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class SomeEntity
{
/**
* @ORM\ManyToOne(targetEntity="Rector\Doctrine\Tests\Rector\Class_\AddUuidMirrorForRelationPropertyRector\Source\AnotherEntity", cascade={"persist", "merge"})
* @ORM\JoinColumn(nullable=false)
*/
private $amenity;
/**
* @ORM\ManyToOne(targetEntity="Rector\Doctrine\Tests\Rector\Class_\AddUuidMirrorForRelationPropertyRector\Source\AnotherEntity", cascade={"persist", "merge"})
* @ORM\JoinColumn(nullable=true, referencedColumnName="uuid")
*/
private $amenityUuid;
}
?>

View File

@ -0,0 +1,23 @@
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\AddUuidMirrorForRelationPropertyRector\Fixture;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class SkipAlreadyAdded
{
/**
* @ORM\ManyToOne(targetEntity="Rector\Doctrine\Tests\Rector\Class_\AddUuidMirrorForRelationPropertyRector\Source\AnotherEntity", cascade={"persist", "merge"})
* @ORM\JoinColumn(nullable=false)
*/
private $amenity;
/**
* @ORM\ManyToOne(targetEntity="Rector\Doctrine\Tests\Rector\Class_\AddUuidMirrorForRelationPropertyRector\Source\AnotherEntity", cascade={"persist", "merge"})
* @ORM\JoinColumn(nullable=true, referencedColumnName="uuid")
*/
private $amenityUuid;
}

View File

@ -0,0 +1,42 @@
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\AddUuidMirrorForRelationPropertyRector\Fixture;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class ToMany
{
/**
* @ORM\ManyToMany(targetEntity="Rector\Doctrine\Tests\Rector\Class_\AddUuidMirrorForRelationPropertyRector\Source\AnotherEntity", cascade={"persist", "merge"})
*/
private $amenity;
}
?>
-----
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\AddUuidMirrorForRelationPropertyRector\Fixture;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class ToMany
{
/**
* @ORM\ManyToMany(targetEntity="Rector\Doctrine\Tests\Rector\Class_\AddUuidMirrorForRelationPropertyRector\Source\AnotherEntity", cascade={"persist", "merge"})
*/
private $amenity;
/**
* @ORM\ManyToMany(targetEntity="Rector\Doctrine\Tests\Rector\Class_\AddUuidMirrorForRelationPropertyRector\Source\AnotherEntity", cascade={"persist", "merge"})
* @ORM\JoinTable (name="tomany_uuid_anotherentity_uuid", joinColumns={@ORM\JoinColumn(referencedColumnName="uuid")}, inverseJoinColumns={@ORM\JoinColumn(referencedColumnName="uuid")})
*/
private $amenityUuid;
}
?>

View File

@ -0,0 +1,25 @@
<?php declare(strict_types=1);
namespace Rector\Doctrine\Tests\Rector\Class_\AddUuidMirrorForRelationPropertyRector\Source;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class AnotherEntity
{
/**
* @var int
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var \Ramsey\Uuid\UuidInterface
* @ORM\Column (type="uuid_binary", unique=true, nullable=true)
*/
private $uuid;
}

View File

@ -0,0 +1,26 @@
<?php declare(strict_types=1);
namespace Rector\Doctrine\Tests\Rector\Class_\AddUuidToEntityWhereMissingRector;
use Rector\Doctrine\Rector\Class_\AddUuidToEntityWhereMissingRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class AddUuidToEntityWhereMissingRectorTest extends AbstractRectorTestCase
{
public function test(): void
{
$this->doTestFiles([
__DIR__ . '/Fixture/fixture.php.inc',
__DIR__ . '/Fixture/already_has_constructor.php.inc',
__DIR__ . '/Fixture/process_string_id.php.inc',
// skip
__DIR__ . '/Fixture/skip_id_with_uuid_type.php.inc',
__DIR__ . '/Fixture/skip_id_with_uuid_binary_type.php.inc',
]);
}
protected function getRectorClass(): string
{
return AddUuidToEntityWhereMissingRector::class;
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\AddUuidToEntityWhereMissingRector\Fixture;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class AlreadyHasConstructor
{
/**
* @var int
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
public function __construct()
{
$this->id = 5;
}
}
?>
-----
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\AddUuidToEntityWhereMissingRector\Fixture;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class AlreadyHasConstructor
{
/**
* @var \Ramsey\Uuid\UuidInterface
* @ORM\Column (type="uuid_binary", unique=true, nullable=true)
*/
private $uuid;
/**
* @var int
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
public function __construct()
{
$this->uuid = \Ramsey\Uuid\Uuid::uuid4();
$this->id = 5;
}
}
?>

View File

@ -0,0 +1,52 @@
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\AddUuidToEntityWhereMissingRector\Fixture;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class SomeEntityWithIntegerId
{
/**
* @var int
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
}
?>
-----
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\AddUuidToEntityWhereMissingRector\Fixture;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class SomeEntityWithIntegerId
{
public function __construct()
{
$this->uuid = \Ramsey\Uuid\Uuid::uuid4();
}
/**
* @var \Ramsey\Uuid\UuidInterface
* @ORM\Column (type="uuid_binary", unique=true, nullable=true)
*/
private $uuid;
/**
* @var int
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
}
?>

View File

@ -0,0 +1,52 @@
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\AddUuidToEntityWhereMissingRector\Fixture;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class ProcessStringId
{
/**
* @var int
* @ORM\Id
* @ORM\Column(type="string")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
}
?>
-----
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\AddUuidToEntityWhereMissingRector\Fixture;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class ProcessStringId
{
public function __construct()
{
$this->uuid = \Ramsey\Uuid\Uuid::uuid4();
}
/**
* @var \Ramsey\Uuid\UuidInterface
* @ORM\Column (type="uuid_binary", unique=true, nullable=true)
*/
private $uuid;
/**
* @var int
* @ORM\Id
* @ORM\Column(type="string")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
}
?>

View File

@ -0,0 +1,19 @@
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\AddUuidToEntityWhereMissingRector\Fixture;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class SkipIdWithUuidBinaryType
{
/**
* @var int
* @ORM\Id
* @ORM\Column(type="uuid_binary")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
}

View File

@ -0,0 +1,19 @@
<?php
namespace Rector\Doctrine\Tests\Rector\Class_\AddUuidToEntityWhereMissingRector\Fixture;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*/
class SkipIdWithUuidType
{
/**
* @var int
* @ORM\Id
* @ORM\Column(type="uuid")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
}

View File

@ -2,6 +2,8 @@
namespace Rector\DoctrinePhpDocParser\Array_;
use Nette\Utils\Strings;
/**
* Helpers class for ordering items in values objects on call.
* Beware of static methods as they might doom you on the edge of life.
@ -9,17 +11,17 @@ namespace Rector\DoctrinePhpDocParser\Array_;
final class ArrayItemStaticHelper
{
/**
* @param string[] $items
* @return string[]
*/
public static function removeItemFromArray(array $items, string $itemToRemove): array
public static function resolveAnnotationItemsOrder(string $content): array
{
$itemPosition = array_search($itemToRemove, $items, true);
if ($itemPosition !== null) {
unset($items[$itemPosition]);
$itemsOrder = [];
$matches = Strings::matchAll($content, '#(?<item>\w+)=#m');
foreach ($matches as $match) {
$itemsOrder[] = $match['item'];
}
return $items;
return $itemsOrder;
}
/**

View File

@ -15,9 +15,9 @@ abstract class AbstractDoctrineTagValueNode implements AttributeAwareNodeInterfa
use AttributeTrait;
/**
* @var string[]
* @var string[]|null
*/
protected $orderedVisibleItems = [];
protected $orderedVisibleItems;
/**
* @param mixed[] $item
@ -36,7 +36,10 @@ abstract class AbstractDoctrineTagValueNode implements AttributeAwareNodeInterfa
*/
protected function printContentItems(array $contentItems): string
{
$contentItems = ArrayItemStaticHelper::filterAndSortVisibleItems($contentItems, $this->orderedVisibleItems);
if ($this->orderedVisibleItems !== null) {
$contentItems = ArrayItemStaticHelper::filterAndSortVisibleItems($contentItems, $this->orderedVisibleItems);
}
if ($contentItems === []) {
return '';
}

View File

@ -30,7 +30,10 @@ final class EntityTagValueNode extends AbstractDoctrineTagValueNode
{
$contentItems = [];
$contentItems['repositoryClass'] = sprintf('repositoryClass="%s"', $this->repositoryClass);
if ($this->repositoryClass !== null) {
$contentItems['repositoryClass'] = sprintf('repositoryClass="%s"', $this->repositoryClass);
}
$contentItems['readOnly'] = sprintf('readOnly=%s', $this->readOnly ? 'true' : 'false');
return $this->printContentItems($contentItems);
@ -38,11 +41,6 @@ final class EntityTagValueNode extends AbstractDoctrineTagValueNode
public function removeRepositoryClass(): void
{
$itemPosition = array_search('repositoryClass', $this->orderedVisibleItems, true);
if ($itemPosition !== null) {
unset($this->orderedVisibleItems[$itemPosition]);
}
$this->repositoryClass = null;
}
}

View File

@ -2,12 +2,16 @@
namespace Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_;
use Nette\Utils\Json;
use Nette\Utils\Strings;
use Rector\DoctrinePhpDocParser\Array_\ArrayItemStaticHelper;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\AbstractDoctrineTagValueNode;
final class ColumnTagValueNode extends AbstractDoctrineTagValueNode
{
/**
* @var string
*/
public const SHORT_NAME = '@ORM\Column';
/**
* @var string|null
*/
@ -19,34 +23,34 @@ final class ColumnTagValueNode extends AbstractDoctrineTagValueNode
private $type;
/**
* @var mixed
* @var mixed|null
*/
private $length;
/**
* @var int
* @var int|null
*/
private $precision;
/**
* @var int
* @var int|null
*/
private $scale;
/**
* @var bool
* @var bool|null
*/
private $unique = false;
private $unique;
/**
* @var bool
* @var bool|null
*/
private $nullable = false;
private $nullable;
/**
* @var mixed[]
* @var mixed[]|null
*/
private $options = [];
private $options;
/**
* @var string|null
@ -56,20 +60,19 @@ final class ColumnTagValueNode extends AbstractDoctrineTagValueNode
/**
* @param mixed[] $options
* @param mixed $type
* @param mixed $length
* @param string[] $orderedVisibleItems
* @param mixed|null $length
*/
public function __construct(
?string $name,
$type,
$length,
int $precision,
int $scale,
bool $unique,
bool $nullable,
array $options,
?string $columnDefinition,
array $orderedVisibleItems
?int $precision = null,
?int $scale = null,
?bool $unique = null,
?bool $nullable = null,
?array $options = null,
?string $columnDefinition = null,
?string $originalContent = null
) {
$this->name = $name;
$this->type = $type;
@ -80,42 +83,64 @@ final class ColumnTagValueNode extends AbstractDoctrineTagValueNode
$this->nullable = $nullable;
$this->options = $options;
$this->columnDefinition = $columnDefinition;
$this->orderedVisibleItems = $orderedVisibleItems;
if ($originalContent !== null) {
$this->orderedVisibleItems = ArrayItemStaticHelper::resolveAnnotationItemsOrder($originalContent);
}
}
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;
if ($this->type !== null) {
$contentItems['type'] = sprintf('type="%s"', $this->type);
}
$contentItems['columnDefinition'] = sprintf('columnDefinition="%s"', $this->columnDefinition);
if ($this->name !== null) {
$contentItems['name'] = sprintf('name="%s"', $this->name);
}
if ($this->length !== null) {
$contentItems['length'] = sprintf('length=%s', $this->length);
}
if ($this->precision !== null) {
$contentItems['precision'] = sprintf('precision=%s', $this->precision);
}
if ($this->scale !== null) {
$contentItems['scale'] = sprintf('scale=%s', $this->scale);
}
if ($this->unique !== null) {
$contentItems['unique'] = sprintf('unique=%s', $this->unique ? 'true' : 'false');
}
if ($this->nullable !== null) {
$contentItems['nullable'] = sprintf('nullable=%s', $this->nullable ? 'true' : 'false');
}
if ($this->options) {
$contentItems['options'] = $this->printArrayItem($this->options, 'options');
}
if ($this->columnDefinition !== null) {
$contentItems['columnDefinition'] = sprintf('columnDefinition="%s"', $this->columnDefinition);
}
return $this->printContentItems($contentItems);
}
/**
* @return mixed
* @return mixed|null
*/
public function getType()
{
return $this->type;
}
public function isNullable(): bool
public function isNullable(): ?bool
{
return $this->nullable;
}

View File

@ -0,0 +1,28 @@
<?php declare(strict_types=1);
namespace Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\AbstractDoctrineTagValueNode;
final class CustomIdGeneratorTagValueNode extends AbstractDoctrineTagValueNode
{
/**
* @var string
*/
public const SHORT_NAME = '@ORM\CustomIdGenerator';
/**
* @var string
*/
private $class;
public function __construct(string $class)
{
$this->class = $class;
}
public function __toString(): string
{
return sprintf('(class="%s")', $this->class);
}
}

View File

@ -0,0 +1,28 @@
<?php declare(strict_types=1);
namespace Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\AbstractDoctrineTagValueNode;
final class GeneratedValueTagValueNode extends AbstractDoctrineTagValueNode
{
/**
* @var string
*/
public const SHORT_NAME = '@ORM\GeneratedValue';
/**
* @var string
*/
private $strategy;
public function __construct(string $strategy)
{
$this->strategy = $strategy;
}
public function __toString(): string
{
return sprintf('(stragety="%s")', $this->strategy);
}
}

View File

@ -0,0 +1,18 @@
<?php declare(strict_types=1);
namespace Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\AbstractDoctrineTagValueNode;
final class IdTagValueNode extends AbstractDoctrineTagValueNode
{
/**
* @var string
*/
public const SHORT_NAME = '@ORM\Id';
public function __toString(): string
{
return '';
}
}

View File

@ -2,14 +2,20 @@
namespace Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_;
use Rector\DoctrinePhpDocParser\Array_\ArrayItemStaticHelper;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\AbstractDoctrineTagValueNode;
final class JoinColumnTagValueNode extends AbstractDoctrineTagValueNode
{
/**
* @var bool
* @var string
*/
private $nullable = false;
public const SHORT_NAME = '@ORM\JoinColumn';
/**
* @var bool|null
*/
private $nullable;
/**
* @var string|null
@ -17,14 +23,14 @@ final class JoinColumnTagValueNode extends AbstractDoctrineTagValueNode
private $name;
/**
* @var string
* @var string|null
*/
private $referencedColumnName;
/**
* @var bool
* @var bool|null
*/
private $unique = false;
private $unique;
/**
* @var string|null
@ -41,20 +47,16 @@ final class JoinColumnTagValueNode extends AbstractDoctrineTagValueNode
*/
private $fieldName;
/**
* @param string[] $orderedVisibleItems
*/
public function __construct(
?string $name,
string $referencedColumnName,
bool $unique,
bool $nullable,
?string $onDelete,
?string $columnDefinition,
?string $fieldName,
array $orderedVisibleItems
?bool $unique = null,
?bool $nullable = null,
?string $onDelete = null,
?string $columnDefinition = null,
?string $fieldName = null,
?string $originalContent = null
) {
$this->orderedVisibleItems = $orderedVisibleItems;
$this->nullable = $nullable;
$this->name = $name;
$this->referencedColumnName = $referencedColumnName;
@ -62,26 +64,63 @@ final class JoinColumnTagValueNode extends AbstractDoctrineTagValueNode
$this->onDelete = $onDelete;
$this->columnDefinition = $columnDefinition;
$this->fieldName = $fieldName;
if ($originalContent !== null) {
$this->orderedVisibleItems = ArrayItemStaticHelper::resolveAnnotationItemsOrder($originalContent);
}
}
public function __toString(): string
{
$contentItems = [];
$contentItems['nullable'] = sprintf('nullable=%s', $this->nullable ? 'true' : 'false');
if ($this->nullable !== null) {
$contentItems['nullable'] = sprintf('nullable=%s', $this->nullable ? 'true' : 'false');
}
$contentItems['name'] = sprintf('name="%s"', $this->name);
$contentItems['referencedColumnName'] = sprintf('referencedColumnName="%s"', $this->referencedColumnName);
$contentItems['unique'] = sprintf('unique=%s', $this->unique ? 'true' : 'false');
$contentItems['nullable'] = sprintf('nullable=%s', $this->nullable ? 'true' : 'false');
$contentItems['onDelete'] = sprintf('onDelete="%s"', $this->onDelete);
$contentItems['columnDefinition'] = sprintf('columnDefinition="%s"', $this->columnDefinition);
$contentItems['fieldName'] = sprintf('fieldName="%s"', $this->fieldName);
if ($this->name !== null) {
$contentItems['name'] = sprintf('name="%s"', $this->name);
}
if ($this->referencedColumnName !== null) {
$contentItems['referencedColumnName'] = sprintf('referencedColumnName="%s"', $this->referencedColumnName);
}
if ($this->unique !== null) {
$contentItems['unique'] = sprintf('unique=%s', $this->unique ? 'true' : 'false');
}
if ($this->nullable !== null) {
$contentItems['nullable'] = sprintf('nullable=%s', $this->nullable ? 'true' : 'false');
}
if ($this->onDelete !== null) {
$contentItems['onDelete'] = sprintf('onDelete="%s"', $this->onDelete);
}
if ($this->columnDefinition !== null) {
$contentItems['columnDefinition'] = sprintf('columnDefinition="%s"', $this->columnDefinition);
}
if ($this->fieldName !== null) {
$contentItems['fieldName'] = sprintf('fieldName="%s"', $this->fieldName);
}
return $this->printContentItems($contentItems);
}
public function isNullable(): bool
public function changeNullable(bool $nullable): void
{
$this->nullable = $nullable;
}
public function changeReferencedColumnName(string $referencedColumnName): void
{
$this->orderedVisibleItems[] = 'referencedColumnName';
$this->referencedColumnName = $referencedColumnName;
}
public function isNullable(): ?bool
{
return $this->nullable;
}

View File

@ -6,6 +6,11 @@ use Rector\DoctrinePhpDocParser\Ast\PhpDoc\AbstractDoctrineTagValueNode;
final class JoinTableTagValueNode extends AbstractDoctrineTagValueNode
{
/**
* @var string
*/
public const SHORT_NAME = '@ORM\JoinTable';
/**
* @var string
*/
@ -17,14 +22,14 @@ final class JoinTableTagValueNode extends AbstractDoctrineTagValueNode
private $schema;
/**
* @var JoinColumnTagValueNode[]
* @var JoinColumnTagValueNode[]|null
*/
private $joinColumns = [];
private $joinColumns;
/**
* @var JoinColumnTagValueNode[]
* @var JoinColumnTagValueNode[]|null
*/
private $inverseJoinColumns = [];
private $inverseJoinColumns;
/**
* @param string[] $orderedVisibleItems
@ -33,10 +38,10 @@ final class JoinTableTagValueNode extends AbstractDoctrineTagValueNode
*/
public function __construct(
string $name,
?string $schema,
array $joinColumns,
array $inverseJoinColumns,
array $orderedVisibleItems
?string $schema = null,
?array $joinColumns = null,
?array $inverseJoinColumns = null,
?array $orderedVisibleItems = null
) {
$this->name = $name;
$this->schema = $schema;
@ -50,17 +55,23 @@ final class JoinTableTagValueNode extends AbstractDoctrineTagValueNode
$contentItems = [];
$contentItems['name'] = sprintf('name="%s"', $this->name);
$contentItems['schema'] = sprintf('schema="%s"', $this->schema);
if ($this->schema !== null) {
$contentItems['schema'] = sprintf('schema="%s"', $this->schema);
}
if ($this->joinColumns) {
$joinColumnsAsString = $this->printTagValueNodesSeparatedByComma($this->joinColumns, '@ORM\JoinColumn');
$joinColumnsAsString = $this->printTagValueNodesSeparatedByComma(
$this->joinColumns,
JoinColumnTagValueNode::SHORT_NAME
);
$contentItems['joinColumns'] = sprintf('joinColumns={%s}', $joinColumnsAsString);
}
if ($this->inverseJoinColumns) {
$inverseJoinColumnsAsString = $this->printTagValueNodesSeparatedByComma(
$this->inverseJoinColumns,
'@ORM\JoinColumn'
JoinColumnTagValueNode::SHORT_NAME
);
$contentItems['inverseJoinColumns'] = sprintf('inverseJoinColumns={%s}', $inverseJoinColumnsAsString);
}

View File

@ -2,7 +2,6 @@
namespace Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_;
use Rector\DoctrinePhpDocParser\Array_\ArrayItemStaticHelper;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\AbstractDoctrineTagValueNode;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\InversedByNodeInterface;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\MappedByNodeInterface;
@ -10,6 +9,11 @@ use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\ToManyTagNodeInterface;
final class ManyToManyTagValueNode extends AbstractDoctrineTagValueNode implements ToManyTagNodeInterface, MappedByNodeInterface, InversedByNodeInterface
{
/**
* @var string
*/
public const SHORT_NAME = '@ORM\ManyToMany';
/**
* @var string
*/
@ -80,8 +84,14 @@ final class ManyToManyTagValueNode extends AbstractDoctrineTagValueNode implemen
$contentItems = [];
$contentItems['targetEntity'] = sprintf('targetEntity="%s"', $this->targetEntity);
$contentItems['mappedBy'] = sprintf('mappedBy="%s"', $this->mappedBy);
$contentItems['inversedBy'] = sprintf('inversedBy="%s"', $this->inversedBy);
if ($this->mappedBy !== null) {
$contentItems['mappedBy'] = sprintf('mappedBy="%s"', $this->mappedBy);
}
if ($this->inversedBy !== null) {
$contentItems['inversedBy'] = sprintf('inversedBy="%s"', $this->inversedBy);
}
if ($this->cascade) {
$contentItems['cascade'] = $this->printArrayItem($this->cascade, 'cascade');
@ -116,21 +126,11 @@ final class ManyToManyTagValueNode extends AbstractDoctrineTagValueNode implemen
public function removeMappedBy(): void
{
$this->orderedVisibleItems = ArrayItemStaticHelper::removeItemFromArray(
$this->orderedVisibleItems,
'mappedBy'
);
$this->mappedBy = null;
}
public function removeInversedBy(): void
{
$this->orderedVisibleItems = ArrayItemStaticHelper::removeItemFromArray(
$this->orderedVisibleItems,
'inversedBy'
);
$this->inversedBy = null;
}
}

View File

@ -2,13 +2,17 @@
namespace Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_;
use Rector\DoctrinePhpDocParser\Array_\ArrayItemStaticHelper;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\AbstractDoctrineTagValueNode;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\InversedByNodeInterface;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\ToOneTagNodeInterface;
final class ManyToOneTagValueNode extends AbstractDoctrineTagValueNode implements ToOneTagNodeInterface, InversedByNodeInterface
{
/**
* @var string
*/
public const SHORT_NAME = '@ORM\ManyToOne';
/**
* @var string
*/
@ -84,11 +88,6 @@ final class ManyToOneTagValueNode extends AbstractDoctrineTagValueNode implement
public function removeInversedBy(): void
{
$this->orderedVisibleItems = ArrayItemStaticHelper::removeItemFromArray(
$this->orderedVisibleItems,
'inversedBy'
);
$this->inversedBy = null;
}
}

View File

@ -2,13 +2,17 @@
namespace Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_;
use Rector\DoctrinePhpDocParser\Array_\ArrayItemStaticHelper;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\AbstractDoctrineTagValueNode;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\MappedByNodeInterface;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\ToManyTagNodeInterface;
final class OneToManyTagValueNode extends AbstractDoctrineTagValueNode implements ToManyTagNodeInterface, MappedByNodeInterface
{
/**
* @var string
*/
public const SHORT_NAME = '@ORM\OneToMany';
/**
* @var string|null
*/
@ -101,11 +105,6 @@ final class OneToManyTagValueNode extends AbstractDoctrineTagValueNode implement
public function removeMappedBy(): void
{
$this->orderedVisibleItems = ArrayItemStaticHelper::removeItemFromArray(
$this->orderedVisibleItems,
'mappedBy'
);
$this->mappedBy = null;
}
}

View File

@ -2,7 +2,6 @@
namespace Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_;
use Rector\DoctrinePhpDocParser\Array_\ArrayItemStaticHelper;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\AbstractDoctrineTagValueNode;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\InversedByNodeInterface;
use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\MappedByNodeInterface;
@ -10,6 +9,11 @@ use Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc\ToOneTagNodeInterface;
final class OneToOneTagValueNode extends AbstractDoctrineTagValueNode implements ToOneTagNodeInterface, MappedByNodeInterface, InversedByNodeInterface
{
/**
* @var string
*/
public const SHORT_NAME = '@ORM\OneToOne';
/**
* @var string
*/
@ -74,8 +78,14 @@ final class OneToOneTagValueNode extends AbstractDoctrineTagValueNode implements
$contentItems = [];
$contentItems['targetEntity'] = sprintf('targetEntity="%s"', $this->targetEntity);
$contentItems['mappedBy'] = sprintf('mappedBy="%s"', $this->mappedBy);
$contentItems['inversedBy'] = sprintf('inversedBy="%s"', $this->inversedBy);
if ($this->mappedBy !== null) {
$contentItems['mappedBy'] = sprintf('mappedBy="%s"', $this->mappedBy);
}
if ($this->inversedBy !== null) {
$contentItems['inversedBy'] = sprintf('inversedBy="%s"', $this->inversedBy);
}
if ($this->cascade) {
$contentItems['cascade'] = $this->printArrayItem($this->cascade, 'cascade');
@ -109,21 +119,11 @@ final class OneToOneTagValueNode extends AbstractDoctrineTagValueNode implements
public function removeInversedBy(): void
{
$this->orderedVisibleItems = ArrayItemStaticHelper::removeItemFromArray(
$this->orderedVisibleItems,
'inversedBy'
);
$this->inversedBy = null;
}
public function removeMappedBy(): void
{
$this->orderedVisibleItems = ArrayItemStaticHelper::removeItemFromArray(
$this->orderedVisibleItems,
'mappedBy'
);
$this->mappedBy = null;
}
}

View File

@ -2,7 +2,7 @@
namespace Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc;
interface RelationTagValueNodeInterface
interface DoctrineRelationTagValueNodeInterface
{
public function getTargetEntity(): ?string;

View File

@ -2,6 +2,6 @@
namespace Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc;
interface ToManyTagNodeInterface extends RelationTagValueNodeInterface
interface ToManyTagNodeInterface extends DoctrineRelationTagValueNodeInterface
{
}

View File

@ -2,6 +2,6 @@
namespace Rector\DoctrinePhpDocParser\Contract\Ast\PhpDoc;
interface ToOneTagNodeInterface extends RelationTagValueNodeInterface
interface ToOneTagNodeInterface extends DoctrineRelationTagValueNodeInterface
{
}

View File

@ -21,6 +21,7 @@ use Rector\Configuration\CurrentNodeProvider;
use Rector\DoctrinePhpDocParser\AnnotationReader\NodeAnnotationReader;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Class_\EntityTagValueNode;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\ColumnTagValueNode;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\IdTagValueNode;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\JoinColumnTagValueNode;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\JoinTableTagValueNode;
use Rector\DoctrinePhpDocParser\Ast\PhpDoc\Property_\ManyToManyTagValueNode;
@ -100,34 +101,38 @@ final class OrmTagParser
Property $property,
string $annotationContent
): ?DoctrineTagNodeInterface {
if ($tag === '@ORM\Column') {
if ($tag === ColumnTagValueNode::SHORT_NAME) {
return $this->createColumnTagValueNode($property, $annotationContent);
}
if ($tag === '@ORM\JoinColumn') {
if ($tag === JoinColumnTagValueNode::SHORT_NAME) {
return $this->createJoinColumnTagValueNode($property, $annotationContent);
}
if ($tag === '@ORM\ManyToMany') {
if ($tag === ManyToManyTagValueNode::SHORT_NAME) {
return $this->createManyToManyTagValueNode($property, $annotationContent);
}
if ($tag === '@ORM\ManyToOne') {
if ($tag === ManyToOneTagValueNode::SHORT_NAME) {
return $this->createManyToOneTagValueNode($property, $annotationContent);
}
if ($tag === '@ORM\OneToOne') {
if ($tag === OneToOneTagValueNode::SHORT_NAME) {
return $this->createOneToOneTagValueNode($property, $annotationContent);
}
if ($tag === '@ORM\OneToMany') {
if ($tag === OneToManyTagValueNode::SHORT_NAME) {
return $this->createOneToManyTagValueNode($property, $annotationContent);
}
if ($tag === '@ORM\JoinTable') {
if ($tag === JoinTableTagValueNode::SHORT_NAME) {
return $this->createJoinTableTagValeNode($property, $annotationContent);
}
if ($tag === IdTagValueNode::SHORT_NAME) {
return $this->createIdTagValueNode();
}
return null;
}
@ -156,7 +161,7 @@ final class OrmTagParser
$column->nullable,
$column->options,
$column->columnDefinition,
$this->resolveAnnotationItemsOrder($annotationContent)
$annotationContent
);
}
@ -319,7 +324,7 @@ final class OrmTagParser
$joinColumn->onDelete,
$joinColumn->columnDefinition,
$joinColumn->fieldName,
$this->resolveAnnotationItemsOrder($annotationContent)
$annotationContent
);
}
@ -327,4 +332,9 @@ final class OrmTagParser
{
return Strings::replace($annotationContent, '#(\s+)\*(\s+)#m', '$1$3');
}
private function createIdTagValueNode(): IdTagValueNode
{
return new IdTagValueNode();
}
}

View File

@ -572,7 +572,7 @@ final class DocBlockManipulator
$phpDocInfo = $this->createPhpDocInfoFromNode($node);
$relationTagValueNode = $phpDocInfo->getRelationTagValueNode();
$relationTagValueNode = $phpDocInfo->getDoctrineRelationTagValueNode();
if ($relationTagValueNode === null) {
return null;
}

View File

@ -37,7 +37,7 @@ final class DoctrineRelationPropertyTypeInferer implements PropertyTypeInfererIn
}
$phpDocInfo = $this->docBlockManipulator->createPhpDocInfoFromNode($property);
$relationTagValueNode = $phpDocInfo->getRelationTagValueNode();
$relationTagValueNode = $phpDocInfo->getDoctrineRelationTagValueNode();
if ($relationTagValueNode === null) {
return [];
}

View File

@ -5,11 +5,13 @@ namespace Rector\Rector\AbstractRector;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use Rector\Doctrine\AbstarctRector\DoctrineTrait;
trait AbstractRectorTrait
{
use AppliedRectorCollectorTrait;
use DocBlockManipulatorTrait;
use DoctrineTrait;
use NodeTypeResolverTrait;
use NameResolverTrait;
use ConstFetchAnalyzerTrait;