A better Record::copy()

Today I’m going to bore you with low-level technical stuff so, if you’ve nothing to do with Symfony or Doctrine you are allowed to leave now.

For the rest of you who love this kind of things, here’s the situation: I’m working on a project which was made some months ago using Symfony 1.2 with Propel and by opening it up again to make some big changes I decided to migrate to Symfony 1.4 and Doctrine (which seems to be the preferred choice for Symfony now).

During the transition I found that the copy() method of Doctrine_Record doesn’t behave at all as its Propel counterpart, and is infact quite buggy: it does duplicate child records (which it should) but is does duplicate parent records as well (which it shouldn’t!) and it doesn’t duplicate referenced records if you don’t load them in advance from the database.

To fix this behavior I defined a new class called BaseDoctrineRecord with my own version of copy(), here it is:

  class BaseDoctrineRecord extends sfDoctrineRecord
  {
    /**
     * Fixes the buggy copy method of Doctrine_Record
     *
     * @param bool $deep
     * @return BaseDoctrineRecord
     */
    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;
  }

Then it was only matter of changing the base class of doctrine auto-generated models to my new BaseDoctrineRecord, and this can be done by adding the following lines to ProjectConfiguration:

  public function configureDoctrine(Doctrine_Manager $manager)
  {
    $options = array('baseClassName' => 'BaseDoctrineRecord');
    sfConfig::set('doctrine_model_builder_options', $options);
  }

And that’s all, simple as that. For me it’s just working perfectly, feel free to reuse the code in your own project.

Hope this can help someone, byeee.

Advertisements