diff --git a/src/Doctrine/BaseRelation.php b/src/Doctrine/BaseRelation.php index 21e06fbda..3e78431cd 100644 --- a/src/Doctrine/BaseRelation.php +++ b/src/Doctrine/BaseRelation.php @@ -19,6 +19,7 @@ abstract class BaseRelation private $propertyName; private $targetClassName; private $targetPropertyName; + private $isSelfReferencing = false; private $mapInverseRelation = true; abstract public function isOwning(): bool; @@ -59,6 +60,18 @@ public function setTargetPropertyName($targetPropertyName) return $this; } + public function isSelfReferencing(): bool + { + return $this->isSelfReferencing; + } + + public function setIsSelfReferencing(bool $isSelfReferencing) + { + $this->isSelfReferencing = $isSelfReferencing; + + return $this; + } + public function getMapInverseRelation(): bool { return $this->mapInverseRelation; diff --git a/src/Doctrine/EntityRelation.php b/src/Doctrine/EntityRelation.php index ccf1b9a4e..e44e5a304 100644 --- a/src/Doctrine/EntityRelation.php +++ b/src/Doctrine/EntityRelation.php @@ -33,6 +33,8 @@ final class EntityRelation private $isNullable = false; + private $isSelfReferencing = false; + private $orphanRemoval = false; private $mapInverseRelation = true; @@ -50,6 +52,7 @@ public function __construct(string $type, string $owningClass, string $inverseCl $this->type = $type; $this->owningClass = $owningClass; $this->inverseClass = $inverseClass; + $this->isSelfReferencing = $owningClass === $inverseClass; } public function setOwningProperty(string $owningProperty) @@ -95,6 +98,7 @@ public function getOwningRelation() ->setTargetClassName($this->inverseClass) ->setTargetPropertyName($this->inverseProperty) ->setIsNullable($this->isNullable) + ->setIsSelfReferencing($this->isSelfReferencing) ->setMapInverseRelation($this->mapInverseRelation) ; break; @@ -104,6 +108,7 @@ public function getOwningRelation() ->setTargetClassName($this->inverseClass) ->setTargetPropertyName($this->inverseProperty) ->setIsOwning(true)->setMapInverseRelation($this->mapInverseRelation) + ->setIsSelfReferencing($this->isSelfReferencing) ; break; case self::ONE_TO_ONE: @@ -113,6 +118,7 @@ public function getOwningRelation() ->setTargetPropertyName($this->inverseProperty) ->setIsNullable($this->isNullable) ->setIsOwning(true) + ->setIsSelfReferencing($this->isSelfReferencing) ->setMapInverseRelation($this->mapInverseRelation) ; break; @@ -130,6 +136,7 @@ public function getInverseRelation() ->setTargetClassName($this->owningClass) ->setTargetPropertyName($this->owningProperty) ->setOrphanRemoval($this->orphanRemoval) + ->setIsSelfReferencing($this->isSelfReferencing) ; break; case self::MANY_TO_MANY: @@ -138,6 +145,7 @@ public function getInverseRelation() ->setTargetClassName($this->owningClass) ->setTargetPropertyName($this->owningProperty) ->setIsOwning(false) + ->setIsSelfReferencing($this->isSelfReferencing) ; break; case self::ONE_TO_ONE: @@ -147,6 +155,7 @@ public function getInverseRelation() ->setTargetPropertyName($this->owningProperty) ->setIsNullable($this->isNullable) ->setIsOwning(false) + ->setIsSelfReferencing($this->isSelfReferencing) ; break; default: @@ -184,6 +193,11 @@ public function isNullable(): bool return $this->isNullable; } + public function isSelfReferencing(): bool + { + return $this->isSelfReferencing; + } + public function getMapInverseRelation(): bool { return $this->mapInverseRelation; diff --git a/src/Maker/MakeEntity.php b/src/Maker/MakeEntity.php index 7454a5245..9b02c10cd 100644 --- a/src/Maker/MakeEntity.php +++ b/src/Maker/MakeEntity.php @@ -201,8 +201,13 @@ public function generate(InputInterface $input, ConsoleStyle $io, Generator $gen } elseif ($newField instanceof EntityRelation) { // both overridden below for OneToMany $newFieldName = $newField->getOwningProperty(); - $otherManipulatorFilename = $this->getPathOfClass($newField->getInverseClass()); - $otherManipulator = $this->createClassManipulator($otherManipulatorFilename, $io, $overwrite); + if ($newField->isSelfReferencing()) { + $otherManipulatorFilename = $entityPath; + $otherManipulator = $manipulator; + } else { + $otherManipulatorFilename = $this->getPathOfClass($newField->getInverseClass()); + $otherManipulator = $this->createClassManipulator($otherManipulatorFilename, $io, $overwrite); + } switch ($newField->getType()) { case EntityRelation::MANY_TO_ONE: if ($newField->getOwningClass() === $entityClassDetails->getFullName()) { diff --git a/src/Util/ClassSourceManipulator.php b/src/Util/ClassSourceManipulator.php index 05fde0b51..a51316985 100644 --- a/src/Util/ClassSourceManipulator.php +++ b/src/Util/ClassSourceManipulator.php @@ -447,7 +447,7 @@ private function addSingularRelation(BaseRelation $relation) private function addCollectionRelation(BaseCollectionRelation $relation) { - $typeHint = $this->addUseStatementIfNecessary($relation->getTargetClassName()); + $typeHint = $relation->isSelfReferencing() ? 'self' : $this->addUseStatementIfNecessary($relation->getTargetClassName()); $arrayCollectionTypeHint = $this->addUseStatementIfNecessary(ArrayCollection::class); $collectionTypeHint = $this->addUseStatementIfNecessary(Collection::class); diff --git a/tests/Maker/FunctionalTest.php b/tests/Maker/FunctionalTest.php index c01429ea9..af1038973 100644 --- a/tests/Maker/FunctionalTest.php +++ b/tests/Maker/FunctionalTest.php @@ -805,6 +805,36 @@ public function getCommandEntityTests() ->setRequiredPhpVersion(70100) ]; + yield 'entity_many_to_one_self_referencing' => [MakerTestDetails::createTest( + $this->getMakerInstance(MakeEntity::class), + [ + // entity class name + 'User', + // field name + 'guardian', + // add a relationship field + 'relation', + // the target entity + 'User', + // relation type + 'ManyToOne', + // nullable + 'y', + // do you want to generate an inverse relation? (default to yes) + '', + // field name on opposite side + 'dependants', + // orphanRemoval (default to no) + '', + // finish adding fields + '', + ]) + ->setFixtureFilesPath(__DIR__.'/../fixtures/MakeEntitySelfReferencing') + ->configureDatabase() + ->updateSchemaAfterCommand() + ->setRequiredPhpVersion(70100) + ]; + yield 'entity_one_to_many_simple' => [MakerTestDetails::createTest( $this->getMakerInstance(MakeEntity::class), [ diff --git a/tests/fixtures/MakeEntitySelfReferencing/src/Entity/User.php b/tests/fixtures/MakeEntitySelfReferencing/src/Entity/User.php new file mode 100644 index 000000000..e64f975d9 --- /dev/null +++ b/tests/fixtures/MakeEntitySelfReferencing/src/Entity/User.php @@ -0,0 +1,53 @@ +id; + } + + public function getFirstName() + { + return $this->firstName; + } + + public function setFirstName(?string $firstName) + { + $this->firstName = $firstName; + } + + public function getCreatedAt(): ?\DateTimeInterface + { + return $this->createdAt; + } + + public function setCreatedAt(?\DateTimeInterface $createdAt) + { + $this->createdAt = $createdAt; + } +} diff --git a/tests/fixtures/MakeEntitySelfReferencing/tests/GeneratedEntityTest.php b/tests/fixtures/MakeEntitySelfReferencing/tests/GeneratedEntityTest.php new file mode 100644 index 000000000..7e774f62c --- /dev/null +++ b/tests/fixtures/MakeEntitySelfReferencing/tests/GeneratedEntityTest.php @@ -0,0 +1,49 @@ +getContainer() + ->get('doctrine') + ->getManager(); + + $em->createQuery('DELETE FROM App\\Entity\\User u')->execute(); + + $user = new User(); + // check that the constructor was instantiated properly + $this->assertInstanceOf(ArrayCollection::class, $user->getDependants()); + // set existing field + $user->setFirstName('Ryan'); + $em->persist($user); + + $ward = new User(); + $ward->setFirstName('Tim'); + $ward->setGuardian($user); + $em->persist($ward); + + // set via the inverse side + $ward2 = new User(); + $ward2->setFirstName('Fabien'); + $user->addDependant($ward2); + $em->persist($ward2); + + $em->flush(); + $em->refresh($user); + + $actualUser = $em->getRepository(User::class) + ->findAll(); + + $this->assertCount(3, $actualUser); + $this->assertCount(2, $actualUser[0]->getDependants()); + } +}