Как сохранить связанные данные Yii
С недавних пор, я начал изучать замечательный фрэймворк 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, наша форма заполнится данными