/** * @param array $data attributes and relations. * @param CActiveRecord $owner for internal needs. * * @return boolean whether the saving succeeds. */ private function internalSave($data = null, $owner = null) { if ($owner === null) { $owner = $this->getOwner(); } $db = $owner->getDbConnection(); $extTransFlag = $db->getCurrentTransaction(); if ($extTransFlag === null) { $transaction = $db->beginTransaction(); } try { if ($data === null) { $attributes = null; $newData = array(); } else { $attributeNames = $owner->attributeNames(); $attributes = array_intersect($data, $attributeNames); if ($attributes === array()) { $attributes = null; } $newData = array_diff($data, $attributeNames); } $ownerTableSchema = $owner->getTableSchema(); $builder = $owner->getCommandBuilder(); $schema = $builder->getSchema(); $relations = $owner->getMetaData()->relations; $queue = array(); foreach ($newData as $name => $data) { if (!is_array($data)) { $name = $data; $data = null; } if (!$owner->hasRelated($name)) { continue; } $relationClass = get_class($relations[$name]); $relatedClass = $relations[$name]->className; if ($relationClass === CActiveRecord::BELONGS_TO) { $related = $owner->getRelated($name); if ($data !== null) { $this->internalSave($data, $related); } else { $related->getIsNewRecord() ? $related->insert() : $related->update(); } $relatedTableSchema = CActiveRecord::model($relatedClass)->getTableSchema(); $fks = preg_split('/\\s*,\\s*/', $relations[$name]->foreignKey, -1, PREG_SPLIT_NO_EMPTY); foreach ($fks as $i => $fk) { if (!isset($ownerTableSchema->columns[$fk])) { throw new CDbException(Yii::t('yiiext', 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key "{key}". There is no such column in the table "{table}".', array('{class}' => get_class($owner), '{relation}' => $relations[$name], '{key}' => $fk, '{table}' => $ownerTableSchema->name))); } if (isset($ownerTableSchema->foreignKeys[$fk]) && $schema->compareTableNames($relatedTableSchema->rawName, $ownerTableSchema->foreignKeys[$fk][0])) { $pk = $ownerTableSchema->foreignKeys[$fk][1]; } else { if (is_array($relatedTableSchema->primaryKey)) { $pk = $relatedTableSchema->primaryKey[$i]; } else { $pk = $relatedTableSchema->primaryKey; } } $owner->{$fk} = $related->{$pk}; } } else { $queue[] = array($relationClass, $relatedClass, $relations[$name]->foreignKey, $name, $data); } } if (!($owner->getIsNewRecord() ? $owner->insert($attributes) : $owner->update($attributes))) { return false; } foreach ($queue as $pack) { list($relationClass, $relatedClass, $foreignKey, $name, $data) = $pack; $related = $owner->getRelated($name); switch ($relationClass) { case CActiveRecord::HAS_ONE: $relatedTableSchema = CActiveRecord::model($relatedClass)->getTableSchema(); $fks = preg_split('/\\s*,\\s*/', $foreignKey, -1, PREG_SPLIT_NO_EMPTY); foreach ($fks as $i => $fk) { if (!isset($relatedTableSchema->columns[$fk])) { throw new CDbException(Yii::t('yiiext', 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key "{key}". There is no such column in the table "{table}".', array('{class}' => get_class($owner), '{relation}' => $name, '{key}' => $fk, '{table}' => $relatedTableSchema->name))); } if (isset($relatedTableSchema->foreignKeys[$fk]) && $schema->compareTableNames($ownerTableSchema->rawName, $relatedTableSchema->foreignKeys[$fk][0])) { $pk = $relatedTableSchema->foreignKeys[$fk][1]; } else { if (is_array($ownerTableSchema->primaryKey)) { $pk = $ownerTableSchema->primaryKey[$i]; } else { $pk = $ownerTableSchema->primaryKey; } } $related->{$fk} = $owner->{$pk}; } if ($data === null) { $related->getIsNewRecord() ? $related->insert() : $related->update(); } else { $this->internalSave($data, $related); } break; case CActiveRecord::HAS_MANY: $relatedTableSchema = CActiveRecord::model($relatedClass)->getTableSchema(); $fks = preg_split('/\\s*,\\s*/', $foreignKey, -1, PREG_SPLIT_NO_EMPTY); $map = array(); foreach ($fks as $i => $fk) { if (!isset($relatedTableSchema->columns[$fk])) { throw new CDbException(Yii::t('yiiext', 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key "{key}". There is no such column in the table "{table}".', array('{class}' => get_class($owner), '{relation}' => $name, '{key}' => $fk, '{table}' => $relatedTableSchema->name))); } if (isset($relatedTableSchema->foreignKeys[$fk]) && $schema->compareTableNames($ownerTableSchema->rawName, $relatedTableSchema->foreignKeys[$fk][0])) { $pk = $relatedTableSchema->foreignKeys[$fk][1]; } else { if (is_array($ownerTableSchema->primaryKey)) { $pk = $ownerTableSchema->primaryKey[$i]; } else { $pk = $ownerTableSchema->primaryKey; } } $map[$pk] = $fk; } foreach ($related as $model) { foreach ($map as $pk => $fk) { $model->{$fk} = $owner->{$pk}; } if ($data === null) { $model->getIsNewRecord() ? $model->insert() : $model->update(); } else { $this->internalSave($data, $model); } } break; case CActiveRecord::MANY_MANY: if (!preg_match('/^\\s*(.*?)\\((.*)\\)\\s*$/', $foreignKey, $matches)) { throw new CDbException(Yii::t('yiiext', 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key. The format of the foreign key must be "joinTable(fk1,fk2,...)".', array('{class}' => get_class($owner), '{relation}' => $name))); } if (($joinTable = $schema->getTable($matches[1])) === null) { throw new CDbException(Yii::t('yiiext', 'The relation "{relation}" in active record class "{class}" is not specified correctly: the join table "{joinTable}" given in the foreign key cannot be found in the database.', array('{class}' => get_class($owner), '{relation}' => $name, '{joinTable}' => $matches[1]))); } $relatedTableSchema = CActiveRecord::model($relatedClass)->getTableSchema(); $fks = preg_split('/\\s*,\\s*/', $matches[2], -1, PREG_SPLIT_NO_EMPTY); $ownerMap = array(); $relatedMap = array(); $fkDefined = true; foreach ($fks as $i => $fk) { if (!isset($joinTable->columns[$fk])) { throw new CDbException(Yii::t('yii', 'The relation "{relation}" in active record class "{class}" is specified with an invalid foreign key "{key}". There is no such column in the table "{table}".', array('{class}' => get_class($owner), '{relation}' => $name, '{key}' => $fk, '{table}' => $joinTable->name))); } if (isset($joinTable->foreignKeys[$fk])) { list($tableName, $pk) = $joinTable->foreignKeys[$fk]; if (!isset($ownerMap[$pk]) && $schema->compareTableNames($ownerTableSchema->rawName, $tableName)) { $ownerMap[$pk] = $fk; } else { if (!isset($relatedMap[$pk]) && $schema->compareTableNames($relatedTableSchema->rawName, $tableName)) { $relatedMap[$pk] = $fk; } else { $fkDefined = false; break; } } } else { $fkDefined = false; break; } } if (!$fkDefined) { $ownerMap = array(); $relatedMap = array(); foreach ($fks as $i => $fk) { if ($i < count($ownerTableSchema->primaryKey)) { $pk = is_array($ownerTableSchema->primaryKey) ? $ownerTableSchema->primaryKey[$i] : $ownerTableSchema->primaryKey; $ownerMap[$pk] = $fk; } else { $j = $i - count($ownerTableSchema->primaryKey); $pk = is_array($relatedTableSchema->primaryKey) ? $relatedTableSchema->primaryKey[$j] : $relatedTableSchema->primaryKey; $relatedMap[$pk] = $fk; } } } if ($ownerMap === array() && $relatedMap === array()) { throw new CDbException(Yii::t('yii', 'The relation "{relation}" in active record class "{class}" is specified with an incomplete foreign key. The foreign key must consist of columns referencing both joining tables.', array('{class}' => get_class($owner), '{relation}' => $name))); } $insertAttributes = array(); $deleteAttributes = array(); foreach ($related as $model) { $newFlag = $model->getIsNewRecord(); if ($data === null) { $newFlag ? $model->insert() : $model->update(); } else { $this->internalSave($data, $model); } $joinTableAttributes = array(); foreach ($ownerMap as $pk => $fk) { $joinTableAttributes[$fk] = $owner->{$pk}; } foreach ($relatedMap as $pk => $fk) { $joinTableAttributes[$fk] = $model->{$pk}; } if (!$newFlag) { $deleteAttributes[] = $joinTableAttributes; } $insertAttributes[] = $joinTableAttributes; } if ($deleteAttributes !== array()) { $condition = $builder->createInCondition($joinTable, array_merge(array_values($ownerMap), array_values($relatedMap)), $deleteAttributes); $criteria = $builder->createCriteria($condition); // $command = $builder->createDeleteCommand($joinTable, $criteria)->execute(); } foreach ($insertAttributes as $attributes) { $f = false; $cr = new CDbCriteria(); foreach ($attributes as $k => $v) { $cr->addCondition('t.' . $k . '=' . $v); } $c = $builder->createFindCommand($joinTable, $cr)->query(); foreach ($c as $arr) { if (ArrayHelper::cmp($arr, $attributes)) { $f = true; break; } } if ($f) { continue; } $builder->createInsertCommand($joinTable, $attributes)->execute(); } break; } } if ($extTransFlag === null) { $transaction->commit(); } return true; } catch (Exception $e) { if ($extTransFlag === null) { $transaction->rollBack(); } throw $e; } }