diff --git a/framework/db/ActiveQuery.php b/framework/db/ActiveQuery.php index 0810620..7757716 100644 --- a/framework/db/ActiveQuery.php +++ b/framework/db/ActiveQuery.php @@ -253,17 +253,9 @@ class ActiveQuery extends Query $t = strtolower($name); if (!isset($relations[$t])) { - $getter = 'get' . $name; - if (!method_exists($model, $getter)) { - throw new Exception("Unknown relation: $name"); - } - $relation = $model->$getter(); - if ($relation instanceof ActiveRelation) { - $relation->primaryModel = null; - $relations[$t] = $relation; - } else { - throw new Exception("Unknown relation: $name"); - } + $relation = $model->getRelation($name); + $relation->primaryModel = null; + $relations[$t] = $relation; } else { $relation = $relations[$t]; } diff --git a/framework/db/ActiveRecord.php b/framework/db/ActiveRecord.php index 39209bb..6ca0d26 100644 --- a/framework/db/ActiveRecord.php +++ b/framework/db/ActiveRecord.php @@ -13,6 +13,7 @@ namespace yii\db; use yii\base\Model; use yii\base\Event; use yii\base\ModelEvent; +use yii\base\BadMethodException; use yii\db\Exception; use yii\db\Connection; use yii\db\TableSchema; @@ -768,6 +769,7 @@ abstract class ActiveRecord extends Model * Returns the primary key value. * @param boolean $asArray whether to return the primary key value as an array. If true, * the return value will be an array with column name as key and column value as value. + * Note that for composite primary keys, an array will always be returned regardless of this parameter value. * @return mixed the primary key value. An array (column name=>column value) is returned if the primary key is composite. * If primary key is not defined, null will be returned. */ @@ -859,27 +861,185 @@ abstract class ActiveRecord extends Model /** * @param string $name * @param ActiveRecord $model - * @throws Exception */ - public function linkWith($name, $model) + public function link($name, $model, $extraAttributes = array()) { - $getter = 'get' . $name; - if (!method_exists($this, $getter)) { - throw new Exception('Unknown relation: ' . $name); + $relation = $this->getRelation($name); + + if ($relation->via !== null) { + if (is_array($relation->via)) { + /** @var $viaQuery ActiveRelation */ + list($viaName, $viaQuery) = $relation->via[1]; + /** @var $viaClass ActiveRecord */ + $viaClass = $viaQuery->modelClass; + $viaTable = $viaClass::tableName(); + } else { + $viaQuery = $relation->via; + $viaTable = reset($relation->via->from); + } + $columns = array(); + foreach ($viaQuery->link as $a => $b) { + $columns[$a] = $this->$b; + } + foreach ($relation->link as $a => $b) { + $columns[$b] = $model->$a; + } + foreach ($extraAttributes as $k => $v) { + $columns[$k] = $v; + } + $command = $this->getDbConnection()->createCommand(); + $command->insert($viaTable, $columns)->execute(); + return; + // todo: update $viaName + } + $keys = $model->primaryKey(); + $p1 = true; + foreach (array_keys($relation->link) as $key) { + if (!in_array($key, $keys, true)) { + $p1 = false; + break; + } } - $relation = $this->$getter(); - if (!$relation instanceof ActiveRelation) { - throw new Exception('Unknown relation: ' . $name); + $keys = $this->primaryKey(); + $p2 = true; + foreach (array_values($relation->link) as $key) { + if (!in_array($key, $keys, true)) { + $p2 = false; + break; + } } - if ($relation->multiple) { + if ($p1 && $p2) { + if ($this->getIsNewRecord() && $model->getIsNewRecord()) { + throw new Exception('both new'); + } elseif ($this->getIsNewRecord()) { + foreach ($relation->link as $a => $b) { + $value = $model->$a; + if ($value === null) { + throw new Exception('key null'); + } + $this->$b = $value; + } + $this->save(false); + } elseif ($model->getIsNewRecord()) { + foreach ($relation->link as $a => $b) { + $value = $this->$b; + if ($value === null) { + throw new Exception('key null'); + } + $model->$a = $value; + } + $model->save(false); + } else { + throw new Exception('both old'); + } + } elseif ($p1) { + foreach ($relation->link as $a => $b) { + $value = $model->$a; + if ($value === null) { + throw new Exception('key null'); + } + $this->$b = $value; + } + $this->save(false); + } elseif ($p2) { foreach ($relation->link as $a => $b) { - $key = $this->$b; - if ($key === null) { + $value = $this->$b; + if ($value === null) { throw new Exception('key null'); } - $model->$a = $this->$b; + $model->$a = $value; + } + $model->save(false); + } else { + throw new Exception(''); + } + // todo: update relation models + } + + /** + * @param string $name + * @param ActiveRecord $model + * @throws Exception + */ + public function unlink($name, $model) + { + $relation = $this->getRelation($name); + + if ($relation->via !== null) { + if (is_array($relation->via)) { + /** @var $viaQuery ActiveRelation */ + $viaQuery = $relation->via[1]; + /** @var $viaClass ActiveRecord */ + $viaClass = $viaQuery->modelClass; + $viaTable = $viaClass::tableName(); + } else { + $viaQuery = $relation->via; + $viaTable = reset($relation->via->from); + } + $columns = array(); + foreach ($viaQuery->link as $a => $b) { + $columns[$a] = $this->$b; + } + foreach ($relation->link as $a => $b) { + $columns[$b] = $model->$a; + } + $command = $this->getDbConnection()->createCommand(); + $command->delete($viaTable, $columns)->execute(); + return; + } + + $keys = $model->primaryKey(); + $p1 = true; + foreach (array_keys($relation->link) as $key) { + if (!in_array($key, $keys, true)) { + $p1 = false; + break; + } + } + $keys = $this->primaryKey(); + $p2 = true; + foreach (array_values($relation->link) as $key) { + if (!in_array($key, $keys, true)) { + $p2 = false; + break; + } + } + if ($p1 && $p2) { + foreach ($relation->link as $a => $b) { + $model->$a = null; + } + $model->save(false); + } elseif ($p1) { + foreach ($relation->link as $b) { + $this->$b = null; + } + $this->save(false); + } elseif ($p2) { + foreach ($relation->link as $a => $b) { + $model->$a = null; + } + $model->save(false); + } else { + throw new Exception(''); + } + // todo: update relation models + } + + /** + * @param string $name + * @return ActiveRelation + * @throws Exception + */ + public function getRelation($name) + { + $getter = 'get' . $name; + try { + $relation = $this->$getter(); + if ($relation instanceof ActiveRelation) { + return $relation; } - return $model->save(false); + } catch (BadMethodException $e) { } + throw new Exception('Unknown relation: ' . $name); } } diff --git a/framework/db/ActiveRelation.php b/framework/db/ActiveRelation.php index 1ef749f..7cc6852 100644 --- a/framework/db/ActiveRelation.php +++ b/framework/db/ActiveRelation.php @@ -12,7 +12,6 @@ namespace yii\db; use yii\db\Connection; use yii\db\Command; -use yii\db\QueryBuilder; /** * It is used in three scenarios: @@ -45,7 +44,7 @@ class ActiveRelation extends ActiveQuery /** * @var array|ActiveRelation */ - protected $via; + public $via; /** * @param string $relationName @@ -55,19 +54,12 @@ class ActiveRelation extends ActiveQuery */ public function via($relationName, $callback = null) { - $getter = 'get' . $relationName; - if (method_exists($this->primaryModel, $getter)) { - $relation = $this->primaryModel->$getter(); - if ($relation instanceof ActiveRelation) { - $relation->primaryModel = null; - $this->via = array($relationName, $relation); - if ($callback !== null) { - call_user_func($callback, $relation); - } - return $this; - } + $relation = $this->primaryModel->getRelation($relationName); + $this->via = array($relationName, $relation); + if ($callback !== null) { + call_user_func($callback, $relation); } - throw new Exception('Unknown relation: ' . $relationName); + return $this; } /** @@ -110,7 +102,6 @@ class ActiveRelation extends ActiveQuery // via relation /** @var $viaQuery ActiveRelation */ list($viaName, $viaQuery) = $this->via; - $viaQuery->primaryModel = $this->primaryModel; if ($viaQuery->multiple) { $viaModels = $viaQuery->all(); $this->primaryModel->populateRelation($viaName, $viaModels); @@ -143,6 +134,7 @@ class ActiveRelation extends ActiveQuery // via relation /** @var $viaQuery ActiveRelation */ list($viaName, $viaQuery) = $this->via; + $viaQuery->primaryModel = null; $viaModels = $viaQuery->findWith($viaName, $primaryModels); $this->filterByModels($viaModels); } else {