How to re-save the entity as another row in Doctrine 2
Asked Answered
C

5

88

Let's say I have entity $e. Is there any generic way to store it as another row, which would have the same entity data but another primary key?

Why I need this: I'm implementing some sort of Temporal Database schema and instead of updating the row I just need to create another one.

Continue answered 30/1, 2012 at 21:34 Comment(6)
Just off the top of my head (ie untested), have you tried $f = clone $e? You may need to implement the __clone() methodFootlambert
@Phil: cloned entity has the same PK, thus just updates the same row. And even more surprising - spl_object_hash (Doctrine uses it to identify particular instances) are the same for the original and the cloned object even though they contain different dataContinue
@Phil: __clone() wouldn't help either - Doctrine uses $oid = spl_object_hash($entity); and some internal map to get the state of the object. And for both (the original and cloned one) it would be the same - MANAGEDContinue
that's not true. clone $e returns another instance and thus, another spl_object_hash() value.Macadam
@Florian: did you try that or do you think it will be that way? I tried and put a comment based on my observations.Continue
Tried and was sure of that anyway. A clone is a different instance, and until you ask the UnitOfWork/ identityMap to register it, this entity will be considered to be INSERTed.Macadam
F
185

Try cloning and add the following method to your entity

public function __clone() {
    $this->id = null;
}

You may need to detach the entity before persisting it. I don't have my dev machine handy to test this right now.

$f = clone $e;
$em->detach($f);
$em->persist($f);
$em->flush();

Update

Just tried using a simple SQLite demo. You shouldn't need to do anything. The following worked for me without adding a __clone() method or doing anything else out of the ordinary

$new = clone $old;
$em->persist($new);
$em->flush();

Once flushed, the $new entity had a new ID and was saved as a new row in the DB.

I would still null the ID property via the __clone() method as it makes sense from a pure model view.

Update 2

Digging into the Doctrine code, this is because the generated proxy classes implement __clone() with this important line

unset($this->_entityPersister, $this->_identifier);
Footlambert answered 30/1, 2012 at 21:42 Comment(12)
Yep, I think this would work, but I also need to modify the original row a little (and they still have the same spl object hash). So for now the only solution I see is to perform what you proposed just after updating original row: a) update original row b) detach c) insert cloned oneContinue
Oh, I was wrong, spl_object_hash are pretty similar, but still differ for one char. So, yes, this is the probable solution. Will check and report if I got this or anything better workedContinue
@Continue I just tried this myself and Doctrine appears to handle the cloning correctly without adding anything specialFootlambert
When I tried this method, any changes to the original entity were also persisted to the DB, as well as a new record being inserted. This is because the entity manager is still managing it. Even when I detach the original entity, it still persists the changes. I don't know why, or how to successfully get the entity manager to stop managing the original entity (i.e. discard the changes). Neither $em->refresh($old) or $em->detach($old) seem to work...Extraditable
You don't need to set the id to null, Doctrine will take care that for you!Anecdotage
@redA Yes, I know (see last code snippet in my answer). The point I was trying to make was that from a pure POPO model view, you should unset the ID when cloningFootlambert
If you are going to implement __clone() be sure to do so safely, as shown in the documentation otherwise you could easily break things.Thurlow
Please be aware that implementing the __clone() method only works in case of having flat objects without any relations. In particular only data that aren't references to other objects will be copied, all references will be the same as in the original object.Enzymology
@Enzymology correct. I have another answer dealing with that scenarioFootlambert
@ChadwickMeyer In case anyone else has this issue just flush your $new entity. $em->flush($new); NOT $em->flush(); otherwise both entities will save with the new data.Avulsion
@Yep_It's_Me here's an unbroken (atm) link to doctrine documentation of how to implement clone or wake upInset
@Josh on Symfony 5.4.2 ObjectManager::flush does not accept any arguments. Thus in this specific version, original entity is still persisted in database.Precocity
I
1

clone and detach worked for me.Symfony version 5.4 does not accept any arguments for flush()

    $new = clone $discount;
    $new->setId(null);
    $discountRequest = new DiscountRequest();
    $discountRequest->setDiscount($new);
    
    $discountRequest->setOldDiscount($discount->getId());
    $entityManager->persist($discountRequest);
    $entityManager->detach($discount);
    $entityManager->flush();
Isom answered 20/6, 2023 at 15:45 Comment(0)
L
0

I just do:

/**
 * __clone
 *
 * @return void
 */
public function __clone()
{
    $this->id = null;
}

More details here https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/cookbook/implementing-wakeup-or-clone.html

Lucier answered 2/10, 2020 at 18:32 Comment(0)
L
0

Setting id to null inside the clone method returned me an error; I solved it using unset() instead

public function __clone() 
{
    unset($this->id);
}
Linden answered 24/2 at 10:23 Comment(1)
"returned me an error" --- saying something "returns an error" without any more details is not helpful.Continue
C
-2

Copying the data in a new Object of the same class and persisting it will do. Keep it simple!

Charming answered 14/1, 2021 at 18:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.