Changes to one-to-one relations get reversed when the inverse side is fetched again using findBy #5652

Closed
opened 2026-01-22 15:13:46 +01:00 by admin · 5 comments
Owner

Originally created by @johanderuijter on GitHub (Aug 21, 2017).

Originally assigned to: @johanderuijter on GitHub.

I found some unexpected behaviour with one to one relations. I couldn't find any information on it, so i hope it's ok i opened an issue about it here.

My problem is the following:
I have 2 entities, with a bidirectional one to one relation. When i remove the relation between them (e.g. set the properties to null) the relation is removed. However, when the inverse side is fetched again (after setting the relation to null, but before a flush) the relation reappears. This is not the case for 'regular' properties which remain changed.

In my particular use case, this is a problem as the symfony unique constraint uses the repositories findBy to check if an entity already exists. In doing so, it restores the relation.

Here is a code example based on the bidirectional one to one mapping from the docs:

Given the following two entities:

/**
 * @Entity
 */
class Customer
{
    /**
     * @Id
     * @Column(type="integer")
     * @GeneratedValue
     */
    private $id;

    /**
     * @Column(type="string")
     */
    private $name;

    /**
     * @OneToOne(targetEntity="Cart", mappedBy="customer", cascade={"all"})
     */
    private $cart;

    public function setName(string $name): void
    {
        $this->name = $name;
    }

    public function setCart(?Cart $cart): void
    {
        $prevCart = $this->cart;

        if ($prevCart) {
            $prevCart->setCustomer(null);
        }

        if ($cart) {
            $cart->setCustomer($this);
        }

        $this->cart = $cart;
    }

    public function getCart(): ?Cart
    {
        return $this->cart;
    }

    // ...
}

/**
 * @Entity
 */
class Cart
{
    /**
     * @Id
     * @Column(type="integer")
     * @GeneratedValue
     */
    private $id;

    /**
     * @OneToOne(targetEntity="Customer", inversedBy="cart")
     * @JoinColumn(name="customer_id", referencedColumnName="id")
     */
    private $customer;

    // ...
}

with the following schema:

CREATE TABLE Cart (id INTEGER NOT NULL, customer_id INTEGER DEFAULT NULL, PRIMARY KEY(id));
CREATE UNIQUE INDEX UNIQ_AB9127899395C3F3 ON Cart (customer_id);
CREATE TABLE Customer (id INTEGER NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id));

And the following code:

// @see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/getting-started.html#obtaining-the-entitymanager
require_once "bootstrap.php";

$initialCustomer = new Customer();
$initialCustomer->setName('Initial Name');
$initialCustomer->setCart(new Cart());

$entityManager->persist($initialCustomer);
$entityManager->flush();

$repository = $entityManager->getRepository(Customer::class);

// Retrieve customer from the database.
$customer = $repository->find($initialCustomer->getId());
$cart = $customer->getCart();

// Current state:
// $name = Initial Name
// $cart = Cart
var_dump($customer);

// Current state:
// $customer = Customer
var_dump($cart);

// Update the customer. Change both the property and the relation
$customer->setName('Updated');
$customer->setCart(null);

// Current state:
// $name = Updated
// $cart = null
var_dump($customer);

// Current state:
// $customer = null
var_dump($cart);

// Call find* on the repository
// In my particular use case, this is called by a symfony unique constraint on one of the properties.
$repository->findByName('Initial Name');

// Current state:
// $name = Updated < The name remains changed
// $cart = Cart < The cart from the initial customer is back
var_dump($customer);

// Current state:
// $customer = Customer < The customer from the initial cart is back
var_dump($cart);

tested on latest stable (doctrine/orm v2.5.10)

As I could not find much information on this, I don't know if this is a bug, know limitation or even intended behaviour?

If more information is needed, please let me know.

Originally created by @johanderuijter on GitHub (Aug 21, 2017). Originally assigned to: @johanderuijter on GitHub. I found some unexpected behaviour with one to one relations. I couldn't find any information on it, so i hope it's ok i opened an issue about it here. My problem is the following: I have 2 entities, with a bidirectional one to one relation. When i remove the relation between them (e.g. set the properties to null) the relation is removed. However, when the inverse side is fetched again (after setting the relation to null, but before a flush) the relation reappears. This is not the case for 'regular' properties which remain changed. In my particular use case, this is a problem as the symfony unique constraint uses the repositories findBy to check if an entity already exists. In doing so, it restores the relation. Here is a code example based on the [bidirectional one to one mapping from the docs](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#one-to-one-bidirectional): Given the following two entities: ``` php /** * @Entity */ class Customer { /** * @Id * @Column(type="integer") * @GeneratedValue */ private $id; /** * @Column(type="string") */ private $name; /** * @OneToOne(targetEntity="Cart", mappedBy="customer", cascade={"all"}) */ private $cart; public function setName(string $name): void { $this->name = $name; } public function setCart(?Cart $cart): void { $prevCart = $this->cart; if ($prevCart) { $prevCart->setCustomer(null); } if ($cart) { $cart->setCustomer($this); } $this->cart = $cart; } public function getCart(): ?Cart { return $this->cart; } // ... } /** * @Entity */ class Cart { /** * @Id * @Column(type="integer") * @GeneratedValue */ private $id; /** * @OneToOne(targetEntity="Customer", inversedBy="cart") * @JoinColumn(name="customer_id", referencedColumnName="id") */ private $customer; // ... } ``` with the following schema: ```sql CREATE TABLE Cart (id INTEGER NOT NULL, customer_id INTEGER DEFAULT NULL, PRIMARY KEY(id)); CREATE UNIQUE INDEX UNIQ_AB9127899395C3F3 ON Cart (customer_id); CREATE TABLE Customer (id INTEGER NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id)); ``` And the following code: ```php // @see http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/getting-started.html#obtaining-the-entitymanager require_once "bootstrap.php"; $initialCustomer = new Customer(); $initialCustomer->setName('Initial Name'); $initialCustomer->setCart(new Cart()); $entityManager->persist($initialCustomer); $entityManager->flush(); $repository = $entityManager->getRepository(Customer::class); // Retrieve customer from the database. $customer = $repository->find($initialCustomer->getId()); $cart = $customer->getCart(); // Current state: // $name = Initial Name // $cart = Cart var_dump($customer); // Current state: // $customer = Customer var_dump($cart); // Update the customer. Change both the property and the relation $customer->setName('Updated'); $customer->setCart(null); // Current state: // $name = Updated // $cart = null var_dump($customer); // Current state: // $customer = null var_dump($cart); // Call find* on the repository // In my particular use case, this is called by a symfony unique constraint on one of the properties. $repository->findByName('Initial Name'); // Current state: // $name = Updated < The name remains changed // $cart = Cart < The cart from the initial customer is back var_dump($customer); // Current state: // $customer = Customer < The customer from the initial cart is back var_dump($cart); ``` tested on latest stable (doctrine/orm v2.5.10) As I could not find much information on this, I don't know if this is a bug, know limitation or even intended behaviour? If more information is needed, please let me know.
admin added the Improvement label 2026-01-22 15:13:46 +01:00
admin closed this issue 2026-01-22 15:13:46 +01:00
Author
Owner

@Ocramius commented on GitHub (Aug 25, 2017):

@johanderuijter does the same thing happen when using DQL? Also, can you try removing the cascade operation and seeing if this affects the issue?

@Ocramius commented on GitHub (Aug 25, 2017): @johanderuijter does the same thing happen when using DQL? Also, can you try removing the cascade operation and seeing if this affects the issue?
Author
Owner

@Ocramius commented on GitHub (Aug 25, 2017):

@johanderuijter one thing that would help a lot is reporting this as a PR containing a failing test. See 61404e2d6d/tests/Doctrine/Tests/ORM/Functional/Ticket for examples.

@Ocramius commented on GitHub (Aug 25, 2017): @johanderuijter one thing that would help a lot is reporting this as a PR containing a failing test. See https://github.com/doctrine/doctrine2/tree/61404e2d6d71a6d46331f2fa1771dad121e49058/tests/Doctrine/Tests/ORM/Functional/Ticket for examples.
Author
Owner

@johanderuijter commented on GitHub (Aug 25, 2017):

I'll try to create a PR with failing tests later today. Are there any naming conventions i should be aware of? The examples seem to be all over the place with regards to that.

@johanderuijter commented on GitHub (Aug 25, 2017): I'll try to create a PR with failing tests later today. Are there any naming conventions i should be aware of? The examples seem to be all over the place with regards to that.
Author
Owner

@Ocramius commented on GitHub (Aug 25, 2017):

@johanderuijter I wouldn't worry about it - can be adapted when at review stage.

@Ocramius commented on GitHub (Aug 25, 2017): @johanderuijter I wouldn't worry about it - can be adapted when at review stage.
Author
Owner

@johanderuijter commented on GitHub (Aug 25, 2017):

Created a PR with the failing test case.

Using either find or a DQL query work as expected (e.g. the relations remains null after). The cascade operation also seems to have no effect on this.

The relation only reappears when findBy or findOneBy is used and the entity is part of the result.

@johanderuijter commented on GitHub (Aug 25, 2017): Created a PR with the failing test case. Using either `find` or a DQL query work as expected (e.g. the relations remains null after). The cascade operation also seems to have no effect on this. The relation only reappears when `findBy` or `findOneBy` is used and the entity is part of the result.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5652