/** * Generates an appropriate input name for the specified attribute name or expression. * * This method generates a name that can be used as the input name to collect user input * for the specified attribute. The name is generated according to the [[Model::formName|form name]] * of the model and the given attribute name. For example, if the form name of the `Post` model * is `Post`, then the input name generated for the `content` attribute would be `Post[content]`. * * See [[getAttributeName()]] for explanation of attribute expression. * * @param Model $model the model object * @param string $attribute the attribute name or expression * @return string the generated input name * @throws InvalidParamException if the attribute name contains non-word characters. */ protected function getInputName($model, $attribute) { $formName = $model->formName(); if (!preg_match('/(^|.*\\])([\\w\\.]+)(\\[.*|$)/', $attribute, $matches)) { throw new Exception('Attribute name must contain word characters only.'); } $prefix = $matches[1]; $attribute = $matches[2]; $suffix = $matches[3]; if ($formName === '' && $prefix === '') { return $attribute . $suffix; } elseif ($formName !== '') { return $formName . $prefix . "[{$attribute}]" . $suffix; } else { throw new Exception(get_class($model) . '::formName() cannot be empty for tabular inputs.'); } }
/** * hydrates model with hierarchical data from request * if primary keys are set, the model is loaded, otherwise a new object is created * @param Model $model model instance to be filled * @param array $data hierarchical data from request * @param array $config used for recursion * @param boolean $first used for recursion * @param Model $modeldata used for recursion * @return boolean ok */ public function hydrate(&$model, $data, $config = null, $first = true, &$modeldata = null) { $db = $model->getDb(); $config = empty($config) ? $this->config : $config; $formName = ArrayHelper::getValue($config, 'formName', $model->formName()); $data = empty($formName) ? $data : $data[$formName]; // begin transaction at root level if ($first) { $transaction = $db->beginTransaction(); if ($ok = $this->loadModel($model, $data)) { $model->save(); } else { $transaction->rollback(); return false; } } $relations = ArrayHelper::getValue($config, 'relations', []); // preload non-incremental relations at once if ($modeldata === null && count($relations) > 0) { $withs = $this->loadRelations(null, $config); if (!empty($withs)) { $find = $model::find(); $keys = $this->getPrimaryKeyFor(get_class($model), $model); $q = []; foreach ($keys as $key => $value) { $q[] = sprintf('%s=%s', $key, $db->quoteValue($model->{$key})); } $find->where(implode(' AND ', $q)); foreach ($withs as $value) { $find->with($value); } $modeldata = $find->one(); } } // assign relational data foreach ($relations as $relation => $subconfig) { $rel = $model->getRelation($relation); $class = $rel->modelClass; $subdata = ArrayHelper::getValue($data, $relation, []); $subconfig['formName'] = ArrayHelper::getValue($subconfig, 'formName', false); $incremental = $subconfig['incremental'] = ArrayHelper::getValue($subconfig, 'incremental', false); $delete = $subconfig['delete'] = ArrayHelper::getValue($subconfig, 'delete', false); if (empty($modeldata)) { $submodeldata = null; } else { $submodeldata = $modeldata->{$relation}; } // 1:n relations if ($rel->multiple) { $submodel = new $class(); $db = $submodel->getDb(); $keys = $this->getPrimaryKeyFor($class, $submodel); // apply updates to existing models // do not delete associated child items, if incremental = true if (!$incremental) { $composite = count($keys) > 1; $akeys = array_keys($keys); if (!empty($submodeldata)) { foreach ($submodeldata as $kkk => $existing) { $existingKey = $existing->getAttributes($akeys); // update already linked and existing models $found = false; foreach ($subdata as $key => $subitemdata) { $pk = array_filter(array_intersect_key($subitemdata, $keys)); if (!empty($pk)) { if (count(array_diff_assoc($existingKey, $pk)) == 0) { $existing->load($subitemdata, false); $existing->save(); $this->hydrate($existing, $subitemdata, $subconfig, false, $existing); unset($subdata[$key]); $found = true; break; } } } if (!$found) { if ($rel->via !== null) { $model->unlink($relation, $existing, true); } if ($delete) { $this->delete($existing, $subconfig); } } } } } foreach ($subdata as $subitemdata) { $submodel = new $class(); $ok = $this->loadModel($submodel, $subitemdata, $class); if (!$ok) { break; } if ($rel->via !== null && $submodel->isNewRecord) { $submodel->save(); $model->link($relation, $submodel); } else { if ($rel->via !== null) { $keys = array_keys($this->getPrimaryKeyFor($class, $submodel)); $keyValues = $submodel->getAttributes($keys); $a = array_map(function ($a, $b) use($db) { return sprintf('%s=%s', $a, $db->quoteValue($b)); }, $keys, $keyValues); $b = implode(' AND ', $a); $find = $model->getRelation($relation)->andWhere($b)->exists(); if (!$find) { $model->link($relation, $submodel); } $submodel->save(); } else { $model->link($relation, $submodel); } } $this->hydrate($submodel, $subitemdata, $subconfig, false, $submodeldata); } } else { // 1:1 relations if (!empty($subdata)) { if (empty($model->{$relation})) { $submodel = new $class(); $ok = $this->loadModel($submodel, $subdata, $class); if ($rel->via !== null) { $ok = $submodel->save(); } if ($ok && $submodel->isNewRecord) { $ok = $model->link($relation, $submodel); } } else { $submodel = $model->{$relation}; $ok = $submodel->load($subdata, false); if ($ok) { $ok = $submodel->save(); } } if (!$ok) { break; } $this->hydrate($submodel, $subdata, $subconfig, false, $submodeldata); } } } if ($first) { $ok ? $transaction->commit() : $transaction->rollback(); } return true; }