与另外2个模型建立多多关系模型


Yii Model ManyToMany Relation with another 2 models

首先我为写作上的错误道歉

好吧,让我解释一下

我有3个模型,音乐模型,音乐链接模型和音乐类型模型。在音乐控制器中,我有一个动作"创建"。

然后,在表单上,我有Music Model字段和其他2个字段。选择一个小部件,让你选择多个流派,和另一个字段的链接(音乐文件的url),可以通过jQuery插件"RelCopy"动态克隆(最多6个字段)

一个音乐可以有多个流派,也可以有多个链接

我的问题是如何保存所有这些字段,正确吗?我希望我说得够清楚了。

这是我到目前为止的代码:

MusicController.php

/**
* Creates a new model.
* If creation is successful, the browser will be redirected to the 'view' page.
*/
public function actionCreate()
{
$music = new Music;
$genre = new MusicGenre;
$link = new MusicLink;
// Uncomment the following line if AJAX validation is needed
// $this->performAjaxValidation($music);
if(isset($_POST['Music']) && isset($_POST['MusicGenre']))
{
    CActiveForm::validate($genre);
    CActiveForm::validate($link);
    $music->attributes=$_POST['Music'];
    if($music->save()){
        foreach(explode(',',$_POST['MusicGenre']['genre_id']) as $gen)
        {
            $genre=new MusicGenre;
            $genre->genre_id = $gen;
            $genre->music_id = $music->id;
            if($genre->validate())
                $valid[]=true;
            else
                $valid[]=false;
        }
        foreach($_POST['Music']['links'] as $lnk)
        {
            $link=new MusicLink;
            $link->link = $lnk;
            $link->music_id = $music->id;
            $link->host = MusicLink::model()->getHoster($lnk);
            if($link->validate())
                $valid[]=true;
            else
                $valid[]=false;
        }
        if(!in_array(false,$valid)){
            $genre->save();
            $link->save();
            $this->redirect(array('view','id'=>$music->id));
        }
    }
}
$this->render('create',array(
'music'=>$music,
'genre'=>$genre,
'link'=>$link,
));
}
/**
* Updates a particular model.
* If update is successful, the browser will be redirected to the 'view' page.
* @param integer $id the ID of the model to be updated
*/
public function actionUpdate($id)
{
$music = $this->loadModel($id);
$genre = MusicGenre::model()->findByAttributes(array('music_id'=>$id));//new MusicGenre;
$link = MusicLink::model()->findAll('music_id=:mID',array(':mID'=>$id));;
// Uncomment the following line if AJAX validation is needed
// $this->performAjaxValidation($music);
if(isset($_POST['Music']) && isset($_POST['MusicGenre']))
{
    $music->attributes=$_POST['Music'];
    if($music->save()){
        foreach(explode(',',$_POST['MusicGenre']['genre_id']) as $gen)
        {
            //$genre = MusicGenre::model()->findByAttributes(array('music_id'=>$music->id));
            $genre->genre_id = Genre::model()->nameToId($gen);
            $genre->music_id = $music->id;
            $valid[]=$genre->save();
        }
        foreach($_POST['Music']['links'] as $lid => $lnk)
        {
            $link = MusicLink::model()->find('music_id=:mID AND id=:lID',array(':mID'=>$music->id,':lID'=>$lid));
            if($link == null){
                $link=new MusicLink;
                $link->link = $lnk;
                $link->music_id = $music->id;
                $link->host = MusicLink::model()->getHoster($lnk);
                $valid[]=$link->save();
            }else if($link->link != $lnk){
                $link->link = $lnk;
                $link->music_id = $music->id;
                $link->host = MusicLink::model()->getHoster($lnk);
                $valid[]=$link->save();
            }
        }
        if(!in_array(false,$valid))
            $this->redirect(array('view','id'=>$music->id));
    }
}
$this->render('update',array(
'music'=>$music,
'genre'=>$genre,
'link'=>$link,
));
}

' music_form.php

<?php $form=$this->beginWidget('bootstrap.widgets.TbActiveForm',array(
    'id'=>'music-form',
    'type' => 'horizontal',
    'customCssErrors' => 'inline',
    'enableAjaxValidation'=>false,
)); ?>
<fieldset>
<legend>
<?php echo $music->isNewRecord ? Yii::t('b2r','Create Music') : Yii::t('b2r','Update Music') ;?>
</legend>
<p class="help-block well well-small"><?php echo Yii::t('b2r','Fields with <span class="required">*</span> are required.'); ?>
</p>
<?php
$models = array($music,$genre);
if (is_array($link))
    foreach ($link as $lnk)
        $models[] = $lnk;
else
    $models[] = $link;
//die(var_dump($models));
?>
<?php echo $form->errorSummary($models); ?>
<div class="span6 first">
    <?php echo $form->textFieldRow($music,'artist',array('class'=>'span12','maxlength'=>255)); ?>
    <?php echo $form->textFieldRow($music,'title',array('class'=>'span12','maxlength'=>255)); ?>
    <?php echo $form->textAreaRow($music,'desc',array('rows'=>3, 'cols'=>60, 'class'=>'span12')); ?>
    <?php echo $form->select2Row($genre, 'genre_id', array(
                        'asDropDownList' => false,
                        'val' => MusicGenre::model()->getMusicGenresNames($music->id),
                        'options' => array(
                            'data' => Genre::model()->getGenres(),
                            'placeholder' => 'Escreva um ou mais categorias, separadas por virgulas',
                            'containerCssClass' => 'span12',
                            'tokenSeparators' => array(','),
                            'multiple'=>true,
                            'width'=>'none',
                            'initSelection' => 'js:function (element, callback) {
                                  var val = [];
                                  $(element.val().split(",")).each(function () {
                                      val.push({id: this, text: this});
                                  });
                                  callback(val);
                            }'
                        )
                      )); ?>
    <?php echo $form->maskedTextFieldRow($music,'length',array('mask'=>'99:99?:99','htmlOptions'=>array('class'=>'span12','maxlength'=>8)),array('hint'=>Yii::t('b2r','No formato {f1} ou {f2}',array('{f1}'=>'<i class="label label-info">MM:SS</i>','{f2}'=>'<i class="label label-info">HH:MM:SS</i>')))); ?>
    <?php echo $form->maskedTextFieldRow($music,'size',array('mask'=>'?~~~.~~','charMap'=>array('~'=>'^[0-9]+('.)?[0-9]{0,2}$'),'placeholder'=>'0','htmlOptions'=>array('class'=>'span12','maxlength'=>8)),array('hint'=>Yii::t('b2r','No formato {f1} ou {f2}',array('{f1}'=>'<i class="label label-info">xx.xx</i>','{f2}'=>'<i class="label label-info">xxx.xx</i>')))); ?>
</div>
<div class="span6">
    <div class="controls">
        <ul class="thumbnails">
            <li class="span5">
                <a class="thumbnail">
                <?php echo CHtml::image('/images/capa.gif',Yii::t('b2r','Previsualização da Imagem'),
                        array('id'=>'previewHolder','width'=>'170px','height'=>'170px')); ?>
                </a>
            </li>
        </ul>
    </div>
    <?php echo $form->textFieldRow($music,'image',array('class'=>'span8','maxlength'=>255),array('controlCss'=>'skipcopy','append'=>'<a href="#" id="findcover" data-toggle="tooltip" title="'.Yii::t('b2r','Procurar capa no Google').'"><i class="icon-circle-arrow-right"></i></a>')); ?>
    <?php
    if ($music->isNewRecord){
        echo $form->textFieldRow($link,'link',
            array('name'=>'MusicLink[links][0]','value'=>'','maxlength'=>255, 'class'=>'span8'),
            array('controlCss'=>'copy clonable','append'=>'<a id="copylink" href="#" rel=".copy"><i class="icon-plus"></i></a> ')
        );
    }else{
        $last = count($link)-1;
        foreach ($link as $k => $v)
            die(var_dump($link));
            $id = (is_null($v)) ? $k : $v->id;
            if($k == $last)
                echo $form->textFieldRow($v,'link',
                    array('name'=>'MusicLink[links]['.$id.']','maxlength'=>255, 'class'=>'span8'),
                    array('controlCss'=>'copy clonable','label'=>'<span class="required">*</span>','append'=>'<a id="copylink" href="#" rel=".copy"><i class="icon-plus"></i></a> ')
                );
            else
                echo $form->textFieldRow($v,'link',
                    array('name'=>'MusicLink[links]['.$id.']','maxlength'=>255, 'class'=>'span8'),
                    array('controlCss'=>'clonable')
                );
    }
    ?>
<?php
$this->widget('ext.jqrelcopy.JQRelcopy',array(
 //the id of the 'Copy' link in the view, see below.
 'id' => 'copylink',
  //add a icon image tag instead of the text
  //leave empty to disable removing
 'removeText' => '<i class="icon-remove"></i>',
 //htmlOptions of the remove link
 'removeHtmlOptions' => array('style'=>'margin-left:2px;padding:3px 10px;','class'=>'btn btn-small btn-danger'),
 //options of the plugin, see http://www.andresvidal.com/labs/relcopy.html
 'options' => array(
       //A class to attach to each copy
      'copyClass'=>'newcopy',
      // The number of allowed copies. Default: 0 is unlimited
      'limit'=>6,
      //Option to clear each copies text input fields or textarea
      'clearInputs'=>true,
      //A jQuery selector used to exclude an element and its children
      'excludeSelector'=>'.skipcopy',
      //Additional HTML to attach at the end of each copy.
      //'append'=>CHtml::tag('span',array('class'=>'hint'),'You can remove this line'),
       //'jsAfterNewId' => "if(typeof $(this > input).attr('name') !== 'undefined'){ $(this > input).attr('name', $(this > input).attr('name').replace('new', 'new_'+counter));}",
   )
));
?>
</div>
<?php $collapse = $this->beginWidget('bootstrap.widgets.TbCollapse',array('htmlOptions'=>array('class'=>'span12 first'))); ?>
    <div class="accordion-group">
        <div class="accordion-heading">
            <a class="accordion-toggle" data-toggle="collapse"
            data-parent="#accordion2" href="#collapseOne">
            Avançado
            </a>
        </div>
        <div id="collapseOne" class="accordion-body collapse">
            <div class="accordion-inner">
                <?php echo $form->textFieldRow($music,'bitrate',array('class'=>'span9')); ?>
            </div>
        </div>
    </div>
<?php $this->endWidget(); ?>
</fieldset>
<div class="form-actions">
    <?php $this->widget('bootstrap.widgets.TbButton', array(
            'buttonType'=>'submit',
            'type'=>'primary',
            'label'=>$music->isNewRecord ? Yii::t('b2r','Create') : Yii::t('b2r','Save'),
        )); ?>
</div>
<?php $this->endWidget(); ?>
<?php Yii::app()->clientScript->registerScript('script', "
   $('#Music_image').change(function() {
       $('#previewHolder').attr('src',$(this).val());
   });
   $('#findcover').click(function() {
       var q = $('#Music_artist').val();
       q += ' - '+$('#Music_title').val();
       q += ' cover';
       window.open('https://www.google.pt/search?q='+escape(q)+'&tbm=isch', '_blank');
   });
"
, CClientScript::POS_READY);?>

编辑

使用这段代码,我可以正确地保存所有内容,但是,例如,如果我不选择Genre,或者不输入到文件的链接,则只保存音乐。但是,例如,如果缺少一个必需的字段Music Model,以及musicgenre或MusicLink模型,则验证所有三个模型。如果错误只属于MusicGenre或MusicLink模型,则保存音乐,但不保存这两个模型


我使用Yii版本1.1.14,与YiiBoostrap和YiiBoilerplate

以下是这些插件的链接:

http://www.andresvidal.com/labs/relcopy.html

http://ivaynberg.github.io/select2/

http://yiibooster.clevertech.biz/

你可以使用sluderitz开发的averelatedbehavior组件来保存相关模型,你可以在这里下载

安装以上扩展后,你可以这样做

$music->attributes=$_POST['Music'];
if (isset($_POST['MusicGenre']))
{
    $music->musicgenre= $_POST['MusicGenre'];
}
if ($music->saveWithRelated('musicgenre'))
    $this->redirect(array('view', 'id' => $model->id));

注意:我们可以做$music->musicgenre,因为musicgenre是一个关系名称。

使用上述扩展保存多个相关模型的详细解决方案可在我的博客文章中找到。

好了,现在我知道你想要什么了。

在create方法中,当您测试generes或links数组是否有效时,您可以生成错误消息并重定向到更新操作,并要求用户插入generes或links数据。

在actionCreate:

if($music->save()){
     ......
    if(!in_array(false,$valid)){
        $genre->save();
        $link->save();
        $this->redirect(array('view','id'=>$music->id));
    } 
    else {
        Yii::app()->user->setFlash('error',"Some cool warning message");
        $this->redirect(array('update','id'=>$music->id)));
    }
}
     ......

在ActionUpdate中做同样的事情:

........
if(!in_array(false,$valid))
        $this->redirect(array('view','id'=>$music->id));
else
        Yii::app()->user->setFlash('error',"Some cool warning message");

现在,在更新视图文件中(而不是在_form中),您必须显示错误消息:

<?php if(Yii::app()->user->hasFlash('error')):?>
<div class="flash-error">
    <?php echo Yii::app()->user->getFlash('error'); ?>
</div>
<?php endif; ?>

"flash-error" DIV有一个默认的CSS定义,参见这里输入链接描述。

我希望它能帮助你。

另一种可能的解决方案可以定义一个数组的类型和链接到音乐模型。然后,在调用$music-> save之前,在这些变量中分配表单的值。在音乐模型中,您必须为数组编写验证器规则,并在 beforeave () afterSave()方法中编写代码以插入类型行和链接行。