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?