Jump to content

Guardar e Validar 2 Models Relacionados com Yii Framework


I-NOZex
 Share

Recommended Posts

Bem, vou tentar aqui a minha sorte, espero encontrar alguem que me consiga ajudar, mas sei que (ainda) não são muitos os que por aqui usam Yii Framework.

Então é assim, tenho 3 models:

Music

MusicGenre

MusicLink

ambos models correspondem a 3 tabelas, sendo a MusicGenre e MusicLink, tabelas auxiliares, que fazem a ligação entre a tabela Music, e as tabelas Genres e Links, respetivamente, isto porque uma Música pode ter varios links de ficheiros, bem como ter varios estilos.

no MusicController tenho a action create e update, entre outras

no form associado a essas actions, tenho para alem dos campos da tabela/model music mais 2 campos, um campo para escolher os genres musicais, o que existe aqui no forum, quando se cria um topico, e permite meter varias tags, usando o Select2, que envia os ids dos generos por post, num unico indice, em que os valores são separados por virgulas (ex: MusicGenre[genre_id] = 1,7,32,4)

tenho depois um outro campo de texto, que corresponde ao link da musica, sendo que este campo pode ser clonado até ter 6 copias, usando o plugin RelCopy, e envia varios links, caso existam, por exemplo: MusicLink[link][0] = http://servidor.com/musica1.mp3, MusicLink[link][1] = http://servidor.com/musica2.mp3

O meu problema está em como guardar estes dados correctamente ?

até agora, tenho este codigo:

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['MusicLink']['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['MusicLink']['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);?>

O codigo funciona, mas tem alguns "bugs".

imaginando que se tenta enviar o form, a faltar algum campo obrigatorio do model Music e os 2 campos dos models MusicLink e MusicGenre por preencher, ele ai valida os 3 models, e não guarda nenhum.

Mas se preencher todos os campos do model Music e deixar por preencher algum campo dos models MusicLink ou MusicGenre, ele ai guarda a musica, e não consigo fazer com que ele valide os outros 2 models alem do MUSIC e que retorne para o form, como é o habitual...

não sei que consegui explicar bem, isto ta-me a matar o juiso...

Estou a usar a Yii 1.1.14, com o YiiBoilerplate e Yiibooster

ficam aqui os links para os plugins usados e mensionados..:

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

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

http://yiibooster.clevertech.biz/

Edited by I-NOZex

B2R » Beat2Revolution v3.0b | Regista e divulga-nos

beat2revolution.net
Link to comment
Share on other sites

mostra-lá os rules que tens nos models correspondentes.

imaginando que se tenta enviar o form, a faltar algum campo obrigatorio do model Music e os 2 campos dos modelsMusicLink e MusicGenre por preencher, ele ai valida os 3 models, e não guarda nenhum.

if($music->save()){
}

Se faltar algum campo obrigatório do model Music nem no if iria entrar.

Mas se preencher todos os campos do model Music e deixar por preencher algum campo dos models MusicLink ouMusicGenre, ele ai guarda a musica,

Obvio porque estás a fazer save primeiro do music e depois é que fazes as validações do link e do genre.

Contador de calorias: caloriaspordia.com

Link to comment
Share on other sites

a cena é que eu preciso de guardar 1º a musica para poder obter o id dela, e assim relacionar os inserts...

as regras de validação é o basico ainda, so defini os campos obrigatórios, e como isto não encontro forma de funcionar ainda nem valeu a pena meter as outras regras

mas basicamente:

[model]|[atributos]

music -> artist, title, size (mb), length (h:m:s), image, descricao (não obrigatorio)

musicgenre -> genre_id, music_id(atributo processado)

musiclink -> link, music_id(atributo processado)

como referi no OP, musicgenre, leva os ids separados por virgulas, e depois faz-se um explode para ficar em array

o musiclink ja é um array...

B2R » Beat2Revolution v3.0b | Regista e divulga-nos

beat2revolution.net
Link to comment
Share on other sites

a cena é que eu preciso de guardar 1º a musica para poder obter o id dela, e assim relacionar os inserts...

podes usar transactions, basicamente se haver algum problema nos segundos inserts, fazes um rollback()

http://www.yiiframework.com/doc/guide/1.1/en/database.ar#using-transaction-with-ar

Mais específico:

http://www.yiiframework.com/wiki/19/how-to-use-a-single-form-to-collect-data-for-two-or-more-models#c9743

Edited by Devexz

Contador de calorias: caloriaspordia.com

Link to comment
Share on other sites

já testei, mas não funciona

$transaction = Yii::app()->db->beginTransaction();
try
{
    $valid[] = $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['MusicLink']['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();
    $transaction->commit();
    $this->redirect(array('view','id'=>$music->id));
    }
}
catch (Exception $ex)
{
// occoreu um problem, dump the $ex->getErrors()
$transaction->rollback();
}

ele continua a guardar a musica

o que fiz ate agora, foi criar 2 variaveis no model MUSIC para os links e genres, e as suas regras de validação, validar valida, a cena é que continuo sem conseguir atribuir o id, pq se fica no if music->save ou mesmo no beforesave no model, aquilo dá erro:

Array to string conversion

C:\Programaxao\EasyPHP\data\localweb\b2rv4\common\lib\Yii\db\schema\CDbColumnSchema.php(146)

134 * Converts the input value to the type that this column is of.

135 * @param mixed $value input value

136 * @return mixed converted value

137 */

138 public function typecast($value)

139 {

140 if(gettype($value)===$this->type || $value===null || $value instanceof CDbExpression)

141 return $value;

142 if($value==='' && $this->allowNull)

143 return $this->type==='string' ? '' : null;

144 switch($this->type)

145 {

146 case 'string': return (string)$value;

147 case 'integer': return (integer)$value;

148 case 'boolean': return (boolean)$value;

149 case 'double':

150 default: return $value;

151 }

152 }

153 }

Stack Trace

#0

+ C:\Programaxao\EasyPHP\data\localweb\b2rv4\common\lib\Yii\db\schema\CDbCommandBuilder.php(229): CDbColumnSchema->typecast(array("http://www24.zippyshare.com/v/31312171/file.html")) #1

+ C:\Programaxao\EasyPHP\data\localweb\b2rv4\common\lib\Yii\db\ar\CActiveRecord.php(1076): CDbCommandBuilder->createInsertCommand(CMysqlTableSchema, array("nr_dls" => "0", "pubstatus" => 0, "is_dead" => 0, "artist" => "W&W", ...)) #2

+ C:\Programaxao\EasyPHP\data\localweb\b2rv4\common\lib\Yii\db\ar\CActiveRecord.php(806): CActiveRecord->insert(null) #3

– C:\Programaxao\EasyPHP\data\localweb\b2rv4\backend\controllers\MusicController.php(79): CActiveRecord->save()

74 //CActiveForm::validate($link);

75 $music->attributes=$_POST['Music'];

76 //die(var_dump($music->save(false)));

77 //$music->genres=explode(',',$_POST['Music']['genres']);

78

79 if($music->save()){

80

81 //$genre->save();

82 //$link->save();

83 /*foreach(explode(',',$_POST['MusicGenre']['genre_id']) as $gen)

84 {

B2R » Beat2Revolution v3.0b | Regista e divulga-nos

beat2revolution.net
Link to comment
Share on other sites

e assim, funciona?

é que se o save falhar, nao é mandado nenhum exception.

<?php
$transaction = Yii::app()->db->beginTransaction();
try
{
		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['MusicLink']['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();
   $transaction->commit();
   $this->redirect(array('view','id'=>$music->id));
  } else {
      throw new Exception('Error saving');
  }
  }

}
catch (Exception $ex)
{
// occoreu um problem, dump the $ex->getErrors()
$transaction->rollback();
}
?>

Edit: editei o código outra vez na parte do execption.

Edited by Devexz

Contador de calorias: caloriaspordia.com

Link to comment
Share on other sites

@I-NOZed

Precisas salvar o registo de uma música que pode ter associados 1 ou mais géneros, e 1 ou mais links, certo?

Precisas validar se tem pelo menos 1 género e 1 link certo? Não vejo essa validação...

Isto é perigoso...

$music->attributes=$_POST['Music'];
       if($music->save()){
               foreach(explode(',',$_POST['MusicGenre']['genre_id']) as $gen)
               {

Precisas antes de mais validar o $music antes do save() para teres a certeza que não vai falhar o save()

Precisas validar para cada $gen se o tipo de dados é correcto e se os dados existem mesmo na base de dados (algo como select count(id) as n_generos from genero where id IN ('1,2,3') e comparas o total de IDs.

É o que vejo para já...

Link to comment
Share on other sites

@I-NOZed

Precisas salvar o registo de uma música que pode ter associados 1 ou mais géneros, e 1 ou mais links, certo?

Precisas validar se tem pelo menos 1 género e 1 link certo? Não vejo essa validação...

Isto é perigoso...

$music->attributes=$_POST['Music'];
	if($music->save()){
			foreach(explode(',',$_POST['MusicGenre']['genre_id']) as $gen)
			{

Precisas antes de mais validar o $music antes do save() para teres a certeza que não vai falhar o save()

Precisas validar para cada $gen se o tipo de dados é correcto e se os dados existem mesmo na base de dados (algo como select count(id) as n_generos from genero where id IN ('1,2,3') e comparas o total de IDs.

É o que vejo para já...

ao fazermos save a validação é feita automaticamente e caso haja algum problema os erros poderão ser consultados depois pelo getErrors().

No entanto o save não emite qualquer tipo de exeption.

Contador de calorias: caloriaspordia.com

Link to comment
Share on other sites

exacto como devexz disse, apenas retorna true ou false

a unica regra de validaçao que tenho agora é ser campo obrigatorio, nao meti as outras pq ainda nem a guardar tá sequer...

pq dizes que é perigoso? por atribuir logo o $_POST? é assim que se faz...

e sim, depois no genero tenho que usar um range validator, bem como no link tenho de usar um url validator

B2R » Beat2Revolution v3.0b | Regista e divulga-nos

beat2revolution.net
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...

Important Information

By using this site you accept our Terms of Use and Privacy Policy. We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.