diff --git a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php index 2797a76e9..14344d9f6 100644 --- a/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php +++ b/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php @@ -37,6 +37,8 @@ use Doctrine\ORM\Query; use Doctrine\ORM\UnitOfWork; use Doctrine\ORM\Utility\IdentifierFlattener; use Doctrine\ORM\Utility\PersisterHelper; +use function array_merge; +use function reset; /** * A BasicEntityPersister maps an entity to a single table in a relational database. @@ -459,20 +461,13 @@ class BasicEntityPersister implements EntityPersister ); $targetMapping = $this->em->getClassMetadata($this->class->associationMappings[$idField]['targetEntity']); + $targetType = PersisterHelper::getTypeOfField($targetMapping->identifier[0], $targetMapping, $this->em); - switch (true) { - case (isset($targetMapping->fieldMappings[$targetMapping->identifier[0]])): - $types[] = $targetMapping->fieldMappings[$targetMapping->identifier[0]]['type']; - break; - - case (isset($targetMapping->associationMappings[$targetMapping->identifier[0]])): - $types[] = $targetMapping->associationMappings[$targetMapping->identifier[0]]['type']; - break; - - default: - throw ORMException::unrecognizedField($targetMapping->identifier[0]); + if ($targetType === []) { + throw ORMException::unrecognizedField($targetMapping->identifier[0]); } + $types[] = reset($targetType); } if ($versioned) { diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7062Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7062Test.php new file mode 100644 index 000000000..1d7a2d7cb --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/GH7062Test.php @@ -0,0 +1,212 @@ +setUpEntitySchema( + [ + GH7062Team::class, + GH7062Season::class, + GH7062Ranking::class, + GH7062RankingPosition::class + ] + ); + } + + /** + * @group 7062 + */ + public function testEntityWithAssociationKeyIdentityCanBeUpdated() : void + { + $this->createInitialRankingWithRelatedEntities(); + $this->modifyRanking(); + $this->verifyRanking(); + } + + private function createInitialRankingWithRelatedEntities() : void + { + $team = new GH7062Team(self::TEAM_ID); + $season = new GH7062Season(self::SEASON_ID); + + $season->ranking = new GH7062Ranking($season, [$team]); + + $this->_em->persist($team); + $this->_em->persist($season); + $this->_em->flush(); + $this->_em->clear(); + + foreach ($season->ranking->positions as $position) { + self::assertSame(0, $position->points); + } + } + + private function modifyRanking() : void + { + /** @var GH7062Ranking $ranking */ + $ranking = $this->_em->find(GH7062Ranking::class, self::SEASON_ID); + + foreach ($ranking->positions as $position) { + $position->points += 3; + } + + $this->_em->flush(); + $this->_em->clear(); + } + + private function verifyRanking() : void + { + /** @var GH7062Season $season */ + $season = $this->_em->find(GH7062Season::class, self::SEASON_ID); + self::assertInstanceOf(GH7062Season::class, $season); + + $ranking = $season->ranking; + self::assertInstanceOf(GH7062Ranking::class, $ranking); + + foreach ($ranking->positions as $position) { + self::assertSame(3, $position->points); + } + } +} + +/** + * Simple Entity whose identity is defined through another Entity (Season) + * + * @Entity + * @Table(name="soccer_rankings") + */ +class GH7062Ranking +{ + /** + * @Id + * @OneToOne(targetEntity=GH7062Season::class, inversedBy="ranking") + * @JoinColumn(name="season", referencedColumnName="id") + * + * @var GH7062Season + */ + public $season; + + /** + * @OneToMany(targetEntity=GH7062RankingPosition::class, mappedBy="ranking", cascade={"all"}) + * + * @var Collection|GH7062RankingPosition[] + */ + public $positions; + + /** + * @param GH7062Team[] $teams + */ + public function __construct(GH7062Season $season, array $teams) + { + $this->season = $season; + $this->positions = new ArrayCollection(); + + foreach ($teams as $team) { + $this->positions[] = new GH7062RankingPosition($this, $team); + } + } +} + +/** + * Entity which serves as a identity provider for other entities + * + * @Entity + * @Table(name="soccer_seasons") + */ +class GH7062Season +{ + /** + * @Id + * @Column(type="string") + * + * @var string + */ + public $id; + + /** + * @OneToOne(targetEntity=GH7062Ranking::class, mappedBy="season", cascade={"all"}) + * + * @var GH7062Ranking|null + */ + public $ranking; + + public function __construct(string $id) + { + $this->id = $id; + } +} + +/** + * Entity which serves as a identity provider for other entities + * + * @Entity + * @Table(name="soccer_teams") + */ +class GH7062Team +{ + /** + * @Id + * @Column(type="string") + * + * @var string + */ + public $id; + + public function __construct(string $id) + { + $this->id = $id; + } +} + +/** + * Entity whose identity is defined through two other entities + * + * @Entity + * @Table(name="soccer_ranking_positions") + */ +class GH7062RankingPosition +{ + /** + * @Id + * @ManyToOne(targetEntity=GH7062Ranking::class, inversedBy="positions") + * @JoinColumn(name="season", referencedColumnName="season") + * + * @var GH7062Ranking + */ + public $ranking; + + /** + * @Id + * @ManyToOne(targetEntity=GH7062Team::class) + * @JoinColumn(name="team_id", referencedColumnName="id") + * + * @var GH7062Team + */ + public $team; + + /** + * @Column(type="integer") + * + * @var int + */ + public $points; + + public function __construct(GH7062Ranking $ranking, GH7062Team $team) + { + $this->ranking = $ranking; + $this->team = $team; + $this->points = 0; + } +}