Model not inserted due to validation error

So it might be a framework issue or it might be a DB issue, but when attempting to insert into a cross table that references an item and a tag it says that the item id is invalid.

Looking through the logs, the item insert is successful and I get an incremented ID back for the item. If I comment out the rollback (it’s in a transaction) then I can see the item in the DB. So I’m not sure where the issue lies, during the transaction I query the DB for the item by the new ID and it returns null. I thought maybe it was the transaction causing the issue, so I tried commenting that out, but I still got the same results.

Any idea? The item model has an ID after it saves and each subsequent rollback increases the ID so it’s definitely inserting it at some point, but it doesn’t exist in the database until after the whole process is complete…

Yii 2.0.13.1

MySQL 5.6.36

1 Like

Please share some of your code to help us understand your problem. Frankly saying, I don’t understand it at all. :(

Guidelines For Posting In This Forum

http://www.yiiframework.com/forum/index.php/topic/19451-guidelines-for-posting-in-this-forum/

This is the form model

[spoiler]


<?php


namespace app\models\form;


use Yii;

use yii\base\Model;

use yii\web\UploadedFile;

use yii\helpers\StringHelper;

use yii\helpers\Inflector;

use app\models\Theme;

use app\models\Tag;

use app\models\ThemeTag;


/**

 * 

 * @property Theme $theme

 * @property ThemeTag $themeTag

 * @property ThemeTags[] $themeTags

 */

class ThemeForm extends \yii\base\Model {	

	/**

	 * 

	 * @var \app\models\Theme

	 */

	private $_theme;

	

	/**

	 * 

	 * @var \app\models\Tag

	 */

	private $_tags;

	

	/**

	 * 

	 * @var \app\models\ThemeTag[]

	 */

	private $_themetag;

	

	/**

	 * 

	 * {@inheritDoc}

	 * @see \yii\base\Model::rules()

	 */

	public function rules () {

		return [

			[['Theme', 'Tags'], 'required'],

		];

	}

	

	public function beforeValidate() {

		$this->theme->image = UploadedFile::getInstance($this->theme, 'image');

		

		if(parent::beforeValidate()) {

			return true;

		}

		

		return false;

	}

	

	/**

	 * 

	 * {@inheritDoc}

	 * @see \yii\base\Model::afterValidate()

	 */

	public function afterValidate() {

		if(!Model::validateMultiple($this->getAllModels())) {

			$this->addError(null);

		}

		

		parent::afterValidate();

	}

	

	/**

	 * Save the form data

	 * @return boolean

	 */

	public function save () {

		if(!$this->validate()) {

			return false;

		}

		

		$transaction = Yii::$app->db->beginTransaction();

		Yii::info('Theme ID beforeSave: ' . $this->theme->id);

		if(!$this->theme->save()) {

			Yii::info($this->theme->errors);

			$transaction->rollBack();

			return false;

		}

		Yii::info('Theme ID afterSave: ' . $this->theme->id);

				

		if(!$this->saveTags()) {

			$transaction->rollBack();

			return false; 

		}

		

		$transaction->commit();

		return true;

	}

	

	/**

	 * Save the tags

	 * @return boolean

	 */

	public function saveTags() {

		Yii::info(Theme::findOne(['id' => $this->theme->id]));

		foreach($this->_tags as $key => $tag) {

			if(!$existing_tag = Tag::findOne(['value' => $tag->value])) {

				Yii::info('Save new tag: ' . $tag->title);

				if(!$tag->save()) {

					return false;

				}

			}

			else {

				$tag = $existing_tag;

			}

			

			$this->_themetag[$key]->tag_id = $tag->id;

			$keep[] = $tag->id;

		}

		

		$query = ThemeTag::find()->andWhere(['theme_id' => $this->theme->id]);

		

		if($keep) {

			$query->andWhere(['not in', 'tag_id', $keep]);

		}


		foreach($query->all() as $themetag) {

			$themetag->delete();

		}

		

		$used_tags = [];

		foreach($this->_themetag as $themetag) {

			$assigned = false;

			foreach($this->theme->themeTags as $existing_tag) {

				Yii::info('Existing tag: ' . $existing_tag->tag_id . ' Form tag: ' . $themetag->tag_id);

				if($existing_tag->tag_id === $themetag->tag_id || in_array($themetag->tag_id, $used_tags)) {

					Yii::info('is assigned ... set');

					$assigned = true;

					break;

				}

			}

			

			if($assigned) {

				Yii::info('is assigned ... continue');

				continue;

			}

			

			

			$themetag->theme_id = $this->theme->id;

			

			Yii::info('Theme ID: ' . $this->theme->id);

			Yii::info('Save tag: ' . $themetag->tag_id . ' To Theme: ' . $this->theme->id);

			Yii::info('ThemeTag Theme ID: ' . $themetag->theme_id);

			if(!$themetag->save()) {

				Yii::info($themetag->errors);

				Yii::info($themetag->theme_id);

				return false;

			}

			

			$used_tags[] = $themetag->tag_id;

		}

		

		return true;

	}

	

	

	/**

	 * 

	 * @return \app\models\Theme

	 */

	public function getTheme () {

		return $this->_theme;

	}

	

	/**

	 * 

	 * @param array|\app\models\Theme $theme

	 */

	public function setTheme ($theme) {

		if($theme instanceof Theme) {

			$this->_theme = $theme;

		}

		else if (is_array($theme)) {

			$this->_theme->setAttributes($theme);

		}

	}

	

	/**

	 * 

	 * @return \app\models\Tag

	 */

	public function getTag () {

		$tag = new Tag;		

		return $tag;

	}

	

	/**

	 * 

	 * @return array|\app\models\Tag[]

	 */

	public function getTags() {

		if($this->_tags === null) {

			$this->_tags = $this->theme->isNewRecord ? [] : $this->theme->themeTags;

		}

		

		return $this->_tags;

	}

	

	/**

	 * Get the theme tags and merge them into a CSV string

	 * @return string

	 */

	public function getTagsList() {

		if($this->_tags === null) {

			$this->_tags = $this->theme->isNewRecord ? [] : $this->theme->themeTags;

		}

		$tags = [];

		

		foreach($this->_tags as $tag) {

			if($tag instanceof Tag) {

				$tags[] = $tag->title;

			}

			else {

				$tags[] = $tag->tag->title;

			}

		}

		return implode(', ', $tags);

	}


	/**

	 * Set an array of tags

	 *

	 * @param string|array|\app\models\Tag $tag

	 */

	public function setTags ($tags) {

		if(is_array($tags)) {

			$this->_tags = [];

			foreach($tags as $key => $tag) {

				$this->_tags[$key] = $this->getTag();

				$this->_tags[$key]->setAttributes($tag);

				

				$this->_themetag[$key] = $this->getThemeTag();

			}

		}

		else if(is_string($tags)) {

			$tags = StringHelper::explode($tags, ',', true, true);

			$list = [];

			foreach($tags as $tag) {

				$list[] = [

					'title' => $tag,

					'value' => Inflector::slug($tag)

				];

			}

			$this->setTags($list);

		}

		else if ($tags instanceof Tag) {

			$this->_tags[$tags->id] = $tags;

		}

	}

	

	public function getThemeTag() {

		$themetag = new ThemeTag;

		return $themetag;

	}

	

	/**

	 * Get the tags for the current theme

	 * 

	 * @return \app\models\ThemeTag[]

	 */

	public function getThemeTags () {

		if($this->_themetag === null) {

			if($this->theme->isNewRecord) {

				$this->_themetag[] = new ThemeTag();

			}

			else {

				$this->_themetag = $this->theme->themeTags;

			}

		}

		

		return $this->_themetag;

	}

	

	/**

	 * Set the tags for the current theme

	 * 

	 * @param array|\app\models\ThemeTag $themetag

	 */

	public function setThemeTags ($themetag) {

		if(is_array($themetag)) {

			$this->themeTags->setAttributes($themetag);

		}

		else if ($themetag instanceof ThemeTag) {

			$this->_themetag[] = $themetag;

		}

	}

	

	/**

	 * 

	 * @param \yii\widgets\ActiveForm $form

	 * @return string

	 */

	public function errorSummary ($form) {

		$errorLists = [];

		foreach($this->getAllModels() as $id => $model) {

			$errorList = $form->errorSummary($model, [

				'header' => '<p>Please fix the following errors for <b>'.$id.'</b></p>',

			]);

			$errorList = str_replace('<li></li>', '', $errorList);

			$errorLists[] = $errorList;

		}

		return implode('', $errorLists);

	}

	

	/**

	 * 

	 * @return NULL[]|\app\models\Tag[]|\yii\db\ActiveRecord[]|array[]

	 */

	private function getAllModels () {

		$models = [

			'Theme' => $this->theme,

		];

		

		foreach($this->tags as $id => $tag) {

			$models['Tag.' . $id] = $tag;

		}

		

		foreach($this->themeTags as $key => $themeTag) {

			$models['ThemeTag.' . $themeTag->id] = $themeTag;

		}

		

		return $models;

	}

}

[/spoiler]

Theme model

[spoiler]


<?php


namespace app\models;


use Yii;

use yii\base\NotSupportedException;

use yii\behaviors\SluggableBehavior;


/**

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

 *

 * @property integer $id

 * @property integer $user_id

 * @property string $created_on

 * @property resource $created_ip

 * @property string $last_updated_on

 * @property resource $last_update_ip

 * @property string $title

 * @property string $slug

 * @property boolean $is_published

 * @property string $published_on

 * @property boolean $is_deleted

 * @property string $deleted_on

 * @property boolean $is_featured

 * @property string $featured_on

 * @property integer $review_count

 * @property string $rating

 * @property integer $installs

 * @property string $display_image

 * @property string $description

 * @property string $template

 * @property string $base_theme

 * @property string $friend_style

 * @property string $comment_style

 * @property resource $sass

 * @property resource $css

 *

 * @property UploadedFile $image

 * @property Theme $featuredThemes

 * @property ThemeReviews[] $themeReviews

 * @property ThemeReview $themeReview

 * @property User $user

 * @property ThemeCategory[] $themeCategories

 * @property ThemeSpotlight $themeSpotlight

 * @property ThemeTag[] $themeTags

 */

class Theme extends \yii\db\ActiveRecord

{

	/**

	 * 

	 * @var string

	 */

	const SCENARIO_CREATE = 'create';

	

	/**

	 * 

	 * @var string

	 */

	const SCENARIO_EDIT = 'edit';

	

	/**

	 * 

	 * @var string

	 */

	const SCENARIO_PUBLISH = 'publish';

	

	/**

	 * 

	 * @var string

	 */

	const SCENARIO_FEATURE = 'feature';

	

	/**

	 * 

	 */

	const SCENARIO_RATING = 'rating';

	

	/**

	 * 

	 * @var \yii\web\UploadedFile

	 */

	public $image;

	

    /**

     * @inheritdoc

     */

    public static function tableName()

    {

        return 'theme';

    }


    /**

     * @inheritdoc

     */

	public function rules()

	{

		$valid_ext = Yii::$app->params['theme_values']['image_extensions'];

		$mime_types = Yii::$app->params['theme_values']['mime_types'];

		return [

			[['user_id', 'created_ip', 'last_update_ip', 'title', 'slug', 'display_image', 'description', 'sass', 'css'], 'required'],

			[['user_id'], 'integer'],

			[['created_on', 'last_updated_on', 'published_on', 'deleted_on', 'featured_on'], 'safe'],

			[['is_published', 'is_deleted', 'is_featured'], 'boolean'],

			[['rating', 'review_count'], 'number'],

			[['template', 'base_theme', 'friend_style', 'comment_style', 'sass', 'css'], 'string'],

			[['created_ip', 'last_update_ip'], 'string', 'max' => 16],

			[['title', 'slug'], 'string', 'max' => 45],

			[['display_image'], 'string', 'max' => 75],

			[['description'], 'string', 'max' => 500],

			[['user_id'], 'exist', 'skipOnError' => true, 'targetClass' => User::className(), 'targetAttribute' => ['user_id' => 'id']],

			[['category_id'], 'exist', 'skipOnError' => true, 'targetClass' => Category::className(), 'targetAttribute' => ['category_id' => 'id']],

			[['image'], 'image', 'skipOnEmpty' => false, 'message' => 'A display image is required', 'on' => self::SCENARIO_CREATE],

			[['image'], 'image', 'minWidth' => 680, 'maxWidth' => 960,'minHeight' => 100, 'maxHeight' => 540],//, 'message' => 'The image must be 960x540'],

			[['image'], 'image', 'extensions' => $valid_ext, 'mimeTypes' => $mime_types, 'message' => "Invalid file type; Accepted file types are {$valid_ext}"],

		];

	}

    

    public function scenarios () {

	    	$scenarios = parent::scenarios();

	    	$scenarios[self::SCENARIO_CREATE] = [

	    		'title', 'slug', 'description', 'category_id', 'created_on', 'created_ip', 'last_updated_on', 'last_update_ip',

	    		'image', 'display_image' ,'template', 'base_theme', 'friend_style', 'comment_style', 'css', 'sass', 'category_id', 'user_id'];

	    	$scenarios[self::SCENARIO_EDIT] = [

	    		'title', 'slug', 'description', 'category_id', 'last_updated_on', 'last_update_ip',

	    		'template', 'base_theme', 'friend_style', 'comment_style', 'css', 'sass', 'category_id', 'user_id'];

	    	$scenarios[self::SCENARIO_PUBLISH] = [

	    		'is_published', 'published_on'

	    	];

	    	$scenarios[self::SCENARIO_FEATURE] = [

	    		'is_featured', 'featured_on'

	    	];

	    $scenarios[self::SCENARIO_RATING] = [

	    		'rating', 'review_count'

	    	];

	    	return $scenarios;

    }


    /**

     * @inheritdoc

     */

    public function attributeLabels()

    {

        return [

            'id' => 'ID',

            'user_id' => 'User ID',

            'created_on' => 'Created On',

            'created_ip' => 'Created Ip',

            'last_updated_on' => 'Last Updated On',

            'last_update_ip' => 'Last Update Ip',

            'title' => 'Title',

            'slug' => 'Slug',

            'is_published' => 'Is Published',

            'published_on' => 'Published On',

            'is_deleted' => 'Is Deleted',

            'deleted_on' => 'Deleted On',

            'is_featured' => 'Is Featured',

            'featured_on' => 'Featured On',

            	'review_count' => 'Review Count',

            'rating' => 'Rating',

            'installs' => 'Installs',

            'display_image' => 'Display Image',

            'description' => 'Description',

            'template' => 'Template',

            'base_theme' => 'Base Theme',

            'friend_style' => 'Friend Style',

            'comment_style' => 'Comment Style',

            'sass' => 'Sass',

            'css' => 'Css',

			'category_id' => 'Category',

        ];

    }

    

    public function beforeValidate() {

    		if($this->isNewRecord) {

    			$this->created_ip = inet_pton(Yii::$app->request->userIP);

    			$this->user_id = Yii::$app->user->id;

    			

    			if(isset($this->image)) {    				

	    			$this->display_image = $this->slug . '.' . $this->image->extension;

    			}

    		}

    		

    		

    		$this->last_update_ip = inet_pton(Yii::$app->request->userIP);

    		

    		$sassc = SassC::createFile($this->sass)->setCompression('compact');

    		$sassc->process();

    		$this->css = trim($sassc->css);

    		$sassc->removeFile();

    		

    		if(parent::beforeValidate()) {

    			return true;

    		}

    		return false;

    }

    

    public function behaviors() {

	    	return [

	    		[

	    			'class' => SluggableBehavior::className(),

	    			'attribute' => 'title',

	    			'slugAttribute' => 'slug'

	    		]

	    	];

    }

    

    /**

     * Upload the display image

     * @return boolean

     */

    public function saveFile() {

    		if($this->validate()) {

    			if(!is_dir(Yii::getAlias(Yii::$app->params['theme_upload_path']) . $this->slug)) {

    				mkdir(Yii::getAlias(Yii::$app->params['theme_upload_path']) . $this->slug);

    			}

    			

    			return $this->image->saveAs(Yii::getAlias(Yii::$app->params['theme_upload_path']) . $this->slug . '/' . $this->display_image);

    		}

    			

    		return false;

    }

    

    /**

     * 

     * @param integer $limit

     * @return \yii\db\ActiveQuery

     */

    public static function findFeaturedThemes($limit = null) {

    		$query = self::find()->where(['is_featured' => true]);

    		

    		if($limit !== null) {

    			$query->limit($limit);

    		}

    		

    		return $query;

    }


    /**

     * @return \yii\db\ActiveQuery

     */

    public function getUser()

    {

        return $this->hasOne(User::className(), ['id' => 'user_id']);

    }


    /**

     * @return \yii\db\ActiveQuery

     */

    public function getThemeCategories()

    {

        return $this->hasMany(ThemeCategory::className(), ['theme_id' => 'id']);

    }

    

    /**

     * @return \yii\db\ActiveQuery

     */

    public function getThemeInstalls() {

    		return $this->hasMany(ThemeInstall::className(), ['theme_id' => 'id']);

    }

    

    public function getThemeReview() {

    		return $this->hasOne(ThemeReview::className(), ['theme_id' => 'id'])->where(['user_id' => Yii::$app->user->id]);

    }

    

    /**

     * @return \yii\db\ActiveQuery

     */

    public function getThemeReviews() {

    		return $this->hasMany(ThemeReview::className(), ['theme_id' => 'id']);

    }

    

    /**

     * @return \yii\db\ActiveQuery

     */

    public function getUsers()

    {

    	    	return $this->hasMany(User::className(), ['id' => 'user_id'])->viaTable('theme_review', ['theme_id' => 'id']);

    }


    /**

     * @throws \yii\base\NotSupportedException

     * @return \yii\db\ActiveQuery

     */

    public function getThemeSpotlight()

    {

    		throw new NotSupportedException('Not implemented');

        return $this->hasOne(ThemeSpotlight::className(), ['theme_id' => 'id']);

    }


    /**

     * @return \yii\db\ActiveQuery

     */

    public function getThemeTags()

    {

        return $this->hasMany(ThemeTag::className(), ['theme_id' => 'id']);

    }

    

    public static function find($is_published = true, $is_deleted = false) {

    		return parent::find()->where(['is_published' => $is_published, 'is_deleted' => $is_deleted]);

    }

}

[/spoiler]

ThemeTag model

[spoiler]


<?php


namespace app\models;


use Yii;


/**

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

 *

 * @property integer $id

 * @property integer $theme_id

 * @property integer $tag_id

 *

 * @property User $tag

 * @property Theme $theme

 */

class ThemeTag extends \yii\db\ActiveRecord

{

    /**

     * @inheritdoc

     */

    public static function tableName()

    {

        return 'theme_tag';

    }


    /**

     * @inheritdoc

     */

    public function rules()

    {

        return [

//             [['theme_id', 'tag_id'], 'required'],

            [['theme_id', 'tag_id'], 'integer'],

            [['tag_id'], 'exist', 'skipOnError' => true, 'targetClass' => Tag::className(), 'targetAttribute' => ['tag_id' => 'id']],

            [['theme_id'], 'exist', 'skipOnError' => true, 'targetClass' => Theme::className(), 'targetAttribute' => ['theme_id' => 'id']],

        ];

    }


    /**

     * @inheritdoc

     */

    public function attributeLabels()

    {

        return [

            'id' => 'ID',

            'theme_id' => 'Theme ID',

            'tag_id' => 'Tag ID',

        ];

    }


    /**

     * @return \yii\db\ActiveQuery

     */

    public function getTag()

    {

        return $this->hasOne(Tag::className(), ['id' => 'tag_id']);

    }


    /**

     * @return \yii\db\ActiveQuery

     */

    public function getTheme()

    {

        return $this->hasOne(Theme::className(), ['id' => 'theme_id']);

    }

}

[/spoiler]

And here’s the log from the transaction start to the rollback

https://imgur.com/CM0rY4N

ThemeTag::save() fails because the "exist" validator for "theme_id" fails.

You have customized "Theme::find()". It requires "is_published" to be true and "is_deleted" to be false by default. So the "exist" validator will try to find a Theme with its "id" being "theme_id", and "is_published" being true and "is_deleted" being false.

But you failed to set them correctly before trying to save ThemeTag.

It also explains why you get null for “Yii::info(Theme::findOne([‘id’ => $this->theme->id]));”

I completely forgot about that… Since that’s being done by the exist validator is there a way to change the condition of those fields?

What about writing an inline validator for theme_id?

http://www.yiiframework.com/doc-2.0/guide-input-validation.html#inline-validators

That’ll work, thanks!