Pull to refresh

Comments 19

Во первых, этот фрагмент лишний:

$model->name = $_POST['User']['name'];
$model->first_name = $_POST['User']['first_name'];
$model->description = $_POST['User']['description'];

достаточно описать правила валидации.

Во вторых, сохраняя дочернюю модель в методе afterSave указанным способом мы можем получить ситуацию, когда первая модель сохранилась, а при сохранении второй возникли ошибки.

Подгрузку данных целесообразнее делать не в loadModel а переопределив метод CActiveRecord::populateRecord.

Я предпочитаю делать по другому. Если нет противопоказаний — не делить таблицы на аккаунт/профиль. Если деление все-же необходимо, то в завимсимости от конкретного случая либо работать с двумя моделями по отдельности:

$user->attributes =    $_POST['User'];
$profile->attributes = $_POST['Profile'];
$transaction = $db->beginTransaction();
if ($user->save() && $profile->save()) {
    ...
    $transaction->commit();
} else {
    $transaction->rollback();
}

либо создаю отдельную CFormModel, которая включает в себя все необходимые поля, описание различных сценариев валидации и логику сохранения данных.

Заранее извиняюсь, если где допустил ошибки в названиях
Если остановиться на вашем сценарии, я бы лучше переопределил метод save а не afterSave
А как быть, если понадобится вставить/обработать данные в БД консольной командой, а у нас save() и т.п. переопределен, и в нем $_POST?
Само собой, в модели не должно быть обращения к таки данным. В save реализуется только транзакция, вызов валидации и сохранение дочерней модели, все остальное в контроллере.
Интуитивно, понимал, что делая вот так
$model->name = $_POST['User']['name'];

я делаю, что то не правильно. Спасибо что, дали совет.

Хочу уточнить, правильно ли я понял идею относительно
$user->attributes =    $_POST['User'];
$profile->attributes = $_POST['Profile'];


такое возможно если мы в контроллере, например в actionCreate делаем так
 public function actionCreate(){
            $modelUser = new User();
            $modelProfile = new Profile();
             . . .
           $this->render('form',array(
			  'modelUser'=>$modelUser,
			  'modelProfile'=>$modelProfile,
		));


а затем, при описании формы делаем следующее
<div class="form">

<?php $form=$this->beginWidget('CActiveForm', array(
	'id'=>'UserForm',
	'enableAjaxValidation'=>false,
)); ?>
       <?php echo $form->errorSummary($modelUser); ?>
       <?php echo $form->errorSummary($modelProfile); ?>

	<div class="row">
		<?php echo $form->textField($modelUser,'login',array('size'=>45,'maxlength'=>45)); ?>
		<?php echo $form->error($modelUser,'login'); ?>
	</div>
       <div class="row">
		<?php echo $form->textField($modelProfile,'name'); ?>
		<?php echo $form->error($modelProfile,'name'); ?>
	</div>
. . .


То есть в рендер переедаю две модели, и при построении единой формы, использую поля из этих моделей.

Да, все верно. Для простоты можно передавать одну модельку, в контроллере написав
$modelUser->profile = $modelProfile
или чуть более корректно
$modelUser->setRelatedRecord('profile', $modelProfile, false);

Естественно, должна быть определена связь 'profile'
В метод CActiveForm::errorSummary() можно передать массив моделей.

<?php echo $form->errorSummary(array($modelUser, $modelProfile)); ?>
Спасибо, учту такую возможность.
$user->attributes = $_POST['User'];


Удобно конечно, но для пользователей (да и в админках) так лучше не делать. Вам могут прислать совершенно любые данные. Скажем сменить id записи дату или владельца. А если хочется так, то лучше делать какую-то подпись для списка параметров, которые действительно должны прийти от пользователя, хотя вроде еще можно помудрить с именованными наборами валидаций.
Правила валидации в Yii для этого и созданы
Вам тогда стоит в своем уточнение и это уточнить, а то возьмут ваш пример. Да и вообще группируют правила и используют эти группы не все (по моему меньшинство), а вот ваш пример используют большинство.
Так пришлось бы всю главу по работе с формами из официального руководства процитировать
С локализацией в этом примере полная лажа, согласен.
Но в этом примере, я ставил акцент на работе с CRUD двух связанных таблиц.

Если столкнусь с проблемами, при разработке локализации, то расскажу в следующем посте о их решении.

А если у Вас есть, какой то совет и ответ, милости прошу опубликовать его в комментарии ниже.

Почему лажа, тут достаточно вместо названий полей сделать вызов Yii::t, а большего вроде в этом примере и не надо. Но не уверен, что в рамках этого примера вообще стоит о ней задумываться.
По-моему недостаток вашего подхода в том, что одна модель должна знать о наличии полей другой модели, а не только о связи с ней.

Для похожей задачи я использовал это расширение github.com/yiiext/activerecord-relation-behavior

Оно позволяет удобно задавать значения для связанных таблиц и прозрачно сохранять просто вызвав ->save().

В вашем случае если бы была связь Profile
'profile'=>array(self::HAS_ONE, 'profile', 'user_id'),
то код сохранения мог бы быть таким

$user = new User();
$user->attributes = $_POST['User'];
$user->profile->attributes = $_POST['Profile'];
$user->save();

Насчет удобства генерации форм в этом случае, не скажу. Главное, что это универсальное средство которое достаточно просто добавить в behaviors, чтоб оно заработало.

P.S. В этом расширении не реализована обработка удаления. Но оно легко допиливается. Если понадобится, могу поделиться собственной допиленной версией этого расширения с delete().
Спасибо за расширение.
А насчёт обработки удаления, я думаю можно настроить каскадное удаление во внешних ключах таблиц, или я что-то упускаю?
Да, можно. Так и рекомендуется делать.
Я допилил, чтоб, так сказать, была функционально целостная картина.
Возможно будет полезным CDbCommandBuilder::createMultipleInsertCommand()
Sign up to leave a comment.

Articles