An idea for handling MANY_MANY relationships

I have a project that has quite a few many to many relationships and dialogs where they can be set.

I found it a bit sad that Yii doesn’t have a built in way to automatically save them.

It’s not very pretty since I wasn’t able to encapsulate everything in a behavior.

I had to overwrite the __get __set and __isset methods in the Active Record class

By doing it this way you can easily create checkbox lists like this:




CHtml::activeCheckBoxList($model, 'group_ids',CHtml::listData(Group::model()->findAll(), 'id', 'name') );



without having to make any other modifications

HabtmBehavior:




class HabtmBehavior extends CActiveRecordBehavior{

  function relationshipIds($relation) {

    if(empty($this->owner->primaryKey)) {

      return array();

    }

    $relations = $this->owner->relations();

    list($table,$fk1,$fk2) = $this->parseTableDefinition($relations[$relation][2]);

    $command=$this->owner->dbConnection->createCommand("SELECT $fk2 FROM $table WHERE $fk1 = {$this->owner->primaryKey}");

    $reader=$command->query();

    $ids = array();

    foreach($reader as $row) {

    $ids[] = $row[$fk2];

    }

    return $ids;

  }


  function afterSave() {

    foreach($this->owner->relations() as $rel_name=>$relation) {

      if($relation[0]!==CActiveRecord::MANY_MANY) {

        continue;

      }

      $check_name = strtolower($relation[1]).'_ids';

      if(!isset($this->owner->$check_name)) {

        continue;

      }

      $data = $this->owner->$check_name;

      if(empty($data)) {

        $data = array();

      }

      list($table,$fk1,$fk2) = $this->parseTableDefinition($relation[2]);


      if(!$this->owner->isNewRecord) {

        $command=$this->owner->dbConnection->createCommand("DELETE FROM $table WHERE $fk1 = {$this->owner->primaryKey}");

        $command->execute();

      }

      foreach($data as $id) {

         $command=$this->owner->dbConnection->createCommand("INSERT INTO $table($fk1,$fk2) VALUES({$this->owner->primaryKey},$id) ");

         $command->execute();

      }

      

    }     

  }


  private function parseTableDefinition($table_definition) {

    preg_match('/([^(]+)\(([^,*]+), ([^)]+)\)/',$table_definition,$matches);

    if(count($matches) !== 4) {

      throw new CHttpException('404', "unable to parse $table_definition");

    }

    return array_slice($matches,1);

  }

}



Methods in Active Record Class




  private $_rel_attribs;


  function __isset($name) {

    if(isset($this->_rel_attribs[$name])) {

      return true;

    }

    return parent::__isset($name);

  }


  function __set($name,$value) {

    if($name === 'attributes' && is_array($value)) {

      $all_habtm = array();

      foreach($this->relations() as $rel_name=>$relation) {

        if($relation[0] === self::MANY_MANY) {

          $all_habtm[strtolower($relation[1]).'_ids'] = $rel_name;

        }

      }

      foreach($value as $key=>$val) {

        if(isset($all_habtm[$key])) {

          $this->_rel_attribs[$key] = $val;

        }

      }

    }

    return parent::__set($name, $value);

  }


  function __get($name) {

    if(isset($this->_rel_attribs[$name])) {

      return $this->_rel_attribs[$name];

    }

    $rel = $this->relations();

    foreach($rel as $r=>$data) {

      if($data[0]===self::MANY_MANY && $name==strtolower($data[1]).'_ids') {

        $result = $this->relationshipIds($r);

        $this->_rel_attribs[$name] = $result;

        return $result;

      }

    }

    return parent::__get($name);

  }



What do you guys think?

Nice work. The only thing not pretty is that it relies on some hardcoded naming conventions. In 1.1.x, we will enhance the support for MM relationships.

true

it would be good if the naming conventions could be customized

I might include that and release it as an extension

Can’t wait to see the MM support in 1.1

Hi,

I’m interested in many to many relationships. Is there something in the current release that would be useful?

Regards,

mavs

This extension can help you, but only in the case if you will switch to Yii 1.1 ;)

Thanks! Interesting. I don’t have to switch. I’m already on it ;)

I found other way to get to related data. It’s easy to get it, but hard to save it. Anyways, I’ll be waiting for some Yii native solutions. Maybe in 2.0?