Ungewollt Mehrere Einträge In Die Db

Hallo,

ich hab seit heute ein sehr komisches Problem mit meinem Demo-Projekt (Preisvergleich für PC Spiele):

Ich erzeuge per Formular einen "Shop", dieser wurde bis gestern korrekt in die MySQL DB eingetragen.

Ab heute wird nach jedem absenden des Formulars jeder Shop 2x eingetragen (1x absenden klicken = 2 Einträge in der Tabelle).

Was noch komischer ist:

Erstelle ich ein "Spiel" per Formular, wurde dieses ebenfalls bis gestern nur 1x in die entsprechende Tabelle eingetragen. Heute wird es jedoch 2x pro abgesendeten Formular eingetragen.

Und weil mir eh keiner glaubt der Gag:

Füge ich einen "preisvergleich" hinzu, wurde dieser bisher nur 1x pro absenden eingetragen. Und heute? 3x pro klick! Ich weiss, dass ich seit gestern abend auf keinen Fall was an den drei genannten Modellen,Controllern usw. geändert habe, was diese Verhalten erklären würde.

Ich habe schon die DB komplett gedroppt und per Script neu erstellt, das Projekt ist SVN-verwaltet, und jeder ausgecheckte Stand zeigt dieses Verhalten.

Füge ich in rules()


array('name','unique');

sind die beschriebenen Probleme weg! Aber dieser Eintrag war noch nie vorhanden und es funktionierte trotzdem.

Ok, der Eintrag schadet nicht und macht Sinn. Ich will aber wissen woher das komische Verhalten aufeinmal kommt.

Ich bedanke mir vorab schon für jede Hilfe.

Anbei der Code für "Shop":

Model:


<?php


/**

 * This is the model class for table "tbl_shop".

 *

 * The followings are the available columns in table 'tbl_shop':

 * @property integer $id

 * @property string $name

 * @property string $imageurl

 * @property string $affiliateurl

 * @property string $zahlungsarten

 * @property integer $aktiv

 *

 * The followings are the available model relations:

 * @property Preisvergleich[] $preisvergleiches

 */

class Shop extends CActiveRecord

{

	/**

	 * Returns the static model of the specified AR class.

	 * @param string $className active record class name.

	 * @return Shop the static model class

	 */

	public static function model($className=__CLASS__)

	{

		return parent::model($className);

	}


	/**

	 * @return string the associated database table name

	 */

	public function tableName()

	{

		return 'tbl_shop';

	}


	/**

	 * @return array validation rules for model attributes.

	 */

	public function rules()

	{

		// NOTE: you should only define rules for those attributes that

		// will receive user inputs.

		return array(

			array('name, imageurl, affiliateurl, zahlungsarten, aktiv', 'required'),

            

			array('aktiv', 'numerical', 'integerOnly'=>true),

			array('name, imageurl, affiliateurl', 'length', 'max'=>256),

			array('zahlungsarten', 'length', 'max'=>1024),

			// The following rule is used by search().

			// Please remove those attributes that should not be searched.

			array('id, name, imageurl, affiliateurl, zahlungsarten, aktiv', 'safe', 'on'=>'search'),

		);

	}


	/**

	 * @return array relational rules.

	 */

	public function relations()

	{

		// NOTE: you may need to adjust the relation name and the related

		// class name for the relations automatically generated below.

		return array(

			'preisvergleiches' => array(self::HAS_MANY, 'Preisvergleich', 'shop_id'),

		);

	}


	/**

	 * @return array customized attribute labels (name=>label)

	 */

	public function attributeLabels()

	{

		return array(

			'id' => 'Shop ID',

			'name' => 'Name',

			'imageurl' => 'Image URL',

			'affiliateurl' => 'Affiliate URL',

			'zahlungsarten' => 'Zahlungsarten',

			'aktiv' => 'Aktiv',

		);

	}


	/**

	 * Retrieves a list of models based on the current search/filter conditions.

	 * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.

	 */

	public function search()

	{

		// Warning: Please modify the following code to remove attributes that

		// should not be searched.


		$criteria=new CDbCriteria;


		$criteria->compare('id',$this->id);

		$criteria->compare('name',$this->name,true);

		$criteria->compare('imageurl',$this->imageurl,true);

		$criteria->compare('affiliateurl',$this->affiliateurl,true);

		$criteria->compare('zahlungsarten',$this->zahlungsarten,true);

		$criteria->compare('aktiv',$this->aktiv);


		return new CActiveDataProvider($this, array(

			'criteria'=>$criteria,

		));

	}

}

Controller:


<?php


class ShopController extends Controller

{

	/**

	 * @var string the default layout for the views. Defaults to '//layouts/column2', meaning

	 * using two-column layout. See 'protected/views/layouts/column2.php'.

	 */

	public $layout='//layouts/column2';


	/**

	 * @return array action filters

	 */

	public function filters()

	{

		return array(

			'accessControl', // perform access control for CRUD operations

			'postOnly + delete', // we only allow deletion via POST request

		);

	}


	/**

	 * Specifies the access control rules.

	 * This method is used by the 'accessControl' filter.

	 * @return array access control rules

	 */

	public function accessRules()

	{

		return array(

			array('allow',  // allow all users to perform 'index' and 'view' actions

				'actions'=>array('index','view'),

				'users'=>array('*'),

			),

			array('allow', // allow authenticated user to perform 'create' and 'update' actions

				'actions'=>array('create','update'),

				'users'=>array('@'),

			),

			array('allow', // allow admin user to perform 'admin' and 'delete' actions

				'actions'=>array('admin','delete'),

				'users'=>array('admin'),

			),

			array('deny',  // deny all users

				'users'=>array('*'),

			),

		);

	}


	/**

	 * Displays a particular model.

	 * @param integer $id the ID of the model to be displayed

	 */

	public function actionView($id)

	{

		$this->render('view',array(

			'model'=>$this->loadModel($id),

		));

	}


	/**

	 * Creates a new model.

	 * If creation is successful, the browser will be redirected to the 'view' page.

	 */

	public function actionCreate()

	{

		$model=new Shop;


		// Uncomment the following line if AJAX validation is needed

		// $this->performAjaxValidation($model);


		if(isset($_POST['Shop']))

		{

			$model->attributes=$_POST['Shop'];

			if($model->save())

				$this->redirect(array('view','id'=>$model->id));

		}


		$this->render('create',array(

			'model'=>$model,

		));

	}


	/**

	 * 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)

	{

		$model=$this->loadModel($id);


		// Uncomment the following line if AJAX validation is needed

		// $this->performAjaxValidation($model);


		if(isset($_POST['Shop']))

		{

			$model->attributes=$_POST['Shop'];

			if($model->save())

				$this->redirect(array('view','id'=>$model->id));

		}


		$this->render('update',array(

			'model'=>$model,

		));

	}


	/**

	 * Deletes a particular model.

	 * If deletion is successful, the browser will be redirected to the 'admin' page.

	 * @param integer $id the ID of the model to be deleted

	 */

	public function actionDelete($id)

	{

		$this->loadModel($id)->delete();


		// if AJAX request (triggered by deletion via admin grid view), we should not redirect the browser

		if(!isset($_GET['ajax']))

			$this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('admin'));

	}


	/**

	 * Lists all models.

	 */

	public function actionIndex()

	{

		$dataProvider=new CActiveDataProvider('Shop');

		$this->render('index',array(

			'dataProvider'=>$dataProvider,

		));

	}


	/**

	 * Manages all models.

	 */

	public function actionAdmin()

	{

		$model=new Shop('search');

		$model->unsetAttributes();  // clear any default values

		if(isset($_GET['Shop']))

			$model->attributes=$_GET['Shop'];


		$this->render('admin',array(

			'model'=>$model,

		));

	}


	/**

	 * Returns the data model based on the primary key given in the GET variable.

	 * If the data model is not found, an HTTP exception will be raised.

	 * @param integer $id the ID of the model to be loaded

	 * @return Shop the loaded model

	 * @throws CHttpException

	 */

	public function loadModel($id)

	{

		$model=Shop::model()->findByPk($id);

		if($model===null)

			throw new CHttpException(404,'The requested page does not exist.');

		return $model;

	}


	/**

	 * Performs the AJAX validation.

	 * @param Shop $model the model to be validated

	 */

	protected function performAjaxValidation($model)

	{

		if(isset($_POST['ajax']) && $_POST['ajax']==='shop-form')

		{

			echo CActiveForm::validate($model);

			Yii::app()->end();

		}

	}

}



_form:


<?php

/* @var $this PreisvergleichController */

/* @var $model Preisvergleich */

/* @var $form CActiveForm */

?>

<div class="form">

  <fieldset>

    <legend>Daten</legend>

    <?php

    $form = $this->beginWidget('foundation.widgets.FounActiveForm', array(

        'id' => 'shop-form',

        'enableAjaxValidation' => true,

    ));

    ?>

    <div class="row">

      <div class="four columns">

        <?php echo $form->labelEx($model, 'name'); ?>

      </div>

    </div>


    <div class="row">

      <div class="six columns">

        <?php echo $form->textField($model,'name',array('size'=>60,'maxlength'=>256)); ?>

      </div>

      <div class="six columns">

        <?php echo $form->error($model, 'name'); ?>

      </div>

    </div>


    <div class="row">

      <div class="four columns">

        <?php echo $form->labelEx($model, 'imageurl'); ?>

      </div>

    </div>

    <div class="row">

      <div class="six columns">

        <?php echo $form->textField($model,'imageurl',array('size'=>60,'maxlength'=>256)); ?>

      </div>

      <div class="six columns">

        <?php echo $form->error($model, 'imageurl'); ?>

      </div>

    </div>


    <div class="row">

      <div class="four columns">

        <?php echo $form->labelEx($model, 'affiliateurl'); ?>

      </div>

    </div>

    <div class="row">

      <div class="six columns">

        <?php echo $form->textField($model,'affiliateurl',array('size'=>60,'maxlength'=>256)); ?>

      </div>

      <div class="six columns">

        <?php echo $form->error($model, 'affiliateurl'); ?>

      </div>

    </div>


    <div class="row">

      <div class="four columns">

        <?php echo $form->labelEx($model, 'zahlungsarten'); ?>

      </div>

    </div>

    <div class="row">

      <div class="six columns">

        <?php echo $form->textField($model,'zahlungsarten',array('size'=>60,'maxlength'=>1024)); ?>

      </div>

      <div class="six columns">

        <?php echo $form->error($model, 'zahlungsarten'); ?>

      </div>

    </div>


    <div class="row">

      <div class="four columns">

        <?php echo $form->labelEx($model, 'aktiv'); ?>

      </div>

    </div>

    <div class="row">

      <div class="two columns">

        <?php echo $form->dropDownList($model, 'aktiv', array(0 => "Nein", 1 => "Ja")); ?>

      </div>

      <div class="four columns">

        <?php echo $form->error($model, 'aktiv'); ?>

      </div>

    </div>

    <br/>


    <div class="row">

      <div class="two columns">

        <?php echo CHtml::submitButton($model->isNewRecord ? 'Erstellen' : 'Speichern', array('class' => 'button')); ?>

      </div>

      <div class="ten columns">

        Felder mit <span class="required">*</span> werden zwingend benötigt.

      </div>      

    </div>


    <?php $this->endWidget(); ?>

  </fieldset>

</div><!-- form -->



Im Controller in ActionUpdate und ActionCreate hast du folgendes:




                // Uncomment the following line if AJAX validation is needed

                // $this->performAjaxValidation($model);



Im __form.php view steht:




        'enableAjaxValidation' => true,



Das passt nicht zusammen, du musst im Controller in beiden actions auch AjaxValididerung einschalten, sonst wird bei jedem Validierungsversuch ein Eintrag in der Tabelle gespeichert, anstatt nur die Validierung geprüft.

Ha, das kann ja eine echt fiese Falle sein, weil die Zeile ja von Haus aus auskommentiert ist. Bei falschen Eingaben passiert dann wie gewollt nix und bei richtigen wird dafür gleich 2 mal gespeichert :)

Das sollte man fast im automatisch generierten Kommentar erwähnen

Pull request is welcome ;)