87

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.

zerkms
  • 249,484
  • 69
  • 436
  • 539
  • Just off the top of my head (ie untested), have you tried `$f = clone $e`? You may need to implement the `__clone()` method – Phil Jan 30 '12 at 21:36
  • 4
    @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 data – zerkms Jan 30 '12 at 21:39
  • @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 - `MANAGED` – zerkms Jan 30 '12 at 21:43
  • 1
    that's not true. clone $e returns another instance and thus, another spl_object_hash() value. – Florian Klein Aug 22 '13 at 12:52
  • @Florian: did you try that or do you think it will be that way? I tried and put a comment based on my observations. – zerkms Aug 22 '13 at 20:55
  • 1
    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. – Florian Klein Aug 23 '13 at 07:28

4 Answers4

183

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);
jake stayman
  • 1,687
  • 13
  • 22
Phil
  • 157,677
  • 23
  • 242
  • 245
  • 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 one – zerkms Jan 30 '12 at 21:52
  • 1
    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 worked – zerkms Jan 30 '12 at 22:41
  • @zerkms I just tried this myself and Doctrine appears to handle the cloning correctly without adding anything special – Phil Jan 30 '12 at 22:58
  • 9
    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... – Chadwick Meyer Jul 14 '14 at 21:37
  • You don't need to set the id to null, Doctrine will take care that for you! – notnotundefined Sep 11 '14 at 10:36
  • @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 cloning – Phil Sep 11 '14 at 23:37
  • 2
    If you are going to implement `__clone()` be sure to [do so safely, as shown in the documentation](http://doctrine-orm.readthedocs.org/en/latest/cookbook/implementing-wakeup-or-clone.html) otherwise you could easily break things. – Yep_It's_Me Feb 03 '15 at 05:16
  • Thanks, this helped me a lot ! – Marco Bax Apr 08 '16 at 08:05
  • 2
    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. – barbieswimcrew Aug 11 '16 at 06:49
  • 3
    @barbieswimcrew correct. I have [another answer](http://stackoverflow.com/a/9089445/283366) dealing with that scenario – Phil Aug 11 '16 at 07:00
  • 3
    @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. – Jackson Jan 11 '19 at 18:12
  • 2
    @Yep_It's_Me here's an unbroken (atm) link to doctrine documentation of [how to implement clone or wake up](https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/cookbook/implementing-wakeup-or-clone.html) – Dimitry K Nov 04 '19 at 22:12
  • @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. – Droom Nov 21 '22 at 19:43
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();
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

user1077915
  • 828
  • 1
  • 12
  • 26
0

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

Mike Smit
  • 71
  • 4