Intro
С недавних пор, я начал изучать замечательный фрэймворк Yii. При разработке, я столкнулся с задачей сохранения данных из одной формы, в несколько таблиц. Погуглив, я не нашел вменяемого руководство, которое объясняет полный смысл этого сохранения. На официальном, русскоязычном сайте, я нашел короткую статью от Александра Макарова, но она, опять же в общих чертах демонстрирует «соль» этого метода.
Я решил написать эту статью, в стиле tutorial, чтобы дать новичкам возможность наглядно увидеть полный цикл CRUD при работе с несколькими моделями, а тем кто по-опытней, покрикивать это решение, и объяснить «как делать не надо».
Постановка задачи
Необходимо создать две таблицы, для хранения данных о пользователе. Одна называется user — предназначена для хранения логина и пароля пользователя, его статуса и его глобального идентификатора, который будет использоваться во всей системе. Вторая user_profile которая предназначена для хранения публичных данных о пользователе, его имени и фамилии и т.п. Таблица профиля связана с таблицей пользователей, при помощи внешнего ключа.
Необходимо сохранять и редактировать данные о пользователя, из одной единой формы, которая включает в себя поля как таблицы user так и user_profile
Создание таблиц БД
Создадим две таблицы такого вида
user — родительская таблица, где создаётся id пользователя
user_profile — дочерняя таблица, имеет внешний ключ user_id на родительскую таблицу
Создание моделей
При помощи генератора кода gii, создадим модели этих таблиц и назовем их соответственно User и UserProfile.
Так же при помощи gii создадим CRUD для модели User (замечу, что для модели UserProfile, я намеренно не создаю CRUD, так как он нам не понадобиться)
Доработка родительской модели
В родительскую модель, нам необходимо добавить поля из дочерней модели:
<?php
class User extends CActiveRecord
{
////добавление начато
public $name;
public $first_name;
public $description;
////добавление окончено
...
public function attributeLabels()
{
return array(
'id' => 'ID',
'login' => 'Login',
'password' => 'Password',
'status' => 'Status',
////добавление начато
'name'=>'Имя',
'first_name'=>'Фамилия',
'description'=>'Описание'
////добавление окончено
);
}
А также определить метод AfterSave добавив код:
////добавление начато
protected function afterSave() {
parent::afterSave();
if($this->isNewRecord){
// если мы создаем нового пользователя, тогда нам необходимо создать
// для него запись в таблице профиля с ссылкой на родительскую таблицу
$user_profile = new UserProfile;
$user_profile->user_id = $this->id;
$user_profile->name = $this->name;
$user_profile->first_name = $this->first_name;
$user_profile->description = $this->description;
$user_profile->save();
} else {
// иначе неободимо обновить данные в таблице профиля
UserProfile::model()->updateAll(array( 'user_id' =>$this->id,
'name' => $this->name,
'first_name'=>$this->first_name,
'description'=>$this->description
), 'user_id=:user_id', array(':user_id'=> $this->id));
}
}
////добавление окончено
Теперь по шагам, что тут произошло:
1) добавили три публичные переменные, которые соответствуют полям модели UserProfile,
теперь это новые поля в модели User
public $name;
public $first_name;
public $description;
2) в методе attributeLabels(), создаем описания для новых полей
'name'=>'Имя',
'first_name'=>'Фамилия',
'description'=>'Описание'
3) Теперь создаем метод afterSave, который срабатывает после сохранения данных в модели User,
и в тут же будем сохранять данные в UserProfile.
Таким образом, мы проверяем, что сейчас происходит: создание новой записи или редактирование существующей.
if($this->isNewRecord){
Если создался новый пользователь, то:
- Создаю экземпляр модели UserProfile
- Получаем ID созданного пользователя, и присваиваем это значение полю
$user_profile->user_id = $this->id; - Присваиваю полям модели UserProfile, значения пришедшие из формы (то как мы получаем эти данные из формы, смотрим действие actionCreate в котнтроллере UserController)
- Выполняем метод save() у модели UserProfile
Если это была операция редактирования, то:
- необходимо выполнить метод updateAll для модели UserProfile
UserProfile::model()->updateAll(array( 'user_id' =>$this->id,
'name' => $this->name,
'first_name'=>$this->first_name,
'description'=>$this->description
), 'user_id=:user_id', array(':user_id'=> $this->id));
Здесь значения заполняются из действия actionUpdate контроллера UserController
Доработка контроллера
Теперь открываем свеже-сгенирированный контроллер UserController.
В нём, нам предстоит поправить два действия actionCreate и actionUpdate,
и одну функцию loadModel
actionCreate
Присваиваем значения, публичным переменным, которые мы добавили в модели.
Вот отсюда используются данные, в методе afterSave
public function actionCreate()
{
$model=new User;
if(isset($_POST['User']))
{
$model->attributes=$_POST['User'];
////добавление начато
$model->name = $_POST['User']['name'];
$model->first_name = $_POST['User']['first_name'];
$model->description = $_POST['User']['description'];
////добавление оконченно
if($model->save())
$this->redirect(array('view','id'=>$model->id));
}
$this->render('create',array(
'model'=>$model,
));
}
actionUpdate
Здесь, происходит аналогичный процесс, что и при создании пользователя
public function actionUpdate($id)
{
$model=$this->loadModel($id);
if(isset($_POST['User']))
{
$model->attributes=$_POST['User'];
////добавление начато
$model->name = $_POST['User']['name'];
$model->first_name = $_POST['User']['first_name'];
$model->description = $_POST['User']['description'];
////добавление оконченно if($model->save())
$this->redirect(array('view','id'=>$model->id));
}
$this->render('update',array(
'model'=>$model,
));
}
loadModel
Тут мы добавляем такие строки.
Нам необходимо загрузить данные из таблицы профиля пользователя, найденные по его user_id.
public function loadModel($id)
{
$model=User::model()->findByPk($id);
////добавление начато
$modelprofile=UserProfile::model()->find('user_id=:user_id', array(':user_id'=> $id));
$model->name = $modelprofile->name;
$model->first_name = $modelprofile->first_name;
$model->description = $modelprofile->description;
////добавление оконченно
if($model===null)
throw new CHttpException(404,'The requested page does not exist.');
return $model;
}
Это необходимо для того, чтобы данные загружались в форму, когда мы нажимаем на ссылку Update User,
и для отображении информации в просмотровом представлении
Доработка родительской формы
В форме, которая находиться по адресу protected/views/user/_form.php
нам необходимо добавить элементы для ввода имени, фамилии и описания пользователя
<?php
<div class="form">
<?php $form=$this->beginWidget('CActiveForm', array(
'id'=>'user-form',
'enableAjaxValidation'=>false,
)); ?>
<p class="note">Fields with <span class="required">*</span> are required.</p>
<?php echo $form->errorSummary($model); ?>
<div class="row">
<?php echo $form->labelEx($model,'login'); ?>
<?php echo $form->textField($model,'login',array('size'=>45,'maxlength'=>45)); ?>
<?php echo $form->error($model,'login'); ?>
</div>
.....
////добавление начато
<div class="row">
<?php echo $form->labelEx($model,'name'); ?>
<?php echo $form->textField($model,'name',array('size'=>45,'maxlength'=>45)); ?>
<?php echo $form->error($model,'name'); ?>
</div>
<div class="row">
<?php echo $form->labelEx($model,'first_name'); ?>
<?php echo $form->textField($model,'first_name',array('size'=>45,'maxlength'=>45)); ?>
<?php echo $form->error($model,'first_name'); ?>
</div>
<div class="row">
<?php echo $form->labelEx($model,'description'); ?>
<?php echo $form->textField($model,'description',array('size'=>45,'maxlength'=>45)); ?>
<?php echo $form->error($model,'description'); ?>
</div>
////добавление оконченно
<div class="row buttons">
<?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Save'); ?>
</div>
<?php $this->endWidget(); ?>
</div><!-- form -->
Доработка представления
Файл представления, который находиться по адресу protected/views/user/view.php, мы, так же добаляем наши новые поля в виджет детального отображения
<?php
....
<?php $this->widget('zii.widgets.CDetailView', array(
'data'=>$model,
'attributes'=>array(
'id',
'login',
'password',
'status',
////добавление начато
'name',
'first_name',
'description'
////добавление оконченно
),
)); ?>
а во вспомогательном файле protected/views/user/_view.php добавим следующее:
<?php
/* @var $this UserController */
/* @var $data User */
?>
<div class="view">
<b><?php echo CHtml::encode($data->getAttributeLabel('id')); ?>:</b>
<?php echo CHtml::link(CHtml::encode($data->id), array('view', 'id'=>$data->id)); ?>
<br />
...
////добавление начато
<b><?php echo CHtml::encode($data->getAttributeLabel('name')); ?>:</b>
<?php echo CHtml::encode($data->name); ?>
<br />
<b><?php echo CHtml::encode($data->getAttributeLabel('first_name')); ?>:</b>
<?php echo CHtml::encode($data->first_name); ?>
<br />
<b><?php echo CHtml::encode($data->getAttributeLabel('descrption')); ?>:</b>
<?php echo CHtml::encode($data->descrption); ?>
<br />
////добавление оконченно
</div>
Проверка результата
Теперь, если всё сделанно как описанно выше, переходим по адресу
localhost/YourProjectName/index.php?r=user/create
Заполняем все поля, и нажимаем Create.
После чего, должны увидеть такой результат:
Если мы хотим редактировать эту запись, нажимаем на ссылку Update User,
наша форма заполнится данными
Outro
Я надеюсь, что статья будет полезна начинающим разработчикам, и сэкономит время при поиске аналогичного решения.
Принимается любая конструктивная критика, опытных разработчиков, сдобренная личными примерами.
Спасибо за внимание!
Используемая литература
Статья из раздела рецепты "Сохранение связанных данных".
Автор: kxxb