Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#6759 showstopper one-to-one inverse not being loaded with correct identifier restrictions when defining joinColumn #6760

10 changes: 4 additions & 6 deletions lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,8 @@ public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifie
$sourceClass = $this->em->getClassMetadata($assoc['sourceEntity']);
$owningAssoc = $targetClass->getAssociationMapping($assoc['mappedBy']);

$computedIdentifier = [];

// TRICKY: since the association is specular source and target are flipped
foreach ($owningAssoc['targetToSourceKeyColumns'] as $sourceKeyColumn => $targetKeyColumn) {
if ( ! isset($sourceClass->fieldNames[$sourceKeyColumn])) {
Expand All @@ -800,15 +802,11 @@ public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifie
);
}

// unset the old value and set the new sql aliased value here. By definition
// unset($identifier[$targetKeyColumn] works here with how UnitOfWork::createEntity() calls this method.
$identifier[$targetClass->getFieldForColumn($targetKeyColumn)] =
$computedIdentifier[$targetClass->getFieldForColumn($targetKeyColumn)] =
$sourceClass->reflFields[$sourceClass->fieldNames[$sourceKeyColumn]]->getValue($sourceEntity);

unset($identifier[$targetKeyColumn]);
}

$targetEntity = $this->load($identifier, null, $assoc);
$targetEntity = $this->load($computedIdentifier, null, $assoc);

if ($targetEntity !== null) {
$targetClass->setFieldValue($targetEntity, $assoc['mappedBy'], $sourceEntity);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\OneToOneInverseSideLoad;

use Doctrine\ORM\Mapping as ORM;

/**
* @Entity()
* @Table(name="one_to_one_inverse_side_load_inverse")
*/
class InverseSide
{
/**
* @Id()
* @Column(type="string")
* @GeneratedValue(strategy="NONE")
*/
public $id;

/**
* @OneToOne(targetEntity=OwningSide::class, mappedBy="inverse")
*/
public $owning;
}
29 changes: 29 additions & 0 deletions tests/Doctrine/Tests/Models/OneToOneInverseSideLoad/OwningSide.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\OneToOneInverseSideLoad;

use Doctrine\ORM\Mapping as ORM;

/**
* @Entity()
* @Table(name="one_to_one_inverse_side_load_owning")
*/
class OwningSide
{
/**
* @Id()
* @Column(type="string")
* @GeneratedValue(strategy="NONE")
*/
public $id;

/**
* Owning side
*
* @OneToOne(targetEntity=InverseSide::class, inversedBy="owning")
* @JoinColumn(nullable=false, name="inverse")
*/
public $inverse;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\ORM\Functional;

use Doctrine\ORM\Tools\ToolsException;
use Doctrine\Tests\Models\OneToOneInverseSideLoad\InverseSide;
use Doctrine\Tests\Models\OneToOneInverseSideLoad\OwningSide;
use Doctrine\Tests\OrmFunctionalTestCase;

class OneToOneInverseSideLoadAfterDqlQueryTest extends OrmFunctionalTestCase
{
protected function setUp()
{
parent::setUp();

try {
$this->_schemaTool->createSchema([
$this->_em->getClassMetadata(OwningSide::class),
$this->_em->getClassMetadata(InverseSide::class),
]);
} catch(ToolsException $e) {
// ignored
}
}

/**
* @group #6759
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the other test cases don't have the #, I'd remove it for consistency.

*/
public function testInverseSideOneToOneLoadedAfterDqlQuery(): void
{
$owner = new OwningSide();
$inverse = new InverseSide();

$owner->id = 'owner';
$inverse->id = 'inverse';
$owner->inverse = $inverse;
$inverse->owning = $owner;

$this->_em->persist($owner);
$this->_em->persist($inverse);
$this->_em->flush();
$this->_em->clear();

/* @var $fetchedInverse InverseSide */
$fetchedInverse = $this
->_em
->createQueryBuilder()
->select('inverse')
->from(InverseSide::class, 'inverse')
->andWhere('inverse.id = :id')
->setParameter('id', 'inverse')
->getQuery()
->getSingleResult();

self::assertInstanceOf(InverseSide::class, $fetchedInverse);
self::assertInstanceOf(OwningSide::class, $fetchedInverse->owning);

$this->assertSQLEquals(
'select o0_.id as id_0 from one_to_one_inverse_side_load_inverse o0_ where o0_.id = ?',
$this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery - 1]['sql']
);

$this->assertSQLEquals(
'select t0.id as id_1, t0.inverse as inverse_2 from one_to_one_inverse_side_load_owning t0 WHERE t0.inverse = ?',
$this->_sqlLoggerStack->queries[$this->_sqlLoggerStack->currentQuery]['sql']
);
}
}