views:

192

answers:

1

I want to make a deep copy/clone of a doctrine record in a symfony project. The existing copy($deep)-method doesn't work properly with $deep=true.

For an example let's have a look at a classroom lesson. This lesson has a start and end date and between them there are several breaks. This classroom is in a buildung.

lesson-break is a one-to-many relationship, so a lot of breaks could be inside a lesson. lesson-building is a many-to-one relationship, so a lesson could only be in ONE Building.

If I want to make a copy of the room the breaks should be copied also. The building should stay the same (no copy here).

I found some examples on the web which create a PHP class which extends from the sfDoctrineRecord and overrides the copy-method.

What I tried was:

class BaseDoctrineRecord extends sfDoctrineRecord {
    public function copy($deep = false) {
        $ret = parent::copy(false);
        if (!$deep)
            return $ret;

        // ensure to have loaded all references (unlike Doctrine_Record)
        foreach ($this->getTable()->getRelations() as $name => $relation) {
            // ignore ONE sides of relationships
            if ($relation->getType() == Doctrine_Relation::MANY) {
                if (empty($this->$name))
                    $this->loadReference($name);

                // do the deep copy
                foreach ($this->$name as $record)
                    $ret->{$name}[] = $record->copy($deep);
            }
        }
        return $ret;
    }
}

Now this causes in a failure: Doctrine_Connection_Mysql_Exception: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '2-1' for key 'PRIMARY'

So I need to "null" the id of the new record ($ret) because this should be a new record. Where and how could/should I do it?

UPDATE: The error is fixed with following code: `class BaseDoctrineRecord extends sfDoctrineRecord { public function copy($deep = false) { $ret = parent::copy(false);

    if($this->Table->getIdentifierType() === Doctrine_Core::IDENTIFIER_AUTOINC) {
        $id = $this->Table->getIdentifier();
        $this->_data[$id] = null;
    }

    if(!$deep) {
        return $ret;
    }

    // ensure to have loaded all references (unlike Doctrine_Record)
    foreach($this->getTable()->getRelations() as $name => $relation) {
        // ignore ONE sides of relationships
        if($relation->getType() == Doctrine_Relation::MANY) {
            if(empty($this->$name)) {
                $this->loadReference($name);
            }

            // do the deep copy
            foreach($this->$name as $record) {
                $ret->{$name}[] = $record->copy($deep);
            }
        }
    }
    return $ret;
}

}`

But it doesn't work well. In the DoctrineCollection lesson->Breaks all new breaks are fine. But they aren't saved in the database. I want to copy a lesson and add 7 days to it's time: foreach($new_shift->Breaks as $break) { $break->start_at = $this->addOneWeek($break->start_at); $break->end_at = $this->addOneWeek($break->end_at); $break->save(); }

So as you see, the breaks are saved, but it seems they are not in the db.

A: 

Hi. Thomas the Tank Engine is blasting in my ear at the moment so I can't really concentrate on the finer points of your question, but it sounds familiar. Does this answer your question?

http://stackoverflow.com/questions/2357498/copy-a-doctrine-object-with-all-relations/2663837#2663837

Basically the Doctrine_Record::link() method is your friend :)

Darragh
actually... I did read your question wrong. the answer I gave is for when you want to copy a record, but retain the original references, i.e: *not* copy the related records.I'm willing to bet you're using Doctrine 1.0. The copy(deep) issue is resolved (along with many other useful stuff like synchronizeWithArray()) in Doctrine 1.2. When you deep copy in Doctrine 1.2 it will copy and save the references also no problem.I don't know if upgrading to 1.2 is an option for you though...
Darragh
Actually I got version 1.4.4 :)
hering
haha. oh right... never mind :D
Darragh